lilalo
view l3-frontend @ 100:2c00c61f2d7b
Коммичу изменения, но сам не знаю зачем.
Нужно l3-cgi переписать вообще с нуля.
Он мерзкий.
И продумать нужно, как он вообще должен работать.
Понятно, приблизительно, как он должен показывать журнал,
когда до него уже дошли,
но вот если не дошли, то что делать не понятно.
Короче, продумать систему навигации.
Нужно l3-cgi переписать вообще с нуля.
Он мерзкий.
И продумать нужно, как он вообще должен работать.
Понятно, приблизительно, как он должен показывать журнал,
когда до него уже дошли,
но вот если не дошли, то что делать не понятно.
Короче, продумать систему навигации.
| author | devi | 
|---|---|
| date | Wed Jun 14 21:37:22 2006 +0300 (2006-06-14) | 
| parents | 45196265d30e | 
| children | c41cc9a4b5ea | 
 line source
     1 #!/usr/bin/perl -w
     3 use IO::Socket;
     4 use lib '.';
     5 use l3config;
     6 use utf8;
     8 our @Command_Lines;
     9 our @Command_Lines_Index;
    10 our %Commands_Description;
    11 our %Args_Description;
    12 our $Mywi_Socket;
    13 our %Sessions;
    15 our %filter;
    16 our $filter_url;
    17 sub init_filter;
    19 our %Files;
    21 # vvv Инициализация переменных выполняется процедурой init_variables
    22 our @Day_Name;
    23 our @Month_Name;
    24 our @Of_Month_Name;
    25 our %Search_Machines;
    26 our %Elements_Visibility;
    27 # ^^^
    29 our %Stat;
    30 our %frequency_of_command; # Сколько раз в журнале встречается какая команда
    31 our $table_number=1;
    33 my %mywi_cache_for;         # Кэш для экономии обращений к mywi
    35 sub make_comment;
    36 sub make_new_entries_table;
    37 sub load_command_lines_from_xml;
    38 sub load_sessions_from_xml;
    39 sub sort_command_lines;
    40 sub process_command_lines;
    41 sub init_variables;
    42 sub main;
    43 sub collapse_list($);
    45 sub minutes_passed;
    47 sub print_all_txt;
    48 sub print_all_html;
    49 sub print_edit_all_html;
    50 sub print_command_lines_html;
    51 sub print_command_lines_txt;
    52 sub print_files_html;
    53 sub print_stat_html;
    54 sub print_header_html;
    55 sub print_footer_html;
    57 main();
    59 sub main
    60 {
    61     $| = 1;
    63     init_variables();
    64     init_config();
    65     $Config{frontend_ico_path}=$Config{frontend_css};
    66     $Config{frontend_ico_path}=~s@/[^/]*$@@;
    67     init_filter();
    69     open_mywi_socket();
    70     load_command_lines_from_xml($Config{"backend_datafile"});
    71     load_sessions_from_xml($Config{"backend_datafile"});
    72     sort_command_lines;
    73     process_command_lines;
    74     if (defined($filter{action}) && $filter{action} eq "edit") {
    75         print_edit_all_html($Config{"output"});
    76     }
    77     else {
    78         print_all_html($Config{"output"});
    79     }
    80     close_mywi_socket;
    81 }
    83 sub init_filter
    84 {
    85     if ($Config{filter}) {
    86         # Инициализация фильтра
    87         for (split /&/,$Config{filter}) {
    88             my ($var, $val) = split /=/;
    89             $filter{$var} = $val || "";
    90         }
    91     }
    92     $filter_url = join ("&", map("$_=$filter{$_}", keys %filter));
    93 }
    95 # extract_from_cline
    97 # In:   $what       = commands | args
    98 # Out:  return      ссылка на хэш, содержащий результаты разбора
    99 #                   команда => позиция
   101 # Разобрать командную строку $_[1] и возвратить хэш, содержащий 
   102 # номер первого появление команды в строке:
   103 #   команда => первая позиция
   104 sub extract_from_cline
   105 {
   106     my $what = $_[0];
   107     my $cline = $_[1];
   108     my @lists = split /\;/, $cline;
   111     my @command_lines = ();
   112     for my $command_list (@lists) {
   113         push(@command_lines, split(/\|/, $command_list));
   114     }
   116     my %position_of_command;
   117     my %position_of_arg;
   118     my $i=0;
   119     for my $command_line (@command_lines) {
   120         $command_line =~ s@^\s*@@;
   121         $command_line =~ /\s*(\S+)\s*(.*)/;
   122         if ($1 && $1 eq "sudo" ) {
   123             $position_of_command{"$1"}=$i++;
   124             $command_line =~ s/\s*sudo\s+//;
   125         }
   126         if ($command_line !~ m@^\s*\S*/etc/@) {
   127             $command_line =~ s@^\s*\S+/@@;
   128         }
   130         $command_line =~ /\s*(\S+)\s*(.*)/;
   131         my $command = $1;
   132         my $args = $2;
   133         if ($command && !defined $position_of_command{"$command"}) {
   134                 $position_of_command{"$command"}=$i++;
   135         };  
   136         if ($args) {
   137             my @args = split (/\s+/, $args);
   138             for my $a (@args) {
   139                 $position_of_arg{"$a"}=$i++
   140                     if !defined $position_of_arg{"$a"};
   141             };  
   142         }
   143     }
   145     if ($what eq "commands") {
   146         return \%position_of_command;
   147     } else {
   148         return \%position_of_arg;
   149     }
   151 }
   156 #
   157 # Подпрограммы для работы с mywi
   158 #
   160 sub open_mywi_socket
   161 {
   162     $Mywi_Socket = IO::Socket::INET->new(
   163                 PeerAddr => $Config{mywi_server},
   164                 PeerPort => $Config{mywi_port},
   165                 Proto    => "tcp",
   166                 Type     => SOCK_STREAM);
   167 }
   169 sub close_mywi_socket
   170 {
   171     close ($Mywi_Socket) if $Mywi_Socket ;
   172 }
   175 sub mywi_client
   176 {
   177     return "";
   178     my $query = $_[0];
   179     my $mywi;
   181     open_mywi_socket;
   182     if ($Mywi_Socket) {
   183         local $| = 1;
   184         local $/ = "";
   185         print $Mywi_Socket $query."\n";
   186         $mywi = <$Mywi_Socket>;
   187         $mywi = "" if $mywi =~ /nothing app/;
   188     }
   189     close_mywi_socket;
   190     return $mywi;
   191 }
   193 sub make_comment
   194 {
   195     my $cline = $_[0];
   196     #my $files = $_[1];
   198     my @comments;
   199     my @commands = keys %{extract_from_cline("commands", $cline)};
   200     my @args = keys %{extract_from_cline("args", $cline)};
   201     return if (!@commands && !@args);
   202     #return "commands=".join(" ",@commands)."; files=".join(" ",@files);
   204     # Commands
   205     for my $command (@commands) {
   206         $command =~ s/'//g;
   207         $frequency_of_command{$command}++;
   208         if (!$Commands_Description{$command}) {
   209             $mywi_cache_for{$command} ||= mywi_client ($command) || "";
   210             my $mywi = join ("\n", grep(/\([18]|sh|script\)/, split(/\n/, $mywi_cache_for{$command})));
   211             $mywi =~ s/\s+/ /;
   212             if ($mywi !~ /^\s*$/) {
   213                 $Commands_Description{$command} = $mywi;
   214             }
   215             else {
   216                 next;
   217             }
   218         }
   220         push @comments, $Commands_Description{$command};
   221     }
   222     return join("
\n", @comments);
   224     # Files
   225     for my $arg (@args) {
   226         $arg =~ s/'//g;
   227         if (!$Args_Description{$arg}) {
   228             my $mywi;
   229             $mywi = mywi_client ($arg);
   230             $mywi = join ("\n", grep(/\([5]\)/, split(/\n/, $mywi)));
   231             $mywi =~ s/\s+/ /;
   232             if ($mywi !~ /^\s*$/) {
   233                 $Args_Description{$arg} = $mywi;
   234             }
   235             else {
   236                 next;
   237             }
   238         }
   240         push @comments, $Args_Description{$arg};
   241     }
   243 }
   245 =cut
   246 Процедура load_command_lines_from_xml выполняет загрузку разобранного lab-скрипта
   247 из XML-документа в переменную @Command_Lines
   249 # In:       $datafile           имя файла
   250 # Out:      @CommandLines       загруженные командные строки
   252 Предупреждение!
   253 Процедура не в состоянии обрабатывать XML-документ любой структуры.
   254 В действительности файл cache из которого загружаются данные 
   255 просто напоминает XML с виду.
   256 =cut
   257 sub load_command_lines_from_xml
   258 {
   259     my $datafile = $_[0];
   261     open (CLASS, $datafile)
   262         or die "Can't open file with xml lablog ",$datafile,"\n";
   263     local $/;
   264     binmode CLASS, ":utf8";
   265     $data = <CLASS>;
   266     close(CLASS);
   268     for $command ($data =~ m@<command>(.*?)</command>@sg) {
   269         my %cl;
   270         while ($command =~ m@<([^>]*?)>(.*?)</\1>@sg) {
   271             $cl{$1} = $2;
   272         }
   273         push @Command_Lines, \%cl;
   274     }
   275 }
   277 sub load_sessions_from_xml
   278 {
   279     my $datafile = $_[0];
   281     open (CLASS,  $datafile)
   282         or die "Can't open file with xml lablog ",$datafile,"\n";
   283     local $/;
   284     binmode CLASS, ":utf8";
   285     my $data = <CLASS>;
   286     close(CLASS);
   288     my $i=0;
   289     for my $session ($data =~ m@<session>(.*?)</session>@msg) {
   290         my %session_hash;
   291         while ($session =~ m@<([^>]*?)>(.*?)</\1>@sg) {
   292             $session_hash{$1} = $2;
   293         }
   294         $Sessions{$session_hash{local_session_id}} = \%session_hash;
   295     }
   296 }
   299 # sort_command_lines
   300 # In:   @Command_Lines
   301 # Out:  @Command_Lies_Index
   303 sub sort_command_lines
   304 {
   306     my @index;
   307     for (my $i=0;$i<=$#Command_Lines;$i++) {
   308         $index[$i]=$i;
   309     }
   311     @Command_Lines_Index = sort {
   312         $Command_Lines[$index[$a]]->{"time"} <=> $Command_Lines[$index[$b]]->{"time"}
   313     } @index;
   315 }
   317 ##################
   318 # process_command_lines
   319 #
   320 # Обрабатываются командные строки @Command_Lines
   321 # Для каждой строки определяется:
   322 #   class   класс    
   323 #   note    комментарий 
   324 #
   325 # In:        @Command_Lines_Index
   326 # In-Out:    @Command_Lines
   328 sub process_command_lines
   329 {
   331 COMMAND_LINE_PROCESSING:
   332     for my $i (@Command_Lines_Index) {
   333         my $cl = \$Command_Lines[$i];
   335         next if !$cl;
   337         for my $filter_key (keys %filter) {
   338             next COMMAND_LINE_PROCESSING
   339                 if defined($$cl->{local_session_id})
   340                 && defined($Sessions{$$cl->{local_session_id}}->{$filter_key})
   341                 && $Sessions{$$cl->{local_session_id}}->{$filter_key} ne $filter{$filter_key};
   342         }
   344         $$cl->{id} = $$cl->{"time"};
   346         $$cl->{err} ||=0;
   348         # Класс команды
   350         $$cl->{"class"} =   $$cl->{"err"} eq 130 ?  "interrupted"
   351                         :   $$cl->{"err"} eq 127 ?  "mistyped"
   352                         :   $$cl->{"err"}        ?  "wrong"
   353                         :                           "normal";
   355         if ($$cl->{"cline"} && 
   356             $$cl->{"cline"} =~ /[^|`]\s*sudo/
   357             || $$cl->{"uid"} eq 0) {
   358             $$cl->{"class"}.="_root";
   359         }
   361         my $hint;
   362         $hint = make_comment($$cl->{"cline"});
   363         if ($hint) {
   364             $$cl->{hint} = $hint;
   365         }
   366 #        $$cl->{hint}="";
   368 # Выводим <head_lines> верхних строк
   369 # и <tail_lines> нижних строк,
   370 # если эти параметры существуют
   371         my $output="";
   373         if ($$cl->{"last_command"} eq "cat" && !$$cl->{"err"} && !($$cl->{"cline"} =~ /</)) {
   374             my $filename = $$cl->{"cline"};
   375             $filename =~ s/.*\s+(\S+)\s*$/$1/;
   376             $Files{$filename}->{"content"} = $$cl->{"output"};
   377            $Files{$filename}->{"source_command_id"} = $$cl->{"id"}
   378         }
   379         my @lines = split '\n', $$cl->{"output"};
   380         if ((
   381              $Config{"head_lines"} 
   382              || $Config{"tail_lines"}
   383              )
   384              && $#lines >  $Config{"head_lines"} + $Config{"tail_lines"} ) {
   385 #
   386             for (my $i=0; $i<= $#lines && $i < $Config{"head_lines"}; $i++) {
   387                 $output .= $lines[$i]."\n";
   388             }
   389             $output .= $Config{"skip_text"}."\n";
   391             my $start_line=$#lines-$Config{"tail_lines"}+1;
   392             for (my $i=$start_line; $i<= $#lines; $i++) {
   393                 $output .= $lines[$i]."\n";
   394             }
   395         } 
   396         else {
   397            $output = $$cl->{"output"};
   398         }
   399         $$cl->{short_output} = $output;
   401 #Обработка пометок
   402 #  Если несколько пометок (notes) идут подряд, 
   403 #  они все объединяются
   405         if ($$cl->{cline} =~ /l3shot/) {
   406                 if ($$cl->{output} =~ m@Screenshot is written to.*/(.*)\.xwd@) {
   407                     $$cl->{screenshot}="$1";
   408                 }
   409         }
   411         if ($$cl->{cline}=~ m@cat[^#]*#([\^=v])\s*(.*)@) {
   413             my $note_operator = $1;
   414             my $note_title = $2;
   416             if ($note_operator eq "=") {
   417                 $$cl->{"class"} = "note";
   418                 $$cl->{"note"} = $$cl->{"output"};
   419                 $$cl->{"note_title"} = $2;
   420             }
   421             else {
   422                 my $j = $i;
   423                 if ($note_operator eq "^") {
   424                     $j--;
   425                     $j-- while ($j >=0  && (!$Command_Lines[$j] || $Command_Lines[$j]->{tty} ne $$cl->{tty}));
   426                 }
   427                 elsif ($note_operator eq "v") {
   428                     $j++;
   429                     $j++ while ($j <= @Command_Lines  && (!$Command_Lines[$j] || $Command_Lines[$j]->{tty} ne $$cl->{tty}));
   430                 }
   431                 $Command_Lines[$j]->{note_title}=$note_title;
   432                 $Command_Lines[$j]->{note}.=$$cl->{output};
   433                 $$cl=0;
   434             }
   435         }
   436         elsif ($$cl->{cline}=~ /#([\^=v])(.*)/) {
   438             my $note_operator = $1;
   439             my $note_text = $2;
   441             if ($note_operator eq "=") {
   442                 $$cl->{"class"} = "note";
   443                 $$cl->{"note"} = $note_text;
   444             }
   445             else {
   446                 my $j=$i;
   447                 if ($note_operator eq "^") {
   448                     $j--;
   449                     $j-- while ($j >=0  && (!$Command_Lines[$j] || $Command_Lines[$j]->{tty} ne $$cl->{tty}));
   450                 }
   451                 elsif ($note_operator eq "v") {
   452                     $j++;
   453                     $j++ while ($j <= @Command_Lines  && $Command_Lines[$j]->{tty} ne $$cl->{tty} || !$Command_Lines[$j]);
   454                 }
   455                 $Command_Lines[$j]->{note}.="$note_text\n";
   456                 $$cl=0;
   457             }
   458         }
   459         if ($$cl->{"class"} eq "note") {
   460                 my $note_html = $$cl->{note};
   461                 $note_html = join ("\n", map ("<p>$_</p>", split (/-\n/, $note_html)));
   462                 $note_html =~ s@(http:[a-zA-Z.0-9/?\_%-]*)@<a href='$1'>$1</a>@g;
   463                 $note_html =~ s@(www\.[a-zA-Z.0-9/?\_%-]*)@<a href='$1'>$1</a>@g;
   464                 $$cl->{"note_html"} = $note_html;
   465         }
   466     }   
   468 }
   471 =cut
   472 Процедура print_command_lines выводит HTML-представление
   473 разобранного lab-скрипта. 
   475 Разобранный lab-скрипт должен находиться в массиве @Command_Lines
   476 =cut
   478 sub print_command_lines_html
   479 {
   481     my @toc;                # Оглавление
   482     my $note_number=0;
   484     my $result = q();
   485     my $this_day_resut = q();
   487     my $cl;
   488     my $last_tty="";
   489     my $last_session="";
   490     my $last_day=q();
   491     my $last_wday=q();
   492     my $in_range=0;
   494     my $current_command=0;
   496     my @known_commands;
   500     $Stat{LastCommand}   ||= 0;
   501     $Stat{TotalCommands} ||= 0;
   502     $Stat{ErrorCommands} ||= 0;
   503     $Stat{MistypedCommands} ||= 0;
   505     my %new_entries_of = (
   506         "1 1"     =>   "программы пользователя",
   507         "2 8"     =>   "программы администратора",
   508         "3 sh"    =>   "команды интерпретатора",
   509         "4 script"=>   "скрипты",
   510     );
   512 COMMAND_LINE:
   513     for my $k (@Command_Lines_Index) {
   515         my $cl=$Command_Lines[$Command_Lines_Index[$current_command++]];
   516         next unless $cl;
   518 # Пропускаем команды, с одинаковым временем
   519 # Это не совсем правильно.
   520 # Возможно, что это команды, набираемые с помощью <completion>
   521 # или запомненные с помощью <ctrl-c>
   523         next if $Stat{LastCommand} == $cl->{time};
   525 # Пропускаем строки, которые противоречат фильтру
   526 # Если у нас недостаточно информации о том, подходит строка под  фильтр или нет, 
   527 # мы её выводим
   529         for my $filter_key (keys %filter) {
   530             next COMMAND_LINE 
   531                 if defined($cl->{local_session_id})
   532                 && defined($Sessions{$cl->{local_session_id}}->{$filter_key})
   533                 && $Sessions{$cl->{local_session_id}}->{$filter_key} ne $filter{$filter_key};
   534         }
   536 # Набираем статистику
   537 # Хэш %Stat
   539         $Stat{FirstCommand} = $cl->{time} unless $Stat{FirstCommand};
   540         if ($cl->{time} - $Stat{LastCommand} < $Config{stat_inactivity_interval}) {
   541             $Stat{TotalTime} += $cl->{time} - $Stat{LastCommand}
   542         }
   543         my $seconds_since_last_command = $cl->{time} - $Stat{LastCommand};
   545         if ($Stat{LastCommand} > $cl->{time}) {
   546                $result .= "Время идёт вспять<br/>";
   547         };
   548         $Stat{LastCommand} = $cl->{time};
   549         $Stat{TotalCommands}++;
   551 # Пропускаем строки, выходящие за границу "signature",
   552 # при условии, что границы указаны
   553 # Пропускаем неправильные/прерванные/другие команды
   554         if ($Config{"from"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"from"}/) {
   555             $in_range=1;
   556             next;
   557         }
   558         if ($Config{"to"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"to"}/) {
   559             $in_range=0;
   560             next;
   561         }
   562         next    if ($Config{"from"} && $Config{"to"}   && !$in_range) 
   563                 || ($Config{"skip_empty"} =~ /^y/i     && $cl->{"cline"} =~ /^\s*$/ )
   564                 || ($Config{"skip_wrong"} =~ /^y/i     && $cl->{"err"} != 0)
   565                 || ($Config{"skip_interrupted"} =~ /^y/i && $cl->{"err"} == 130);
   570 #
   571 ##
   572 ## Начинается собственно вывод
   573 ##
   574 #
   576 ### Сначала обрабатываем границы разделов
   577 ### Если тип команды "note", это граница
   579         if ($cl->{class} eq "note") {
   580             $this_day_result .= "<tr><td colspan='6'>"
   581                              .  "<h4 id='note$note_number'>".$cl->{note_title}."</h4>" if $cl->{note_title}
   582                              .  "".$cl->{note_html}."<p/><p/></td></tr>";
   584             if ($cl->{note_title}) {
   585                 push @{$toc[@toc]},"<a href='#note$note_number'>".$cl->{note_title}."</a>";
   586                 $note_number++;
   587             }
   588             next;
   589         }
   591         my ($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = localtime($cl->{time});
   593         # Добавляем спереди 0 для удобочитаемости
   594         $min  = "0".$min  if $min  =~ /^.$/;
   595         $hour = "0".$hour if $hour =~ /^.$/;
   596         $sec  = "0".$sec  if $sec  =~ /^.$/;
   598         $class=$cl->{"class"};
   599         $Stat{ErrorCommands}++          if $class =~ /wrong/;
   600         $Stat{MistypedCommands}++       if $class =~ /mistype/;
   602 # DAY CHANGE
   603         if ( $last_day ne $day) {
   604             if ($last_day) {
   606 # Вычисляем разность множеств.
   607 # Что-то вроде этого, если бы так можно было писать:
   608 #   @new_commands = keys %frequency_of_command - @known_commands;
   611                 $result .= "<h3 id='day$last_day'>".$Day_Name[$last_wday]."</h3>";
   612                 for my $entry_class (sort keys %new_entries_of) {
   613                     my $table_caption = "Таблица ".$table_number++.".".$Day_Name[$last_wday]
   614                                         .". Новые ".$new_entries_of{$entry_class};
   615                     my $new_commands_section = make_new_entries_table(
   616                                                 $table_caption, 
   617                                                 $entry_class=~/[0-9]+\s+(.*)/, 
   618                                                 \@known_commands);
   619                 }
   620                 @known_commands = keys %frequency_of_command;
   621                 $result .= $this_day_result;
   622             }
   624             push @toc, "<a href='#day$day'>".$Day_Name[$wday]."</a>\n";
   625             $last_day=$day;
   626             $last_wday=$wday;
   627             $this_day_result = q();
   628         }
   629         else {
   630             $this_day_result .= minutes_passed($seconds_since_last_command);
   631         }
   633         $this_day_result .= "<div class='command' id='command:".$cl->{"id"}."' >\n";
   635 # CONSOLE CHANGE
   636         if ($cl->{"tty"} && $last_tty ne $cl->{"tty"} && 0) {
   637             my $tty = $cl->{"tty"};
   638             $this_day_result .= "<div class='ttychange'>"
   639                                 . $tty
   640                                 ."</div>";
   641             $last_tty=$cl->{"tty"};
   642         }
   644 # Session change
   645         if ( $last_session ne $cl->{"local_session_id"}) {
   646             my $tty;
   647             if (defined $Sessions{$cl->{"local_session_id"}}->{"tty"}) {
   648                 $this_day_result .= "<div class='ttychange'><a href='?local_session_id=".$cl->{"local_session_id"}."'>"
   649                                 . $Sessions{$cl->{"local_session_id"}}->{"tty"}
   650                                 ."</a></div>";
   651             }
   652             $last_session=$cl->{"local_session_id"};
   653         }
   655 # TIME
   656         if ($Config{"show_time"} =~ /^y/i) {
   657             $this_day_result .= "<div class='time'>$hour:$min:$sec</div>" 
   658         }
   660 # COMMAND
   661         my $cline;
   662         $prompt_hint = join ("
", map("$_=$cl->{$_}", grep (!/^(output|diff)$/, sort(keys(%{$cl})))));
   663         $cline = "<span title='$prompt_hint'>".$cl->{"prompt"}."</span>".$cl->{"cline"};
   664         $cline =~ s/\n//;
   666         if ($cl->{"hint"}) {
   667             $cline = "<span title='$cl->{hint}' class='with_hint'>$cline</span>" ;
   668         } 
   669         else {
   670             $cline = "<span class='without_hint'>$cline</span>";
   671         }
   673         $this_day_result .= "<table cellpadding='0' cellspacing='0'><tr><td>\n<div class='cblock_$cl->{class}'>\n";
   674         $this_day_result .= "<div class='cline'>\n" . $cline ;      #cline
   675         $this_day_result .= "<span title='Код завершения ".$cl->{"err"}."'>\n"
   676                          .  "<img src='".$Config{frontend_ico_path}."/error.png'/>\n"
   677                          .  "</span>\n" if $cl->{"err"};
   678         $this_day_result .= "</div>\n";                             #cline
   680 # OUTPUT
   681         my $last_command = $cl->{"last_command"};
   682         if (!( 
   683         $Config{"suppress_editors"} =~ /^y/i && grep ($_ eq $last_command, @{$Config{"editors"}}) ||
   684         $Config{"suppress_pagers"}  =~ /^y/i && grep ($_ eq $last_command, @{$Config{"pagers"}}) ||
   685         $Config{"suppress_terminal"}=~ /^y/i && grep ($_ eq $last_command, @{$Config{"terminal"}})
   686             )) {
   687             $this_day_result .= "<pre class='output'>\n" . $cl->{short_output} . "</pre>\n";
   688         }
   690 # DIFF
   691         $this_day_result .= "<pre class='diff'>".$cl->{"diff"}."</pre>"
   692             if ( $Config{"show_diffs"} =~ /^y/i && $cl->{"diff"});
   693 # SHOT
   694         $this_day_result .= "<img src='"
   695                 .$Config{l3shot_path}
   696                 .$cl->{"screenshot"}
   697                 .$Config{l3shot_suffix}
   698                 ."' alt ='screenshot id ".$cl->{"screenshot"}
   699                 ."'/>"
   700             if ( $Config{"show_screenshots"} =~ /^y/i && $cl->{"screenshot"});
   702 #NOTES
   703         if ( $Config{"show_notes"} =~ /^y/i && $cl->{"note"}) {
   704             my $note=$cl->{"note"};
   705             $note =~ s/\n/<br\/>\n/msg;
   706             if (not $note =~ s@(http:[a-zA-Z.0-9/_?%-]*)@<a href='$1'>$1</a>@g) {
   707               $note =~ s@(www\.[a-zA-Z.0-9/_?%-]*)@<a href='$1'>$1</a>@g;
   708             };
   709             $this_day_result .= "<div class='note'>";
   710             $this_day_result .= "<div class='note_title'>".$cl->{note_title}."</div>" if $cl->{note_title};
   711             $this_day_result .= "<div class='note_text'>".$note."</div>";
   712             $this_day_result .= "</div>\n";
   713         }
   715         # Вывод очередной команды окончен
   716         $this_day_result .= "</div>\n";                     # cblock
   717         $this_day_result .= "</td></tr></table>\n"
   718                          .  "</div>\n";                     # command
   719     }
   720     last: {
   721         $result .= "<h3 id='day$last_day'>".$Day_Name[$last_wday]."</h3>";
   723         for my $entry_class (keys %new_entries_of) {
   724             my $table_caption = "Таблица ".$table_number++.".".$Day_Name[$last_wday]
   725                               . ". Новые ".$new_entries_of{$entry_class};
   726             my $new_commands_section = make_new_entries_table(
   727                                         $table_caption, 
   728                                         $entry_class=~/[0-9]+\s+(.*)/, 
   729                                         \@known_commands);
   730         }
   731         @known_commands = keys %frequency_of_command;
   732         $result .= $this_day_result;
   733    }
   735     return ($result, collapse_list (\@toc));
   737 }
   739 #############
   740 # make_new_entries_table
   741 #
   742 # Напечатать таблицу неизвестных команд
   743 #
   744 # In:       $_[0]       table_caption
   745 #           $_[1]       entries_class
   746 #           @_[2..]     known_commands
   747 # Out:
   749 sub make_new_entries_table
   750 {
   751     my $table_caption;
   752     my $entries_class = shift;
   753     my @known_commands = @{$_[0]};
   754     my $result = "";
   756     my %count;
   757     my @new_commands = ();
   758     for my $c (keys %frequency_of_command, @known_commands) {
   759         $count{$c}++
   760     }
   761     for my $c (keys %frequency_of_command) {
   762         push @new_commands, $c if $count{$c} != 2;
   763     }
   765     my $new_commands_section;
   766     if (@new_commands){
   767         my $hint;
   768         for my $c (reverse sort { $frequency_of_command{$a} <=> $frequency_of_command{$b} } @new_commands) {
   769                 $hint = make_comment($c);
   770                 next unless $hint;
   771                 my ($command, $hint) = $hint =~ m/(.*?) \s*- \s*(.*)/;
   772                 next unless $command =~ s/\($entries_class\)//i;
   773                 $new_commands_section .= "<tr><td valign='top'>$command</td><td>$hint</td></tr>";
   774         }
   775     }
   776     if ($new_commands_section) {
   777         $result .= "<table class='new_commands_table' width='700' cellspacing='0' cellpadding='0'>"
   778                 .  "<tr class='new_commands_caption'>"
   779                 .  "<td colspan='2' align='right'>$table_caption</td>"
   780                 .  "</tr>"
   781                 .  "<tr class='new_commands_header'>"
   782                 .  "<td width=100>Команда</td><td width=600>Описание</td>"
   783                 .  "</tr>"
   784                 .  $new_commands_section 
   785                 .  "</table>"
   786     }
   787     return $result;
   788 }
   790 #############
   791 # minutes_passed
   792 #
   793 #
   794 #
   795 # In:       $_[0]       seconds_since_last_command
   796 # Out:                  "minutes passed" text
   798 sub minutes_passed
   799 {
   800         my $seconds_since_last_command = shift;
   801         my $result = "";
   802         if ($seconds_since_last_command > 7200) {
   803             my $hours_passed =  int($seconds_since_last_command/3600);
   804             my $passed_word  = $hours_passed % 10 == 1 ? "прошла"
   805                                                          : "прошло";
   806             my $hours_word   = $hours_passed % 10 == 1 ?   "часа":
   807                                                            "часов";
   808             $result .= "<div class='much_time_passed'>"
   809                     .  $passed_word." >".$hours_passed." ".$hours_word
   810                     .  "</div>\n";
   811         }
   812         elsif ($seconds_since_last_command > 600) {
   813             my $minutes_passed =  int($seconds_since_last_command/60);
   816             my $passed_word  = $minutes_passed % 100 > 10 
   817                             && $minutes_passed % 100 < 20 ? "прошло"
   818                              : $minutes_passed % 10 == 1  ? "прошла"
   819                                                           : "прошло";
   821             my $minutes_word = $minutes_passed % 100 > 10 
   822                             && $minutes_passed % 100 < 20 ? "минут" :
   823                                $minutes_passed % 10 == 1 ? "минута":
   824                                $minutes_passed % 10 == 0 ? "минут" :
   825                                $minutes_passed % 10  > 4 ? "минут" :
   826                                                            "минуты";
   828             if ($seconds_since_last_command < 1800) {
   829                 $result .= "<div class='time_passed'>"
   830                         .  $passed_word." ".$minutes_passed." ".$minutes_word
   831                         .  "</div>\n";
   832             }
   833             else {
   834                 $result .= "<div class='much_time_passed'>"
   835                         .  $passed_word." ".$minutes_passed." ".$minutes_word
   836                         .  "</div>\n";
   837             }
   838         }
   839         return $result;
   840 }
   842 #############
   843 # print_all_txt
   844 #
   845 # Вывести журнал в текстовом формате
   846 #
   847 # In:       $_[0]       output_filename
   848 # Out:
   850 sub print_command_lines_txt
   851 {
   853     my $output_filename=$_[0];
   854     my $note_number=0;
   856     my $result = q();
   857     my $this_day_resut = q();
   859     my $cl;
   860     my $last_tty="";
   861     my $last_session="";
   862     my $last_day=q();
   863     my $last_wday=q();
   864     my $in_range=0;
   866     my $current_command=0;
   868     my $cursor_position = 0;
   871     if ($Config{filter}) {
   872         # Инициализация фильтра
   873         for (split /&/,$Config{filter}) {
   874             my ($var, $val) = split /=/;
   875             $filter{$var} = $val || "";
   876         }
   877     }
   880 COMMAND_LINE:
   881     for my $k (@Command_Lines_Index) {
   883         my $cl=$Command_Lines[$Command_Lines_Index[$current_command++]];
   884         next unless $cl;
   887 # Пропускаем строки, которые противоречат фильтру
   888 # Если у нас недостаточно информации о том, подходит строка под  фильтр или нет, 
   889 # мы её выводим
   891         for my $filter_key (keys %filter) {
   892             next COMMAND_LINE 
   893                 if defined($cl->{local_session_id})
   894                 && defined($Sessions{$cl->{local_session_id}}->{$filter_key})
   895                 && $Sessions{$cl->{local_session_id}}->{$filter_key} ne $filter{$filter_key};
   896         }
   898 # Пропускаем строки, выходящие за границу "signature",
   899 # при условии, что границы указаны
   900 # Пропускаем неправильные/прерванные/другие команды
   901         if ($Config{"from"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"from"}/) {
   902             $in_range=1;
   903             next;
   904         }
   905         if ($Config{"to"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"to"}/) {
   906             $in_range=0;
   907             next;
   908         }
   909         next    if ($Config{"from"} && $Config{"to"}   && !$in_range) 
   910                 || ($Config{"skip_empty"} =~ /^y/i     && $cl->{"cline"} =~ /^\s*$/ )
   911                 || ($Config{"skip_wrong"} =~ /^y/i     && $cl->{"err"} != 0)
   912                 || ($Config{"skip_interrupted"} =~ /^y/i && $cl->{"err"} == 130);
   915 #
   916 ##
   917 ## Начинается собственно вывод
   918 ##
   919 #
   921 ### Сначала обрабатываем границы разделов
   922 ### Если тип команды "note", это граница
   924         if ($cl->{class} eq "note") {
   925             $this_day_result .= " === ".$cl->{note_title}." === \n" if $cl->{note_title};
   926             $this_day_result .= $cl->{note}."\n";
   927             next;
   928         }
   930         my ($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = localtime($cl->{time});
   932         # Добавляем спереди 0 для удобочитаемости
   933         $min  = "0".$min  if $min  =~ /^.$/;
   934         $hour = "0".$hour if $hour =~ /^.$/;
   935         $sec  = "0".$sec  if $sec  =~ /^.$/;
   937         $class=$cl->{"class"};
   939 # DAY CHANGE
   940         if ( $last_day ne $day) {
   941             if ($last_day) {
   942                 $result .= "== ".$Day_Name[$last_wday]." == \n";
   943                 $result .= $this_day_result;
   944             }
   945             $last_day   = $day;
   946             $last_wday  = $wday;
   947             $this_day_result = q();
   948         }
   950 # CONSOLE CHANGE
   951         if ($cl->{"tty"} && $last_tty ne $cl->{"tty"} && 0) {
   952             my $tty = $cl->{"tty"};
   953             $this_day_result .= "         #l3: ------- другая консоль ----\n";
   954             $last_tty=$cl->{"tty"};
   955         }
   957 # Session change
   958         if ( $last_session ne $cl->{"local_session_id"}) {
   959             $this_day_result .= "# ------------------------------------------------------------"
   960                              .  "  l3: local_session_id=".$cl->{"local_session_id"}
   961                              .  " ---------------------------------- \n";
   962             $last_session=$cl->{"local_session_id"};
   963         }
   965 # TIME
   966         my @nl_counter = split (/\n/, $result);
   967         $cursor_position=length($result) - @nl_counter;
   969         if ($Config{"show_time"} =~ /^y/i) {
   970             $this_day_result .= "$hour:$min:$sec" 
   971         }
   973 # COMMAND
   974         $this_day_result .= " ".$cl->{"prompt"}.$cl->{"cline"}."\n";
   975         if ($cl->{"err"}) {
   976             $this_day_result .= "         #l3: err=".$cl->{'err'}."\n";
   977         }
   979 # OUTPUT
   980         my $last_command = $cl->{"last_command"};
   981         if (!( 
   982         $Config{"suppress_editors"} =~ /^y/i && grep ($_ eq $last_command, @{$Config{"editors"}}) ||
   983         $Config{"suppress_pagers"}  =~ /^y/i && grep ($_ eq $last_command, @{$Config{"pagers"}}) ||
   984         $Config{"suppress_terminal"}=~ /^y/i && grep ($_ eq $last_command, @{$Config{"terminal"}})
   985             )) {
   986             my $output = $cl->{short_output};
   987             if ($output) {
   988                  $output =~ s/^/         |/mg;
   989             }
   990             $this_day_result .= $output;
   991         }
   993 # DIFF
   994         if ( $Config{"show_diffs"} =~ /^y/i && $cl->{"diff"}) {
   995             my $diff = $cl->{"diff"};
   996             $diff =~ s/^/         |/mg;
   997             $this_day_result .= $diff;
   998         };
   999 # SHOT
  1000         if ($Config{"show_screenshots"} =~ /^y/i && $cl->{"screenshot"}) {
  1001             $this_day_result .= "         #l3: screenshot=".$cl->{'screenshot'}."\n";
  1002         }
  1004 #NOTES
  1005         if ( $Config{"show_notes"} =~ /^y/i && $cl->{"note"}) {
  1006             my $note=$cl->{"note"};
  1007             $note =~ s/\n/\n#^/msg;
  1008             $this_day_result .= "#^ == ".$cl->{note_title}." ==\n" if $cl->{note_title};
  1009             $this_day_result .= "#^ ".$note."\n";
  1010         }
  1012     }
  1013     last: {
  1014         $result .= "== ".$Day_Name[$last_wday]." == \n";
  1015         $result .= $this_day_result;
  1016    }
  1018    return $result;
  1022 }
  1024 #############
  1025 # print_edit_all_html
  1026 #
  1027 # Вывести страницу с текстовым представлением журнала для редактирования
  1028 #
  1029 # In:       $_[0]       output_filename
  1030 # Out:
  1032 sub print_edit_all_html
  1033 {
  1034     my $output_filename= shift;
  1035     my $result;
  1036     my $cursor_position = 0;
  1038     $result = print_command_lines_txt;
  1039     my $title = ">Журнал лабораторных работ. Правка";
  1041     $result = 
  1042                "<html>"
  1043                 ."<head>"
  1044                 ."<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />"
  1045                 ."<link rel='stylesheet' href='$Config{frontend_css}' type='text/css'/>"
  1046                 ."<title>$title</title>"
  1047                 ."</head>"
  1048               ."<script>"
  1049               .$SetCursorPosition_JS
  1050               ."</script>"
  1051               ."<body onLoad='setCursorPosition(document.all.mytextarea, $cursor_position, $cursor_position+10)'>"
  1052               ."<h1>Журнал лабораторных работ. Правка</h1>"
  1053               ."<form>"
  1054               ."<textarea rows='30' cols='100' wrap='off' id='mytextarea'>$result</textarea>"
  1055               ."<br/><input type='submit' value='Сохранить' label='label'/>"
  1056               ."</form>"
  1057               ."<p>Внимательно правим, потом сохраняем</p>"
  1058               ."<p>Строки, начинающиеся символами #l3: можно трогать, только если точно знаешь, что делаешь</p>"
  1059               ."</body>"
  1060               ."</html>";
  1062     if ($output_filename eq "-") {
  1063         print $result;
  1064     }
  1065     else {
  1066         open(OUT, ">", $output_filename)
  1067             or die "Can't open $output_filename for writing\n";
  1068         binmode ":utf8";
  1069         print OUT "$result";
  1070         close(OUT);
  1071     }
  1072 }
  1074 #############
  1075 # print_all_txt
  1076 #
  1077 # Вывести страницу с текстовым представлением журнала для редактирования
  1078 #
  1079 # In:       $_[0]       output_filename
  1080 # Out:
  1082 sub print_all_txt
  1083 {
  1084     my $result;
  1086     $result = print_command_lines_txt;
  1088     $result =~ s/>/>/g;
  1089     $result =~ s/</</g;
  1090     $result =~ s/&/&/g;
  1092     if ($output_filename eq "-") {
  1093         print $result;
  1094     }
  1095     else {
  1096         open(OUT, ">:utf8", $output_filename)
  1097             or die "Can't open $output_filename for writing\n";
  1098         print OUT "$result";
  1099         close(OUT);
  1100     }
  1101 }
  1104 #############
  1105 # print_all_html
  1106 #
  1107 #
  1108 #
  1109 # In:       $_[0]       output_filename
  1110 # Out:
  1113 sub print_all_html
  1114 {
  1115     my $output_filename=$_[0];
  1117     my $result;
  1118     my ($command_lines,$toc)  = print_command_lines_html;
  1119     my $files_section         = print_files_html;
  1121     $result = print_header_html($toc);
  1124 #    $result.= join " <br/>", keys %Sessions;
  1125 #    for my $sess (keys %Sessions) {
  1126 #            $result .= join " ", keys (%{$Sessions{$sess}});
  1127 #            $result .= "<br/>";
  1128 #    }
  1130     $result.= "<h2 id='log'>Журнал</h2>"       . $command_lines;
  1131     $result.= "<h2 id='files'>Файлы</h2>"      . $files_section if $files_section;
  1132     $result.= "<h2 id='stat'>Статистика</h2>"  . print_stat_html;
  1133     $result.= "<h2 id='help'>Справка</h2>"     . $Html_Help . "<br/>"; 
  1134     $result.= "<h2 id='about'>О программе</h2>". $Html_About. "<br/>"; 
  1135     $result.= print_footer_html;
  1137     if ($output_filename eq "-") {
  1138         binmode STDOUT, ":utf8";
  1139         print $result;
  1140     }
  1141     else {
  1142         open(OUT, ">:utf8", $output_filename)
  1143             or die "Can't open $output_filename for writing\n";
  1144         print OUT $result;
  1145         close(OUT);
  1146     }
  1147 }
  1149 #############
  1150 # print_header_html
  1151 #
  1152 #
  1153 #
  1154 # In:   $_[0]       Содержание
  1155 # Out:              Распечатанный заголовок
  1157 sub print_header_html
  1158 {
  1159     my $toc = $_[0];
  1160     my $course_name = $Config{"course-name"};
  1161     my $course_code = $Config{"course-code"};
  1162     my $course_date = $Config{"course-date"};
  1163     my $course_center = $Config{"course-center"};
  1164     my $course_trainer = $Config{"course-trainer"};
  1165     my $course_student = $Config{"course-student"};
  1167     my $title    = "Журнал лабораторных работ";
  1168     $title      .= " -- ".$course_student if $course_student;
  1169     if ($course_date) {
  1170         $title  .= " -- ".$course_date; 
  1171         $title  .= $course_code ? "/".$course_code 
  1172                                 : "";
  1173     }
  1174     else {
  1175         $title  .= " -- ".$course_code if $course_code;
  1176     }
  1178     # Управляющая форма
  1179     my $control_form .= "<div class='visibility_form' title='Выберите какие элементы должны быть показаны в журнале'>"
  1180                      .  "<span class='header'>Видимые элементы</span>"
  1181                      .  "<span class='window_controls'><a href='' onclick='' title='свернуть форму управления'>_</a> <a href='' onclick='' title='закрыть форму управления'>x</a></span>"
  1182                      .  "<div><form>\n";
  1183     for my $element (sort keys %Elements_Visibility)
  1184     {
  1185         my ($skip, @e) = split /\s+/, $element;
  1186         my $showhide = join "", map { "ShowHide('$_');" } @e ;
  1187         $control_form .= "<div><input type='checkbox' name='$e[0]' onclick=\"$showhide\" checked>".
  1188                 $Elements_Visibility{$element}.
  1189                 "</input></div>";
  1190     }
  1191     $control_form .= "</form>\n"
  1192                   .  "</div>\n";
  1195     # Управляющая форма отключена
  1196     # Она слишеком сильно мешает, нужно что-то переделать
  1197     $control_form = "";
  1199     my $result;
  1200     $result = <<HEADER;
  1201     <html>
  1202     <head>
  1203     <meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
  1204     <link rel='stylesheet' href='$Config{frontend_css}' type='text/css'/>
  1205     <title>$title</title>
  1206     </head>
  1207     <body>
  1208     <script>
  1209     $Html_JavaScript
  1210     </script>
  1212 <!-- vvv Tigra Hints vvv -->
  1213 <script language="JavaScript" src="/tigra/hints.js"></script>
  1214 <script language="JavaScript" src="/tigra/hints_cfg.js"></script>
  1215 <style>
  1216 /* a class for all Tigra Hints boxes, TD object */
  1217     .hintsClass
  1218         {text-align: center; font-family: Verdana, Arial, Helvetica; padding: 0px 0px 0px 0px;}
  1219 /* this class is used by Tigra Hints wrappers */
  1220     .row
  1221         {background: white;}
  1222 </style>
  1223 <!-- ^^^ Tigra Hints ^^^ -->
  1226     <div class='edit_link'>
  1227     [ <a href='?action=edit&$filter_url'>править</a> ]
  1228     </div>
  1229     <h1 onmouseover="myHint.show('1')" onmouseout="myHint.hide()" class='lined_header'>Журнал лабораторных работ</h1>
  1230 HEADER
  1231     if (    $course_student 
  1232             || $course_trainer 
  1233             || $course_name 
  1234             || $course_code 
  1235             || $course_date 
  1236             || $course_center) {
  1237             $result .= "<p>";
  1238             $result .= "Выполнил $course_student<br/>"  if $course_student;
  1239             $result .= "Проверил $course_trainer <br/>" if $course_trainer;
  1240             $result .= "Курс "                          if $course_name 
  1241                                                             || $course_code 
  1242                                                             || $course_date;
  1243             $result .= "$course_name "                  if $course_name;
  1244             $result .= "($course_code)"                 if $course_code;
  1245             $result .= ", $course_date<br/>"            if $course_date;
  1246             $result .= "Учебный центр $course_center <br/>" if $course_center;
  1247             $result .= "Фильтр ".join(" ", map("$filter{$_}=$_", keys %filter))."<br/>" if %filter;
  1248             $result .= "</p>";
  1249     }
  1251     $result .= <<HEADER;
  1252     <table width='100%'>
  1253     <tr>
  1254     <td width='*'>
  1256     <table border=0 id='toc' class='toc'>
  1257     <tr>
  1258     <td>
  1259     <div class='toc_title'>Содержание</div>
  1260     <ul>
  1261         <li><a href='#log'>Журнал</a></li>
  1262         <ul>$toc</ul>
  1263         <li><a href='#files'>Файлы</a></li>
  1264         <li><a href='#stat'>Статистика</a></li>
  1265         <li><a href='#help'>Справка</a></li>
  1266         <li><a href='#about'>О программе</a></li>
  1267     </ul>
  1268     </td>
  1269     </tr>
  1270     </table>
  1272     </td>
  1273     <td valign='top' width=200>$control_form</td>
  1274     </tr>
  1275     </table>
  1276 HEADER
  1278     return $result;
  1279 }
  1282 #############
  1283 # print_footer_html
  1284 #
  1285 #
  1286 #
  1287 #
  1288 #
  1290 sub print_footer_html
  1291 {
  1292     return "</body>\n</html>\n";
  1293 }
  1298 #############
  1299 # print_stat_html
  1300 #
  1301 #
  1302 #
  1303 # In:
  1304 # Out:
  1306 sub print_stat_html
  1307 {
  1308     %StatNames = (
  1309         FirstCommand        => "Время первой команды журнала",
  1310         LastCommand         => "Время последней команды журнала",
  1311         TotalCommands       => "Количество командных строк в журнале",
  1312         ErrorsPercentage    => "Процент команд с ненулевым кодом завершения, %",
  1313         MistypesPercentage  => "Процент синтаксически неверно набранных команд, %",
  1314         TotalTime           => "Суммарное время работы с терминалом <sup><font size='-2'>*</font></sup>, час",
  1315         CommandsPerTime     => "Количество командных строк в единицу времени, команда/мин",
  1316         CommandsFrequency   => "Частота использования команд",
  1317         RareCommands        => "Частота использования этих команд < 0.5%",
  1318     );
  1319     @StatOrder = (
  1320         FirstCommand,
  1321         LastCommand,
  1322         TotalCommands,
  1323         ErrorsPercentage,
  1324         MistypesPercentage,
  1325         TotalTime,
  1326         CommandsPerTime,
  1327         CommandsFrequency,
  1328         RareCommands,
  1329     );
  1331     # Подготовка статистики к выводу
  1332     # Некоторые значения пересчитываются!
  1333     # Дальше их лучше уже не использовать!!!
  1335     my %CommandsFrequency = %frequency_of_command;
  1337     $Stat{TotalTime} ||= 0;
  1338     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($Stat{FirstCommand} || 0);
  1339     $Stat{FirstCommand} = sprintf "%02i:%02i:%02i %04i-%2i-%2i", $hour, $min, $sec,  $year+1900, $mon+1, $mday;
  1340     ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($Stat{LastCommand} || 0);
  1341     $Stat{LastCommand} = sprintf "%02i:%02i:%02i %04i-%2i-%2i", $hour, $min, $sec,  $year+1900, $mon+1, $mday;
  1342     if ($Stat{TotalCommands}) {
  1343         $Stat{ErrorsPercentage} = sprintf "%5.2f", $Stat{ErrorCommands}*100/$Stat{TotalCommands};
  1344         $Stat{MistypesPercentage} = sprintf "%5.2f", $Stat{MistypedCommands}*100/$Stat{TotalCommands};
  1345     }
  1346     $Stat{CommandsPerTime} = sprintf "%5.2f", $Stat{TotalCommands}*60/$Stat{TotalTime}
  1347         if $Stat{TotalTime};
  1348     $Stat{TotalTime} = sprintf "%5.2f", $Stat{TotalTime}/60/60;
  1350     my $total_commands=0;
  1351     for $command (keys %CommandsFrequency){
  1352         $total_commands += $CommandsFrequency{$command};
  1353     }
  1354     if ($total_commands) {
  1355         for $command (reverse sort {$CommandsFrequency{$a} <=> $CommandsFrequency{$b}} keys %CommandsFrequency){
  1356             my $command_html;
  1357             my $percentage = sprintf "%5.2f",$CommandsFrequency{$command}*100/$total_commands;
  1358             if ($percentage < 0.5) {
  1359                 my $hint = make_comment($command);
  1360                 $command_html = "$command";
  1361                 $command_html = "<span title='$hint' class='with_hint'>$command_html</span>" if $hint;
  1362                 $command_html = "<span class='without_hint'>$command_html</span>" if not $hint;
  1363                 my $command_html = "<tt>$command_html</tt>";
  1364                 $Stat{RareCommands} .= $command_html."<sub><font size='-2'>".$CommandsFrequency{$command}."</font></sub> , ";
  1365             }
  1366             else {
  1367                 my $hint = make_comment($command);
  1368                 $command_html = "$command";
  1369                 $command_html = "<span title='$hint' class='with_hint'>$command_html</span>" if $hint;
  1370                 $command_html = "<span class='without_hint'>$command_html</span>" if not $hint;
  1371                 my $command_html = "<tt>$command_html</tt>";
  1372                 $percentage = sprintf "%5.2f",$percentage;
  1373                 $Stat{CommandsFrequency} .= "<tr><td>".$command_html."</td><td>".$CommandsFrequency{$command}."</td>".
  1374                     "<td>|".("="x int($CommandsFrequency{$command}*100/$total_commands))."| $percentage%</td></tr>";
  1375             }
  1376         }
  1377         $Stat{CommandsFrequency} = "<table>".$Stat{CommandsFrequency}."</table>";
  1378         $Stat{RareCommands} =~ s/, $// if $Stat{RareCommands};
  1379     }
  1381     my $result = q();
  1382     for my $stat (@StatOrder) {
  1383         next unless $Stat{"$stat"};
  1384         $result .= "<tr valign='top'><td width='300'>".$StatNames{"$stat"}."</td><td>".$Stat{"$stat"}."</td></tr>"
  1385     }
  1386     $result  = "<table>$result</table>"
  1387              . "<font size='-2'>____<br/>*) Интервалы неактивности длительностью "
  1388              .  ($Config{stat_inactivity_interval}/60)
  1389              . " минут и более не учитываются</font></br>";
  1391     return $result;
  1392 }
  1395 sub collapse_list($)
  1396 {
  1397     my $res = "";
  1398     for my $elem (@{$_[0]}) {
  1399         if (ref $elem eq "ARRAY") {
  1400             $res .= "<ul>".collapse_list($elem)."</ul>";
  1401         }
  1402         else
  1403         {
  1404             $res .= "<li>".$elem."</li>";
  1405         }
  1406     }
  1407     return $res;
  1408 }
  1411 sub print_files_html
  1412 {
  1413     my $result = qq(); 
  1414     my @toc;
  1415     for my $file (sort keys %Files) {
  1416           my $div_id = "file:$file";
  1417           $div_id =~ s@/@_@g;
  1418           push @toc, "<a href='#$div_id'>$file</a>";
  1419           $result .= "<div class='filename' id='$div_id'>".$file."</div>\n"
  1420                   .  "<div class='file_navigation'><a href='#command:".$Files{$file}->{source_command_id}."'>".">"."</a></div>"
  1421                   .  "<div class='filedata'><pre>".$Files{$file}->{content}."</pre></div>";
  1422     }
  1423     if ($result) {
  1424         return "<div class='files_toc'>".collapse_list(\@toc)."</div>".$result;
  1425     } 
  1426     else {
  1427         return "";
  1428     }
  1429 }
  1432 sub init_variables
  1433 {
  1434 $Html_Help = <<HELP;
  1435     Для того чтобы использовать LiLaLo, не нужно знать ничего особенного:
  1436     всё происходит само собой.
  1437     Однако, чтобы ведение и последующее использование журналов
  1438     было как можно более эффективным, желательно иметь в виду следующее:
  1439     <ol>
  1440     <li><p> 
  1441     В журнал автоматически попадают все команды, данные в любом терминале системы.
  1442     </p></li>
  1443     <li><p>
  1444     Для того чтобы убедиться, что журнал на текущем терминале ведётся, 
  1445     и команды записываются, дайте команду w.
  1446     В поле WHAT, соответствующем текущему терминалу, 
  1447     должна быть указана программа script.
  1448     </p></li>
  1449     <li><p>
  1450     Команды, при наборе которых были допущены синтаксические ошибки, 
  1451     выводятся перечёркнутым текстом:
  1452 <table>
  1453 <tr class='command'>
  1454 <td class='script'>
  1455 <pre class='_mistyped_cline'>
  1456 \$ l s-l</pre>
  1457 <pre class='_mistyped_output'>bash: l: command not found
  1458 </pre>
  1459 </td>
  1460 </tr>
  1461 </table>
  1462 <br/>
  1463     </p></li>
  1464     <li><p>
  1465     Если код завершения команды равен нулю, 
  1466     команда была выполнена без ошибок.
  1467     Команды, код завершения которых отличен от нуля, выделяются цветом.
  1468 <table>
  1469 <tr class='command'>
  1470 <td class='script'>
  1471 <pre class='_wrong_cline'>
  1472 \$ test 5 -lt 4</pre>
  1473 </pre>
  1474 </td>
  1475 </tr>
  1476 </table>
  1477     Обратите внимание на то, что код завершения команды может быть отличен от нуля
  1478     не только в тех случаях, когда команда была выполнена с ошибкой.
  1479     Многие команды используют код завершения, например, для того чтобы показать результаты проверки
  1480 <br/>
  1481     </p></li>
  1482     <li><p>
  1483     Команды, ход выполнения которых был прерван пользователем, выделяются цветом.
  1484 <table>
  1485 <tr class='command'>
  1486 <td class='script'>
  1487 <pre class='_interrupted_cline'>
  1488 \$ find / -name abc</pre>
  1489 <pre class='interrupted_output'>find: /home/devi-orig/.gnome2: Keine Berechtigung
  1490 find: /home/devi-orig/.gnome2_private: Keine Berechtigung
  1491 find: /home/devi-orig/.nautilus/metafiles: Keine Berechtigung
  1492 find: /home/devi-orig/.metacity: Keine Berechtigung
  1493 find: /home/devi-orig/.inkscape: Keine Berechtigung
  1494 ^C
  1495 </pre>
  1496 </td>
  1497 </tr>
  1498 </table>
  1499 <br/>
  1500     </p></li>
  1501     <li><p>
  1502     Команды, выполненные с привилегиями суперпользователя,
  1503     выделяются слева красной чертой.
  1504 <table>
  1505 <tr class='command'>
  1506 <td class='script'>
  1507 <pre class='_root_cline'>
  1508 # id</pre>
  1509 <pre class='_root_output'>
  1510 uid=0(root) gid=0(root) Gruppen=0(root)
  1511 </pre>
  1512 </td>
  1513 </tr>
  1514 </table>
  1515     <br/>
  1516     </p></li>
  1517     <li><p>
  1518     Изменения, внесённые в текстовый файл с помощью редактора, 
  1519     запоминаются и показываются в журнале в формате ed.
  1520     Строки, начинающиеся символом "<", удалены, а строки,
  1521     начинающиеся символом ">" -- добавлены.
  1522 <table>
  1523 <tr class='command'>
  1524 <td class='script'>
  1525 <pre class='cline'>
  1526 \$ vi ~/.bashrc</pre>
  1527 <table><tr><td width='5'/><td class='diff'><pre>2a3,5
  1528 >    if [ -f /usr/local/etc/bash_completion ]; then
  1529 >         . /usr/local/etc/bash_completion
  1530 >        fi
  1531 </pre></td></tr></table></td>
  1532 </tr>
  1533 </table>
  1534     <br/>
  1535     </p></li>
  1536     <li><p>
  1537     Для того чтобы изменить файл в соответствии с показанными в диффшоте
  1538     изменениями, можно воспользоваться командой patch.
  1539     Нужно скопировать изменения, запустить программу patch, указав в
  1540     качестве её аргумента файл, к которому применяются изменения,
  1541     и всавить скопированный текст:
  1542 <table>
  1543 <tr class='command'>
  1544 <td class='script'>
  1545 <pre class='cline'>
  1546 \$ patch ~/.bashrc</pre>
  1547 </td>
  1548 </tr>
  1549 </table>
  1550     В данном случае изменения применяются к файлу ~/.bashrc
  1551     </p></li>
  1552     <li><p>
  1553     Для того чтобы получить краткую справочную информацию о команде, 
  1554     нужно подвести к ней мышь. Во всплывающей подсказке появится краткое
  1555     описание команды.
  1556     </p>
  1557     <p>
  1558     Если справочная информация о команде есть, 
  1559     команда выделяется голубым фоном, например: <span class="with_hint" title="главный текстовый редактор Unix">vi</span>.
  1560     Если справочная информация отсутствует,
  1561     команда выделяется розовым фоном, например: <span class="without_hint">notepad.exe</span>.
  1562     Справочная информация может отсутствовать в том случае, 
  1563     если (1) команда введена неверно; (2) если распознавание команды LiLaLo выполнено неверно;
  1564     (3) если информация о команде неизвестна LiLaLo.
  1565     Последнее возможно для редких команд.
  1566     </p></li>
  1567     <li><p>
  1568     Большие, в особенности многострочные, всплывающие подсказки лучше 
  1569     всего показываются браузерами KDE Konqueror, Apple Safari и Microsoft Internet Explorer.
  1570     В браузерах Mozilla и Firefox они отображаются не полностью, 
  1571     а вместо перевода строки выводится специальный символ.
  1572     </p></li>
  1573     <li><p>
  1574     Время ввода команды, показанное в журнале, соответствует времени 
  1575     <i>начала ввода командной строки</i>, которое равно тому моменту, 
  1576     когда на терминале появилось приглашение интерпретатора
  1577     </p></li>
  1578     <li><p>
  1579     Имя терминала, на котором была введена команда, показано в специальном блоке.
  1580     Этот блок показывается только в том случае, если терминал
  1581     текущей команды отличается от терминала предыдущей.
  1582     </p></li>
  1583     <li><p>
  1584     Вывод не интересующих вас в настоящий момент элементов журнала,
  1585     таких как время, имя терминала и других, можно отключить.
  1586     Для этого нужно воспользоваться <a href='#visibility_form'>формой управления журналом</a>
  1587     вверху страницы.
  1588     </p></li>
  1589     <li><p>
  1590     Небольшие комментарии к командам можно вставлять прямо из командной строки.
  1591     Комментарий вводится прямо в командную строку, после символов #^ или #v.
  1592     Символы ^ и v показывают направление выбора команды, к которой относится комментарий:
  1593     ^ - к предыдущей, v - к следующей.
  1594     Например, если в командной строке было введено:
  1595 <pre class='cline'>
  1596 \$ whoami
  1597 </pre>
  1598 <pre class='output'>
  1599 user
  1600 </pre>
  1601 <pre class='cline'>
  1602 \$ #^ Интересно, кто я?
  1603 </pre>
  1604     в журнале это будет выглядеть так:
  1606 <pre class='cline'>
  1607 \$ whoami
  1608 </pre>
  1609 <pre class='output'>
  1610 user
  1611 </pre>
  1612 <table class='note'><tr><td width='100%' class='note_text'>
  1613 <tr> <td> Интересно, кто я?<br/> </td></tr></table> 
  1614     </p></li>
  1615     <li><p>
  1616     Если комментарий содержит несколько строк,
  1617     его можно вставить в журнал следующим образом:
  1618 <pre class='cline'>
  1619 \$ whoami
  1620 </pre>
  1621 <pre class='output'>
  1622 user
  1623 </pre>
  1624 <pre class='cline'>
  1625 \$ cat > /dev/null #^ Интересно, кто я?
  1626 </pre>
  1627 <pre class='output'>
  1628 Программа whoami выводит имя пользователя, под которым 
  1629 мы зарегистрировались в системе.
  1630 -
  1631 Она не может ответить на вопрос о нашем назначении 
  1632 в этом мире.
  1633 </pre>
  1634     В журнале это будет выглядеть так:
  1635 <table>
  1636 <tr class='command'>
  1637 <td class='script'>
  1638 <pre class='cline'>
  1639 \$ whoami</pre>
  1640 <pre class='output'>user
  1641 </pre>
  1642 <table class='note'><tr><td class='note_title'>Интересно, кто я?</td></tr><tr><td width='100%' class='note_text'>
  1643 Программа whoami выводит имя пользователя, под которым<br/>
  1644 мы зарегистрировались в системе.<br/>
  1645 <br/>
  1646 Она не может ответить на вопрос о нашем назначении<br/>
  1647 в этом мире.<br/>
  1648 </td></tr></table>
  1649 </td>
  1650 </tr>
  1651 </table>
  1652     Для разделения нескольких абзацев между собой
  1653     используйте символ "-", один в строке.
  1654     <br/>
  1655 </p></li>
  1656     <li><p>
  1657     Комментарии, не относящиеся непосредственно ни к какой из команд, 
  1658     добавляются точно таким же способом, только вместо симолов #^ или #v 
  1659     нужно использовать символы #=
  1660     </p></li>
  1662     <p><li>
  1663     Содержимое файла может быть показано в журнале.
  1664     Для этого его нужно вывести с помощью программы cat.
  1665     Если вывод команды отметить симоволами #!, 
  1666     содержимое файла будет показано в журнале
  1667     в специально отведённой для этого секции.
  1668     </li></p>
  1670     <p>
  1671     <li>
  1672     Для того чтобы вставить скриншот интересующего вас окна в журнал,
  1673     нужно воспользоваться командой l3shot.
  1674     После того как команда вызвана, нужно с помощью мыши выбрать окно, которое
  1675     должно быть в журнале.
  1676     </li>
  1677     </p>
  1679     <p>
  1680     <li>
  1681     Команды в журнале расположены в хронологическом порядке.
  1682     Если две команды давались одна за другой, но на разных терминалах,
  1683     в журнале они будут рядом, даже если они не имеют друг к другу никакого отношения.
  1684 <pre>
  1685 1
  1686     2
  1687 3   
  1688     4
  1689 </pre>
  1690     Группы команд, выполненных на разных терминалах, разделяются специальной линией.
  1691     Под этой линией в правом углу показано имя терминала, на котором выполнялись команды.
  1692     Для того чтобы посмотреть команды только одного сенса, 
  1693     нужно щёкнуть по этому названию.
  1694     </li>
  1695     </p>
  1696 </ol>
  1697 HELP
  1699 $Html_About = <<ABOUT;
  1700     <p>
  1701     LiLaLo (L3) расшифровывается как Live Lab Log.<br/>
  1702     Программа разработана для повышения эффективности обучения Unix/Linux-системам.<br/>
  1703     (c) Игорь Чубин, 2004-2006<br/>
  1704     </p>
  1705 ABOUT
  1706 $Html_About.='$Id$ </p>';
  1708 $Html_JavaScript = <<JS;
  1709     function getElementsByClassName(Class_Name)
  1710     {
  1711         var Result=new Array();
  1712         var All_Elements=document.all || document.getElementsByTagName('*');
  1713         for (i=0; i<All_Elements.length; i++)
  1714             if (All_Elements[i].className==Class_Name)
  1715         Result.push(All_Elements[i]);
  1716         return Result;
  1717     }
  1718     function ShowHide (name)
  1719     {
  1720         elements=getElementsByClassName(name);
  1721         for(i=0; i<elements.length; i++)
  1722             if (elements[i].style.display == "none")
  1723                 elements[i].style.display = "";
  1724             else
  1725                 elements[i].style.display = "none";
  1726             //if (elements[i].style.visibility == "hidden")
  1727             //  elements[i].style.visibility = "visible";
  1728             //else
  1729             //  elements[i].style.visibility = "hidden";
  1730     }
  1731     function filter_by_output(text)
  1732     {
  1734         var jjj=0;
  1736         elements=getElementsByClassName('command');
  1737         for(i=0; i<elements.length; i++) {
  1738             subelems = elements[i].getElementsByTagName('pre');
  1739             for(j=0; j<subelems.length; j++) {
  1740                 if (subelems[j].className = 'output') {
  1741                     var str = new String(subelems[j].nodeValue);
  1742                     if (jjj != 1) { 
  1743                         alert(str);
  1744                         jjj=1;
  1745                     }
  1746                     if (str.indexOf(text) >0) 
  1747                         subelems[j].style.display = "none";
  1748                     else
  1749                         subelems[j].style.display = "";
  1751                 }
  1753             }
  1754         }       
  1756     }
  1757 JS
  1759 $SetCursorPosition_JS = <<JS;
  1760 function setCursorPosition(oInput,oStart,oEnd) {
  1761     oInput.focus();
  1762     if( oInput.setSelectionRange ) {
  1763         oInput.setSelectionRange(oStart,oEnd);
  1764     } else if( oInput.createTextRange ) {
  1765         var range = oInput.createTextRange();
  1766         range.collapse(true);
  1767         range.moveEnd('character',oEnd);
  1768         range.moveStart('character',oStart);
  1769         range.select();
  1770     }
  1771 }
  1772 JS
  1774 %Search_Machines = (
  1775         "google" =>     {   "query" =>  "http://www.google.com/search?q=" ,
  1776                     "icon"  =>  "$Config{frontend_google_ico}" },
  1777         "freebsd" =>    {   "query" =>  "http://www.freebsd.org/cgi/man.cgi?query=",
  1778                     "icon"  =>  "$Config{frontend_freebsd_ico}" },
  1779         "linux"  =>     {   "query" =>  "http://man.he.net/?topic=",
  1780                     "icon"  =>  "$Config{frontend_linux_ico}"},
  1781         "opennet"  =>   {   "query" =>  "http://www.opennet.ru/search.shtml?words=",
  1782                     "icon"  =>  "$Config{frontend_opennet_ico}"},
  1783         "local" =>  {   "query" =>  "http://www.freebsd.org/cgi/man.cgi?query=",
  1784                     "icon"  =>  "$Config{frontend_local_ico}" },
  1786     );
  1788 %Elements_Visibility = (
  1789         "0 new_commands_table"      =>  "новые команды",
  1790         "1 diff"      =>  "редактор",
  1791         "2 time"      =>  "время",
  1792         "3 ttychange"     =>  "терминал",
  1793         "4 wrong_output wrong_cline wrong_root_output wrong_root_cline" 
  1794                 =>  "команды с ненулевым кодом завершения",
  1795         "5 mistyped_output mistyped_cline mistyped_root_output mistyped_root_cline" 
  1796                 =>  "неверно набранные команды",
  1797         "6 interrupted_output interrupted_cline interrupted_root_output interrupted_root_cline" 
  1798                 =>  "прерванные команды",
  1799         "7 tab_completion_output tab_completion_cline"    
  1800                 =>  "продолжение с помощью tab"
  1801 );
  1803 @Day_Name      = qw/ Воскресенье Понедельник Вторник Среда Четверг Пятница Суббота /;
  1804 @Month_Name    = qw/ Январь Февраль Март Апрель Май Июнь Июль Август Сентябрь Октябрь Ноябрь Декабрь /;
  1805 @Of_Month_Name = qw/ Января Февраля Марта Апреля Мая Июня Июля Августа Сентября Октября Ноября Декабря /;
  1806 }
  1811 # Временно удалённый код
  1812 # Возможно, он не понадобится уже никогда
  1815 sub search_by
  1816 {
  1817     my $sm = shift;
  1818     my $topic = shift;
  1819     $topic =~ s/ /+/;
  1821     return "<a href='". $Search_Machines{$sm}->{"query"}."$topic'><img width='16' height='16' src='".
  1822                 $Search_Machines{$sm}->{"icon"}."' border='0'/></a>";
  1823 }
