lilalo
view l3-frontend @ 55:d3fcff5e3757
mywi ответы кэшируются + подсветка для записей с хинтами
author | devi |
---|---|
date | Thu Dec 22 11:56:06 2005 +0200 (2005-12-22) |
parents | f9447da96f15 |
children | 43aeb3036aaa |
line source
1 #!/usr/bin/perl -w
3 use IO::Socket;
4 use lib '.';
5 use l3config;
6 use locale;
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 # vvv Инициализация переменных выполняется процедурой init_variables
16 our @Day_Name;
17 our @Month_Name;
18 our @Of_Month_Name;
19 our %Search_Machines;
20 our %Elements_Visibility;
21 # ^^^
23 our %Stat;
24 our %CommandsFDistribution; # Сколько раз в журнале встречается какая команда
26 my %mywi_cache_for; # Кэш для экономии обращений к mywi
29 sub search_buy;
30 sub make_comment;
31 sub load_command_lines_from_xml;
32 sub load_sessions_from_xml;
33 sub print_command_lines;
34 sub sort_command_lines;
35 sub process_command_lines;
36 sub init_variables;
37 sub main;
38 sub collapse_list($);
40 main();
42 sub main
43 {
44 $| = 1;
46 init_variables();
47 init_config();
49 open_mywi_socket();
50 load_command_lines_from_xml($Config{"backend_datafile"});
51 load_sessions_from_xml($Config{"backend_datafile"});
52 sort_command_lines;
53 process_command_lines;
54 print_command_lines($Config{"output"});
55 close_mywi_socket;
56 }
59 sub search_by
60 {
61 my $sm = shift;
62 my $topic = shift;
63 $topic =~ s/ /+/;
65 return "<a href='". $Search_Machines{$sm}->{"query"}."$topic'><img width='16' height='16' src='".
66 $Search_Machines{$sm}->{"icon"}."' border='0'/></a>";
67 }
69 sub extract_from_cline
70 # Разобрать командную строку $_[1] и возвратить хэш, содержащий
71 # номер первого появление команды в строке:
72 # команда => первая позиция
73 {
74 my $what = $_[0];
75 my $cline = $_[1];
76 my @lists = split /\;/, $cline;
79 my @commands = ();
80 for my $list (@lists) {
81 push @commands, split /\|/, $list;
82 }
84 my %commands;
85 my %args;
86 my $i=0;
87 for my $command (@commands) {
88 $command =~ s@^\s*\S+/@@;
89 $command =~ /\s*(\S+)\s*(.*)/;
90 if ($1 && $1 eq "sudo" ) {
91 $commands{"$1"}=$i++;
92 $command =~ s/\s*sudo\s+//;
93 }
94 $command =~ s@^\s*\S+/@@;
95 $command =~ /\s*(\S+)\s*(.*)/;
96 if ($1 && !defined $commands{"$1"}) {
97 $commands{"$1"}=$i++;
98 };
99 if ($2) {
100 my $args = $2;
101 my @args = split (/\s+/, $args);
102 for my $a (@args) {
103 $args{"$a"}=$i++
104 if !defined $args{"$a"};
105 };
108 }
109 }
111 if ($what eq "commands") {
112 return \%commands;
113 } else {
114 return \%args;
115 }
117 }
119 sub open_mywi_socket
120 {
121 $Mywi_Socket = IO::Socket::INET->new(
122 PeerAddr => $Config{mywi_server},
123 PeerPort => $Config{mywi_port},
124 Proto => "tcp",
125 Type => SOCK_STREAM);
126 }
128 sub close_mywi_socket
129 {
130 close ($Mywi_Socket) if $Mywi_Socket ;
131 }
134 sub mywi_client
135 {
136 my $query = $_[0];
137 my $mywi;
139 open_mywi_socket;
140 if ($Mywi_Socket) {
141 local $| = 1;
142 local $/ = "";
143 print $Mywi_Socket $query."\n";
144 $mywi = <$Mywi_Socket>;
145 $mywi = "" if $mywi =~ /nothing app/;
146 }
147 close_mywi_socket;
148 return $mywi;
149 }
151 sub make_comment
152 {
153 my $cline = $_[0];
154 #my $files = $_[1];
156 my @comments;
157 my @commands = keys %{extract_from_cline("commands", $cline)};
158 my @args = keys %{extract_from_cline("args", $cline)};
159 return if (!@commands && !@args);
160 #return "commands=".join(" ",@commands)."; files=".join(" ",@files);
162 # Commands
163 for my $command (@commands) {
164 $command =~ s/'//g;
165 $CommandsFDistribution{$command}++;
166 if (!$Commands_Description{$command}) {
167 $mywi_cache_for{$command} ||= mywi_client ($command) || "";
168 my $mywi = join ("\n", grep(/\([18]\)/, split(/\n/, $mywi_cache_for{$command})));
169 $mywi =~ s/\s+/ /;
170 if ($mywi !~ /^\s*$/) {
171 $Commands_Description{$command} = $mywi;
172 }
173 else {
174 next;
175 }
176 }
178 push @comments, $Commands_Description{$command};
179 }
180 return join(" \n", @comments);
182 # Files
183 for my $arg (@args) {
184 $arg =~ s/'//g;
185 if (!$Args_Description{$arg}) {
186 my $mywi;
187 $mywi = mywi_client ($arg);
188 $mywi = join ("\n", grep(/\([5]\)/, split(/\n/, $mywi)));
189 $mywi =~ s/\s+/ /;
190 if ($mywi !~ /^\s*$/) {
191 $Args_Description{$arg} = $mywi;
192 }
193 else {
194 next;
195 }
196 }
198 push @comments, $Args_Description{$arg};
199 }
201 }
203 =cut
204 Процедура load_command_lines_from_xml выполняет загрузку разобранного lab-скрипта
205 из XML-документа в переменную @Command_Lines
207 Предупреждение!
208 Процедура не в состоянии обрабатывать XML-документ любой структуры.
209 В действительности файл cache из которого загружаются данные
210 просто напоминает XML с виду.
211 =cut
212 sub load_command_lines_from_xml
213 {
214 my $datafile = $_[0];
216 open (CLASS, $datafile)
217 or die "Can't open file of the class ",$datafile,"\n";
218 local $/;
219 $data = <CLASS>;
220 close(CLASS);
222 for $command ($data =~ m@<command>(.*?)</command>@sg) {
223 my %cl;
224 while ($command =~ m@<([^>]*?)>(.*?)</\1>@sg) {
225 $cl{$1} = $2;
226 }
227 push @Command_Lines, \%cl;
228 }
229 }
231 sub load_sessions_from_xml
232 {
233 my $datafile = $_[0];
235 open (CLASS, $datafile)
236 or die "Can't open file of the class ",$datafile,"\n";
237 local $/;
238 my $data = <CLASS>;
239 close(CLASS);
241 for my $session ($data =~ m@<session>(.*?)</session>@sg) {
242 my %session;
243 while ($session =~ m@<([^>]*?)>(.*?)</\1>@sg) {
244 $session{$1} = $2;
245 }
246 $Sessions{$session{local_session_id}} = \%session;
247 }
248 }
252 sub sort_command_lines
253 {
254 # Sort Command_Lines
255 # Write Command_Lines to Command_Lines_Index
257 my @index;
258 for (my $i=0;$i<=$#Command_Lines;$i++) {
259 $index[$i]=$i;
260 }
262 @Command_Lines_Index = sort {
263 $Command_Lines[$index[$a]]->{"time"} <=> $Command_Lines[$index[$b]]->{"time"}
264 } @index;
266 }
268 sub process_command_lines
269 {
270 for my $i (@Command_Lines_Index) {
272 my $cl = \$Command_Lines[$i];
273 #@{${$cl}->{"new_commands"}} =();
274 #@{${$cl}->{"new_files"}} =();
275 $$cl->{"class"} = "";
277 if ($$cl->{"err"}) {
278 $$cl->{"class"}="wrong";
279 $$cl->{"class"}="interrupted"
280 if ($$cl->{"err"} eq 130);
281 }
282 if (!$$cl->{"euid"}) {
283 $$cl->{"class"}.="_root";
284 }
286 #tab# my @tab_words=split /\s+/, $$cl->{"output"};
287 #tab# my $last_word= $$cl->{"cline"} =~ /(\S*)$/;
288 #tab# $last_word =~ s@.*/@@;
289 #tab# my $this_is_tab=1;
290 #tab#
291 #tab# if ($last_word && @tab_words >2) {
292 #tab# for my $tab_words (@tab_words) {
293 #tab# if ($tab_words !~ /^$last_word/) {
294 #tab# $this_is_tab=0;
295 #tab# last;
296 #tab# }
297 #tab# }
298 #tab# }
299 #tab# $$cl->{"class"}="tab" if $this_is_tab;
302 # if ( !$$cl->{"err"}) {
303 # # Command does not contain mistakes
304 #
305 # my %commands = extract_from_cline("commands", ${$cl}->{"cline"});
306 # my %files = extract_from_cline("files", ${$cl}->{"cline"});
307 #
308 # # Searching for new commands only
309 # for my $command (keys %commands) {
310 # if (!defined $Commands_Stat{$command}) {
311 # push @{$$cl->{new_commands}}, $command;
312 # }
313 # $Commands_Stat{$command}++;
314 # }
315 #
316 # for my $file (keys %files) {
317 # if (!defined $Files_Stat{$file}) {
318 # push @{$$cl->{new_files}}, $file;
319 # }
320 # $Files_Stat{$file}++;
321 # }
322 # }
324 if ($$cl->{cline}=~ m@cat[^#]*#([\^=v])\s*(.*)@) {
325 if ($1 eq "=") {
326 $$cl->{"class"} = "note";
327 $$cl->{"note"} = $$cl->{"output"};
328 $$cl->{"note_title"} = $2;
329 }
330 else {
331 my $j = $i;
332 if ($1 eq "^") {
333 $j--;
334 $j-- while ($j >=0 && (!$Command_Lines[$j] || $Command_Lines[$j]->{tty} ne $$cl->{tty}));
335 }
336 elsif ($1 eq "v") {
337 $j++;
338 $j++ while ($j <= @Command_Lines && (!$Command_Lines[$j] || $Command_Lines[$j]->{tty} ne $$cl->{tty}));
339 }
340 $Command_Lines[$j]->{note_title}="$2";
341 $Command_Lines[$j]->{note}=$$cl->{output};
342 $$cl=0;
343 }
344 }
345 elsif ($$cl->{cline}=~ /#([\^=v])(.*)/) {
346 if ($1 eq "=") {
347 $$cl->{"class"} = "note";
348 $$cl->{"note"} = $2;
349 }
350 else {
351 my $j=$i;
352 if ($1 eq "^") {
353 $j--;
354 $j-- while ($j >=0 && (!$Command_Lines[$j] || $Command_Lines[$j]->{tty} ne $$cl->{tty}));
355 }
356 elsif ($1 eq "v") {
357 $j++;
358 $j++ while ($j <= @Command_Lines && $Command_Lines[$j]->{tty} ne $$cl->{tty} || !$Command_Lines[$j]);
359 }
360 $Command_Lines[$j]->{note}.="$2\n";
361 $$cl=0;
362 }
363 }
364 }
366 }
369 =cut
370 Процедура print_command_lines выводит HTML-представление
371 разобранного lab-скрипта.
373 Разобранный lab-скрипт должен находиться в массиве @Command_Lines
374 =cut
376 sub print_command_lines
377 {
378 my $output_filename=$_[0];
380 my $course_name = $Config{"course-name"};
381 my $course_code = $Config{"course-code"};
382 my $course_date = $Config{"course-date"};
383 my $course_center = $Config{"course-center"};
384 my $course_trainer = $Config{"course-trainer"};
385 my $course_student = $Config{"course-student"};
388 # Результат выполнения процедуры равен
389 # join("", @Result{header,body,stat,help,about,footer})
390 my %Result;
391 my @toc; # Хранит оглавление
392 my $note_number=0;
394 $Result{"body"} = "<table width='100%'>\n";
396 my $cl;
397 my $last_tty="";
398 my $last_day="";
399 my $in_range=0;
401 my $current_command=0;
403 COMMAND_LINE:
404 for my $k (@Command_Lines_Index) {
406 my $cl=$Command_Lines[$Command_Lines_Index[$current_command++]];
408 next unless $cl;
411 if ($Config{filter}) {
412 # Инициализация фильтра
413 my %filter;
414 for (split /&/,$Config{filter}) {
415 my ($var, $val) = split /=/;
416 $filter{$var} = $val || "";
417 }
419 for my $filter_key (keys %filter) {
420 next COMMAND_LINE if
421 defined($cl->{local_session_id})
422 && defined($Sessions{$cl->{local_session_id}}->{$filter_key})
423 && $Sessions{$cl->{local_session_id}}->{$filter_key} ne $filter{$filter_key};
424 #print $filter_key,"\n";
425 }
427 #if ($filter{user}) {
428 # next COMMAND_LINE unless $Sessions{$cl->{local_session_id}}->{user} eq $filter{user};
429 #}
431 #for my $filter_field (keys %filter) {
432 # next COMMAND_LINE unless $Sessions{$cl->{local_session_id}}->{$filter_field} eq $filter{$filter_field};
433 #}
434 }
436 if ($Config{"from"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"from"}/) {
437 $in_range=1;
438 next;
439 }
440 if ($Config{"to"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"to"}/) {
441 $in_range=0;
442 next;
443 }
444 next if ($Config{"from"} && $Config{"to"} && !$in_range)
445 ||
446 ($Config{"skip_empty"} =~ /^y/i && $cl->{"cline"} =~ /^\s*$/ )
447 ||
448 ($Config{"skip_wrong"} =~ /^y/i && $cl->{"err"} != 0)
449 ||
450 ($Config{"skip_interrupted"} =~ /^y/i && $cl->{"err"} == 130);
452 #my @new_commands=@{$cl->{"new_commands"}};
453 #my @new_files=@{$cl->{"new_files"}};
455 if ($cl->{class} eq "note") {
456 my $note = $cl->{note};
457 $note = join ("\n", map ("<p>$_</p>", split (/-\n/, $note)));
458 $note =~ s@(http:[a-zA-Z.0-9/?\_%-]*)@<a href='$1'>$1</a>@g;
459 $note =~ s@(www\.[a-zA-Z.0-9/?\_%-]*)@<a href='$1'>$1</a>@g;
460 $Result{"body"} .= "<tr><td colspan='6'>";
461 $Result{"body"} .= "<h4 id='note$note_number'>".$cl->{note_title}."</h4>" if $cl->{note_title};
462 $Result{"body"} .= "".$note."<p/><p/></td></td>";
464 if ($cl->{note_title}) {
465 push @{$toc[@toc]},"<a href='#note$note_number'>".$cl->{note_title}."</a>";
466 $note_number++;
467 }
468 next;
469 }
471 my $cl_class="cline";
472 my $out_class="output";
473 if ($cl->{"class"}) {
474 $cl_class = $cl->{"class"}."_".$cl_class;
475 $out_class = $cl->{"class"}."_".$out_class;
476 }
478 my @new_commands;
479 my @new_files;
480 @new_commands = split (/\s+/, $cl->{"new_commands"}) if defined $cl->{"new_commands"};
481 @new_files = split (/\s+/, $cl->{"new_files"}) if defined $cl->{"new_files"};
483 my $output="";
484 if ($Config{"head_lines"} || $Config{"tail_lines"}) {
485 # Partialy output
486 my @lines = split '\n', $cl->{"output"};
487 # head
488 my $mark=1;
489 for (my $i=0; $i<= $#lines && $i < $Config{"head_lines"}; $i++) {
490 $output .= $lines[$i]."\n";
491 }
492 # tail
493 my $start=$#lines-$Config{"tail_lines"}+1;
494 if ($start < 0) {
495 $start=0;
496 $mark=0;
497 }
498 if ($start < $Config{"head_lines"}) {
499 $start=$Config{"head_lines"};
500 $mark=0;
501 }
502 $output .= $Config{"skip_text"}."\n" if $mark;
503 for ($i=$start; $i<= $#lines; $i++) {
504 $output .= $lines[$i]."\n";
505 }
506 }
507 else {
508 # Full output
509 $output .= $cl->{"output"};
510 }
511 #$output .= "^C\n" if ($cl->{"err"} eq "130");
513 #
514 ##
515 ## Начинается собственно вывод
516 ##
517 #
519 # <command>
521 my ($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = localtime($cl->{time});
522 next if $Stat{LastCommand} == $cl->{time};
523 $Stat{FirstCommand} = $cl->{time} unless $Stat{FirstCommand};
524 $Stat{LastCommand} = 0 unless defined $Stat{LastCommand};
525 $Stat{TotalTime} += $cl->{time} - $Stat{LastCommand}
526 if $cl->{time} - $Stat{LastCommand} < $Config{stat_inactivity_interval};
527 $Stat{LastCommand} = $cl->{time};
528 $Stat{TotalCommands} = 0 unless $Stat{TotalCommands};
529 $Stat{TotalCommands}++;
531 # Добавляем спереди 0 для удобочитаемости
532 $min = "0".$min if $min =~ /^.$/;
533 $hour = "0".$hour if $hour =~ /^.$/;
534 $sec = "0".$sec if $sec =~ /^.$/;
536 $class=$cl->{"out_class"};
537 $class =~ s/output$//;
539 $Stat{ErrorCommands}++
540 if $class =~ /wrong/;
542 $Result{"body"} .= "<tr class='command'>\n";
545 # DAY CHANGE
546 if ( $last_day ne $day) {
547 #$Result{"body"} .= "<td colspan='6'><p></p><h3>День ",$day,"</h4></td></tr><tr>";
548 $Result{"body"} .= "<td colspan='6'><p></p><h3 id='day$day'>".$Day_Name[$wday]."</h4></td></tr><tr>";
549 push @toc, "<a href='#day$day'>".$Day_Name[$wday]."</a>\n";
550 $last_day=$day;
551 }
553 # CONSOLE CHANGE
554 if ( $last_tty ne $cl->{"tty"}) {
555 my $host;
556 #$host = $Sessions{$cl->{local_session_id}}->{user}."@".$Sessions{$cl->{local_session_id}}->{hostname};
557 my $body = $cl->{"tty"};
558 $body .= " \@$host" if $host;
559 $Result{"body"} .= "<td colspan='6'><table><tr><td class='ttychange' width='140' align='center'>".$body."</td></tr></table></td></tr><tr>";
560 $last_tty=$cl->{"tty"};
561 }
563 # TIME
564 if ($Config{"show_time"} =~ /^y/i) {
565 $Result{"body"} .= "<td valign='top' class='time' width='$Config{time_width}'><pre>".
566 $hour. ":". $min. ":". $sec.
567 "</td>";
568 } else {
569 $Result{"body"} .= "<td width='0'/>"
570 }
572 # COMMAND
573 $Result{"body"} .= "<td class='script'>\n";
574 $Result{"body"} .= "<pre class='${class}cline'>\n";
575 my $cline = $cl->{"prompt"}.$cl->{"cline"};
576 $cline =~ s/\n//;
578 #$cline .= "(".$Sessions{$cl->{local_session_id}}.")";
580 my $hint = make_comment($cl->{"cline"});
581 $cline = "<span title='$hint' class='with_hint'>$cline</span>" if $hint;
582 $cline = "<span class='without_hint'>$cline</span>" if !$hint;
583 $Result{"body"} .= $cline;
584 $Result{"body"} .= "</pre>\n";
586 my $last_command = $cl->{"last_command"};
587 if (!(
588 $Config{"suppress_editors"} =~ /^y/i && grep ($_ eq $last_command, @{$Config{"editors"}}) ||
589 $Config{"suppress_pagers"} =~ /^y/i && grep ($_ eq $last_command, @{$Config{"pagers"}}) ||
590 $Config{"suppress_terminal"}=~ /^y/i && grep ($_ eq $last_command, @{$Config{"terminal"}})
591 )) {
593 $Result{"body"} .= "<pre class='".$cl->{out_class}."'>";
594 $Result{"body"} .= $output;
595 $Result{"body"} .= "</pre>\n";
596 }
598 # DIFF
599 if ( $Config{"show_diffs"} =~ /^y/i && $cl->{"diff"}) {
600 $Result{"body"} .= "<table><tr><td width='5'/><td class='diff'><pre>";
601 $Result{"body"} .= $cl->{"diff"};
602 $Result{"body"} .= "</pre></td></tr></table>";
603 }
605 #NOTES
606 if ( $Config{"show_notes"} =~ /^y/i && $cl->{"note"}) {
607 my $note=$cl->{"note"};
608 $note =~ s/\n/<br\/>\n/msg;
609 if (not $note =~ s@(http:[a-zA-Z.0-9/_?%-]*)@<a href='$1'>$1</a>@g) {
610 $note =~ s@(www\.[a-zA-Z.0-9/_?%-]*)@<a href='$1'>$1</a>@g;
611 };
612 # Ширину пока не используем
613 # $Result{"body"} .= "<table width='$Config{note_width}' class='note'>";
614 $Result{"body"} .= "<table class='note'>";
615 $Result{"body"} .= "<tr><td class='note_title'>".$cl->{note_title}."</td></tr>" if $cl->{note_title};
616 $Result{"body"} .= "<tr><td width='100%' class='note_text'>".$note."</td></tr>";
617 $Result{"body"} .= "</table>\n";
618 }
620 # COMMENT
621 if ( $Config{"show_comments"} =~ /^y/i) {
622 my $comment = make_comment($cl->{"cline"});
623 if ($comment) {
624 $Result{"body"} .= "<table width='$Config{comment_width}'>".
625 "<tr><td width='5'/><td>";
626 $Result{"body"} .= "<table class='note' width='100%'>";
627 $Result{"body"} .= $comment;
628 $Result{"body"} .= "</table>\n";
629 $Result{"body"} .= "</td></tr></table>";
630 }
631 }
633 # Вывод очередной команды окончен
634 $Result{"body"} .= "</td>\n";
635 $Result{"body"} .= "</tr>\n";
636 }
638 $Result{"body"} .= "</table>\n";
640 #$Result{"stat"} = "<hr/>";
642 %StatNames = (
643 FirstCommand => "Время первой команды журнала",
644 LastCommand => "Время последней команды журнала",
645 TotalCommands => "Количество командных строк в журнале",
646 ErrorsPercentage => "Процент команд с ненулевым кодом завершения, %",
647 TotalTime => "Суммарное время работы с терминалом <sup><font size='-2'>*</font></sup>, час",
648 CommandsPerTime => "Количество командных строк в единицу времени, команда/мин",
649 CommandsFrequency => "Частота использования команд",
650 RareCommands => "Частота использования этих команд < 0.5%",
651 );
652 @StatOrder = (
653 FirstCommand,
654 LastCommand,
655 TotalCommands,
656 ErrorsPercentage,
657 TotalTime,
658 CommandsPerTime,
659 CommandsFrequency,
660 RareCommands,
661 );
663 # Подготовка статистики к выводу
664 # Некоторые значения пересчитываются!
665 # Дальше их лучше уже не использовать!!!
667 my %CommandsFrequency = %CommandsFDistribution;
669 $Stat{TotalTime} ||= 0;
670 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($Stat{FirstCommand} || 0);
671 $Stat{FirstCommand} = sprintf "%02i:%02i:%02i %04i-%2i-%2i", $hour, $min, $sec, $year+1900, $mon+1, $mday;
672 ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($Stat{LastCommand} || 0);
673 $Stat{LastCommand} = sprintf "%02i:%02i:%02i %04i-%2i-%2i", $hour, $min, $sec, $year+1900, $mon+1, $mday;
674 $Stat{ErrorsPercentage} = sprintf "%5.2f", $Stat{ErrorCommands}*100/$Stat{TotalCommands}
675 if $Stat{TotalCommands};
676 $Stat{CommandsPerTime} = sprintf "%5.2f", $Stat{TotalCommands}*60/$Stat{TotalTime}
677 if $Stat{TotalTime};
678 $Stat{TotalTime} = sprintf "%5.2f", $Stat{TotalTime}/60/60;
680 my $total_commands=0;
681 for $command (keys %CommandsFrequency){
682 $total_commands += $CommandsFrequency{$command};
683 }
684 if ($total_commands) {
685 for $command (reverse sort {$CommandsFrequency{$a} <=> $CommandsFrequency{$b}} keys %CommandsFrequency){
686 my $command_html;
687 my $percentage = sprintf "%5.2f",$CommandsFrequency{$command}*100/$total_commands;
688 if ($percentage < 0.5) {
689 my $hint = make_comment($command);
690 $command_html = "$command";
691 $command_html = "<span title='$hint' class='hint'>$command_html</span>" if $hint;
692 my $command_html = "<tt>$command_html</tt>";
693 $Stat{RareCommands} .= $command_html."<sub><font size='-2'>".$CommandsFrequency{$command}."</font></sub> , ";
694 }
695 else {
696 my $hint = make_comment($command);
697 $command_html = "$command";
698 $command_html = "<span title='$hint' class='hint'>$command_html</span>" if $hint;
699 my $command_html = "<tt>$command_html</tt>";
700 $percentage = sprintf "%5.2f",$percentage;
701 $Stat{CommandsFrequency} .= "<tr><td>".$command_html."</td><td>".$CommandsFrequency{$command}."</td>".
702 "<td>|".("="x int($CommandsFrequency{$command}*100/$total_commands))."| $percentage%</td></tr>";
703 }
704 }
705 $Stat{CommandsFrequency} = "<table>".$Stat{CommandsFrequency}."</table>";
706 $Stat{RareCommands} =~ s/, $// if $Stat{RareCommands};
707 }
709 $Result{"stat"} .= "<h2 id='stat'>Статистика</h2>";
710 $Result{"stat"} .= "<table>";
711 for my $stat (@StatOrder) {
712 $Result{"stat"} .= "<tr valign='top'><td width='300'>".$StatNames{"$stat"}."</td><td>".$Stat{"$stat"}."</td></tr>"
713 if $Stat{"$stat"};
714 }
716 $Result{"stat"} .= "</table>";
717 $Result{"stat"} .= "<font size='-2'>____<br/>*) Интервалы неактивности длительностью ".($Config{stat_inactivity_interval}/60)." минут и более не учитываются</font></br>";
719 #$Result{"help"} .= "<hr/>";
720 $Result{"help"} .= "<h2 id='help'>Справка</h2>";
721 $Result{"help"} .= "$Html_Help<br/>";
722 #$Result{"about"} .= "<hr/>";
723 $Result{"about"} .= "<h2 id='about'>О программе</h2>";
724 $Result{"about"} .= "$Html_About";
725 $Result{"footer"} .= "</body>\n";
726 $Result{"footer"} .= "</html>\n";
728 $Result{"title"} = "Журнал лабораторных работ";
729 $Result{"title"}.= " -- ".$course_student if $course_student;
730 if ($course_date) {
731 $Result{"title"}.= " -- ".$course_date;
732 $Result{"title"}.= "/".$course_code if $course_code;
733 }
734 else {
735 $Result{"title"}.= " -- ".$course_code if $course_code;
736 }
738 # Заголовок генерируется позже всего
739 # Тогда, когда известно уже, что должно быть написано в
740 # оглавлении
741 $Result{"header"} = <<HEADER;
742 <html>
743 <head>
744 <meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
745 <link rel='stylesheet' href='$Config{frontend_css}' type='text/css'/>
746 <title>$Result{title}</title>
747 </head>
748 <body>
749 <script>
750 $Html_JavaScript
751 </script>
752 <h1>Журнал лабораторных работ</h1>
754 HEADER
755 $Result{"header"} .= "<p>" if $course_student || $course_trainer || $course_name || $course_code || $course_date || $course_center;
756 $Result{"header"} .= "Выполнил $course_student<br/>" if $course_student;
757 $Result{"header"} .= "Проверил $course_trainer <br/>" if $course_trainer;
758 $Result{"header"} .= "Курс " if $course_name || $course_code || $course_date;
759 $Result{"header"} .= "$course_name " if $course_name;
760 $Result{"header"} .= "($course_code)" if $course_code;
761 $Result{"header"} .= ", $course_date<br/>" if $course_date;
762 $Result{"header"} .= "Учебный центр $course_center <br/>" if $course_center;
763 $Result{"header"} .= "</p>" if $course_student || $course_trainer || $course_name || $course_code || $course_date || $course_center;
765 my $toc = collapse_list (\@toc);
766 $Result{"header"} .= <<HEADER;
767 <table border=0 id='toc' class='toc'>
768 <tr>
769 <td>
770 <div class='toc_title'>Содержание</div>
771 <ul>
772 <li><a href='#log'>Журнал</a></li>
773 <ul>$toc</ul>
774 <li><a href='#stat'>Статистика</a></li>
775 <li><a href='#help'>Справка</a></li>
776 <li><a href='#about'>О программе</a></li>
777 </ul>
778 </td>
779 </tr>
780 </table>
782 <h2 id="log">Журнал</h2>
783 HEADER
784 $Result{"header"} .= "<table id='visibility_form' class='visibility_form'><tr><td><form>\n";
785 for my $element (keys %Elements_Visibility)
786 {
787 my @e = split /\s+/, $element;
788 my $showhide = join "", map { "ShowHide('$_');" } @e ;
789 $Result{"header"} .= "<input type='checkbox' name='$e[0]' onclick=\"$showhide\" checked>".
790 $Elements_Visibility{$element}.
791 "</input><br>\n";
792 }
794 $Result{"header"} .= "</form></td></tr></table>\n";
796 if ($output_filename eq "-") {
797 print $Result{"header"}, $Result{"body"}, $Result{"stat"}, $Result{"help"}, $Result{"about"}, $Result{"footer"};
798 }
799 else {
800 open(OUT, ">", $output_filename)
801 or die "Can't open $output_filename for writing\n";
802 print OUT $Result{"header"}, $Result{"body"}, $Result{"stat"}, $Result{"help"}, $Result{"about"}, $Result{"footer"};
803 close(OUT);
804 }
805 }
809 sub collapse_list($)
810 {
811 my $res = "";
812 for my $elem (@{$_[0]}) {
813 if (ref $elem eq "ARRAY") {
814 $res .= "<ul>".collapse_list($elem)."</ul>";
815 }
816 else
817 {
818 $res .= "<li>".$elem."</li>";
819 }
820 }
821 return $res;
822 }
827 sub init_variables
828 {
829 $Html_Help = <<HELP;
830 Для того чтобы использовать LiLaLo, не нужно знать ничего особенного:
831 всё происходит само собой.
832 Однако, чтобы ведение и последующее использование журналов
833 было как можно более эффективным, желательно иметь в виду следующее:
834 <ul>
835 <li><p>
836 В журнал автоматически попадают все команды, данные в любом терминале системы.
837 </p></li>
838 <li><p>
839 Для того чтобы убедиться, что журнал на текущем терминале ведётся,
840 и команды записываются, дайте команду w.
841 В поле WHAT, соответствующем текущему терминалу,
842 должна быть указана программа script.
843 </p></li>
844 <li><p>
845 Если код завершения команды равен нулю,
846 команда была выполнена без ошибок.
847 Команды, код завершения которых отличен от нуля, выделяются цветом.
848 <table>
849 <tr class='command'>
850 <td class='script'>
851 <pre class='wrong_cline'>
852 \$ l s-l</pre>
853 <pre class='wrong_output'>bash: l: command not found
854 </pre>
855 </td>
856 </tr>
857 </table>
858 <br/>
859 </p></li>
860 <li><p>
861 Команды, ход выполнения которых был прерван пользователем, выделяются цветом.
862 <table>
863 <tr class='command'>
864 <td class='script'>
865 <pre class='interrupted_cline'>
866 \$ find / -name abc</pre>
867 <pre class='interrupted_output'>find: /home/devi-orig/.gnome2: Keine Berechtigung
868 find: /home/devi-orig/.gnome2_private: Keine Berechtigung
869 find: /home/devi-orig/.nautilus/metafiles: Keine Berechtigung
870 find: /home/devi-orig/.metacity: Keine Berechtigung
871 find: /home/devi-orig/.inkscape: Keine Berechtigung
872 ^C
873 </pre>
874 </td>
875 </tr>
876 </table>
877 <br/>
878 </p></li>
879 <li><p>
880 Команды, выполненные с привилегиями суперпользователя,
881 выделяются слева красной чертой.
882 <table>
883 <tr class='command'>
884 <td class='script'>
885 <pre class='_root_cline'>
886 # id</pre>
887 <pre class='_root_output'>
888 uid=0(root) gid=0(root) Gruppen=0(root)
889 </pre>
890 </td>
891 </tr>
892 </table>
893 <br/>
894 </p></li>
895 <li><p>
896 Изменения, внесённые в текстовый файл с помощью редактора,
897 запоминаются и показываются в журнале в формате ed.
898 Строки, начинающиеся символом "<", удалены, а строки,
899 начинающиеся символом ">" -- добавлены.
900 <table>
901 <tr class='command'>
902 <td class='script'>
903 <pre class='cline'>
904 \$ vi ~/.bashrc</pre>
905 <table><tr><td width='5'/><td class='diff'><pre>2a3,5
906 > if [ -f /usr/local/etc/bash_completion ]; then
907 > . /usr/local/etc/bash_completion
908 > fi
909 </pre></td></tr></table></td>
910 </tr>
911 </table>
912 <br/>
913 </p></li>
914 <li><p>
915 Для того чтобы изменить файл в соответствии с показанными в диффшоте
916 изменениями, можно воспользоваться командой patch.
917 Нужно скопировать изменения, запустить программу patch, указав в
918 качестве её аргумента файл, к которому применяются изменения,
919 и всавить скопированный текст:
920 <table>
921 <tr class='command'>
922 <td class='script'>
923 <pre class='cline'>
924 \$ patch ~/.bashrc</pre>
925 </td>
926 </tr>
927 </table>
928 В данном случае изменения применяются к файлу ~/.bashrc
929 </p></li>
930 <li><p>
931 Для того чтобы получить краткую справочную информацию о команде,
932 нужно подвести к ней мышь. Во всплывающей подсказке появится краткое
933 описание команды.
934 </p></li>
935 <li><p>
936 Время ввода команды, показанное в журнале, соответствует времени
937 <i>начала ввода командной строки</i>, которое равно тому моменту,
938 когда на терминале появилось приглашение интерпретатора
939 </p></li>
940 <li><p>
941 Имя терминала, на котором была введена команда, показано в специальном блоке.
942 Этот блок показывается только в том случае, если терминал
943 текущей команды отличается от терминала предыдущей.
944 </p></li>
945 <li><p>
946 Вывод не интересующих вас в настоящий момент элементов журнала,
947 таких как время, имя терминала и других, можно отключить.
948 Для этого нужно воспользоваться <a href='#visibility_form'>формой управления журналом</a>
949 вверху страницы.
950 </p></li>
951 <li><p>
952 Небольшие комментарии к командам можно вставлять прямо из командной строки.
953 Комментарий вводится прямо в командную строку, после символов #^ или #v.
954 Символы ^ и v показывают направление выбора команды, к которой относится комментарий:
955 ^ - к предыдущей, v - к следующей.
956 Например, если в командной строке было введено:
957 <pre class='cline'>
958 \$ whoami
959 </pre>
960 <pre class='output'>
961 user
962 </pre>
963 <pre class='cline'>
964 \$ #^ Интересно, кто я?
965 </pre>
966 в журнале это будет выглядеть так:
968 <pre class='cline'>
969 \$ whoami
970 </pre>
971 <pre class='output'>
972 user
973 </pre>
974 <table class='note'><tr><td width='100%' class='note_text'>
975 <tr> <td> Интересно, кто я?<br/> </td></tr></table>
976 </p></li>
977 <li><p>
978 Если комментарий содержит несколько строк,
979 его можно вставить в журнал следующим образом:
980 <pre class='cline'>
981 \$ whoami
982 </pre>
983 <pre class='output'>
984 user
985 </pre>
986 <pre class='cline'>
987 \$ cat > /dev/null #^ Интересно, кто я?
988 </pre>
989 <pre class='output'>
990 Программа whoami выводит имя пользователя, под которым
991 мы зарегистрировались в системе.
992 -
993 Она не может ответить на вопрос о нашем назначении
994 в этом мире.
995 </pre>
996 В журнале это будет выглядеть так:
997 <table>
998 <tr class='command'>
999 <td class='script'>
1000 <pre class='cline'>
1001 \$ whoami</pre>
1002 <pre class='output'>user
1003 </pre>
1004 <table class='note'><tr><td class='note_title'>Интересно, кто я?</td></tr><tr><td width='100%' class='note_text'>
1005 Программа whoami выводит имя пользователя, под которым<br/>
1006 мы зарегистрировались в системе.<br/>
1007 <br/>
1008 Она не может ответить на вопрос о нашем назначении<br/>
1009 в этом мире.<br/>
1010 </td></tr></table>
1011 </td>
1012 </tr>
1013 </table>
1014 Для разделения нескольких абзацев между собой
1015 используйте символ "-", один в строке.
1016 <br/>
1017 </p></li>
1018 <li><p>
1019 Комментарии, не относящиеся непосредственно ни к какой из команд,
1020 добавляются точно таким же способом, только вместо симолов #^ или #v
1021 нужно использовать символы #=
1022 </p></li>
1023 </ul>
1024 HELP
1026 $Html_About = <<ABOUT;
1027 <p>
1028 LiLaLo (L3) расшифровывается как Live Lab Log.<br/>
1029 Программа разработана для повышения эффективности обучения Unix/Linux-системам.<br/>
1030 (c) Игорь Чубин, 2004-2005<br/>
1031 </p>
1032 ABOUT
1033 $Html_About.='$Id$ </p>';
1035 $Html_JavaScript = <<JS;
1036 function getElementsByClassName(Class_Name)
1037 {
1038 var Result=new Array();
1039 var All_Elements=document.all || document.getElementsByTagName('*');
1040 for (i=0; i<All_Elements.length; i++)
1041 if (All_Elements[i].className==Class_Name)
1042 Result.push(All_Elements[i]);
1043 return Result;
1044 }
1045 function ShowHide (name)
1046 {
1047 elements=getElementsByClassName(name);
1048 for(i=0; i<elements.length; i++)
1049 if (elements[i].style.display == "none")
1050 elements[i].style.display = "";
1051 else
1052 elements[i].style.display = "none";
1053 //if (elements[i].style.visibility == "hidden")
1054 // elements[i].style.visibility = "visible";
1055 //else
1056 // elements[i].style.visibility = "hidden";
1057 }
1058 function filter_by_output(text)
1059 {
1061 var jjj=0;
1063 elements=getElementsByClassName('command');
1064 for(i=0; i<elements.length; i++) {
1065 subelems = elements[i].getElementsByTagName('pre');
1066 for(j=0; j<subelems.length; j++) {
1067 if (subelems[j].className = 'output') {
1068 var str = new String(subelems[j].nodeValue);
1069 if (jjj != 1) {
1070 alert(str);
1071 jjj=1;
1072 }
1073 if (str.indexOf(text) >0)
1074 subelems[j].style.display = "none";
1075 else
1076 subelems[j].style.display = "";
1078 }
1080 }
1081 }
1083 }
1084 JS
1086 %Search_Machines = (
1087 "google" => { "query" => "http://www.google.com/search?q=" ,
1088 "icon" => "$Config{frontend_google_ico}" },
1089 "freebsd" => { "query" => "http://www.freebsd.org/cgi/man.cgi?query=",
1090 "icon" => "$Config{frontend_freebsd_ico}" },
1091 "linux" => { "query" => "http://man.he.net/?topic=",
1092 "icon" => "$Config{frontend_linux_ico}"},
1093 "opennet" => { "query" => "http://www.opennet.ru/search.shtml?words=",
1094 "icon" => "$Config{frontend_opennet_ico}"},
1095 "local" => { "query" => "http://www.freebsd.org/cgi/man.cgi?query=",
1096 "icon" => "$Config{frontend_local_ico}" },
1098 );
1100 %Elements_Visibility = (
1101 "note" => "замечания",
1102 "diff" => "редактор",
1103 "time" => "время",
1104 "ttychange" => "терминал",
1105 "wrong_output wrong_cline wrong_root_output wrong_root_cline"
1106 => "команды с ошибками",
1107 "interrupted_output interrupted_cline interrupted_root_output interrupted_root_cline"
1108 => "прерванные команды",
1109 "tab_completion_output tab_completion_cline"
1110 => "продолжение с помощью tab"
1111 );
1113 @Day_Name = qw/ Воскресенье Понедельник Вторник Среда Четверг Пятница Суббота /;
1114 @Month_Name = qw/ Январь Февраль Март Апрель Май Июнь Июль Август Сентябрь Октябрь Ноябрь Декабрь /;
1115 @Of_Month_Name = qw/ Января Февраля Марта Апреля Мая Июня Июля Августа Сентября Октября Ноября Декабря /;
1116 }