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