lilalo

changeset 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 - при подсчёте времени работы с терминалом,
интервалы превышающие какую длительность не должны учитываться, сек
author devi
date Fri Nov 11 21:29:49 2005 +0200 (2005-11-11)
parents f5f07049bd4f
children 4d252e7dd478
files l3-agent l3-cgi l3-frontend l3config.pm
line diff
     1.1 --- a/l3-agent	Tue Nov 08 12:16:20 2005 +0200
     1.2 +++ b/l3-agent	Fri Nov 11 21:29:49 2005 +0200
     1.3 @@ -543,9 +543,10 @@
     1.4  				if (!$last_cl{"suppress_output"} || $last_cl{"err"}) {
     1.5  					for (my $i=0; $i<$Config{"terminal_height"}; $i++) {
     1.6  						my $line= $vt->row_plaintext($i);
     1.7 -						next if !defined ($line) || $line =~ /^\s*$/;
     1.8 +						next if !defined ($line) ; #|| $line =~ /^\s*$/;
     1.9  						$line =~ s/\s*$//;
    1.10 -						$last_cl{"output"} .= $line."\n";
    1.11 +						$line .= "\n" unless $line =~ /^\s*$/;
    1.12 +						$last_cl{"output"} .= $line;
    1.13  					}
    1.14  				}
    1.15  				else {
    1.16 @@ -690,6 +691,13 @@
    1.17  				$Files_Stat{$file}++;
    1.18  			}
    1.19  		}	
    1.20 +
    1.21 +		#if ($$cl->{cline}=~ /#\^(.*)/) {
    1.22 +		#	my $j=$i-1;
    1.23 +		#	$j-- while ($j >=0 && $Command_Lines[$j]->{tty} ne $$cl->{tty});
    1.24 +		#	$Command_Lines[$j]->{note_title}="Замечание";
    1.25 +		#	$Command_Lines[$j]->{note}="$1";
    1.26 +		#}
    1.27  	}	
    1.28  
    1.29  }
    1.30 @@ -773,7 +781,7 @@
    1.31  			# Full output
    1.32  			$output .= $cl->{"output"};
    1.33  		}	
    1.34 -		$output .= "^C\n" if ($cl->{"err"} eq "130");
    1.35 +		#$output .= "^C\n" if ($cl->{"err"} eq "130");
    1.36  
    1.37  
    1.38  		# Совместимость с labmaker
    1.39 @@ -825,6 +833,16 @@
    1.40  				printq(\*OUT,${$Diffs{$cl->{"diff"}}}{"text"});
    1.41  			print OUT "</diff>\n";
    1.42  		}
    1.43 +		if ($cl->{"note"}) {
    1.44 +			print OUT "<note>";
    1.45 +				printq(\*OUT,$cl->{"note"});
    1.46 +			print OUT "</note>\n";
    1.47 +		}
    1.48 +		if ($cl->{"note_title"}) {
    1.49 +			print OUT "<note_title>";
    1.50 +				printq(\*OUT,$cl->{"note_title"});
    1.51 +			print OUT "</note_title>\n";
    1.52 +		}
    1.53  		print OUT "</command>\n";
    1.54  
    1.55  	}
     2.1 --- a/l3-cgi	Tue Nov 08 12:16:20 2005 +0200
     2.2 +++ b/l3-cgi	Fri Nov 11 21:29:49 2005 +0200
     2.3 @@ -72,6 +72,13 @@
     2.4  		print "Template to load files: ".$l3config::Config{"path_classes"}."*".$l3config::Config{"class_suffix"}."\n"
     2.5  	}
     2.6  }
     2.7 +elsif ($ENV{PATH_INFO} eq "/current") {
     2.8 +	open (FRONTEND, "./l3-frontend --output - --show_comments no |");
     2.9 +	while (<FRONTEND>) {
    2.10 +		print;
    2.11 +	}
    2.12 +	close(FRONTEND);
    2.13 +}
    2.14  else {
    2.15  	# Вызов производится по URL
    2.16  	my ($skip, $course, $host, $user) = split /\//,$ENV{PATH_INFO},4;
    2.17 @@ -88,6 +95,7 @@
    2.18  		"--course-code"		=>	$XMLTraining->{course},
    2.19  		"--course-date"		=>	$XMLTraining->{date},
    2.20  		"--backend_datafile"	=>	"/var/lilalo/lablogs-xml/$course/$host/$user.xml",
    2.21 +		"--encoding"		=>	$XMLTraining->{host}->{$host}->{charset},
    2.22  	);
    2.23  
    2.24  	open (FRONTEND, "./l3-frontend ".join(" ",map("\"$_\"",@args))." |");
     3.1 --- a/l3-frontend	Tue Nov 08 12:16:20 2005 +0200
     3.2 +++ b/l3-frontend	Fri Nov 11 21:29:49 2005 +0200
     3.3 @@ -1,9 +1,15 @@
     3.4  #!/usr/bin/perl -w
     3.5  
     3.6 +use IO::Socket;
     3.7  use lib '.';
     3.8  use l3config;
     3.9 +use locale;
    3.10  
    3.11  our @Command_Lines;
    3.12 +our @Command_Lines_Index;
    3.13 +our %Commands_Description;
    3.14 +our %Args_Description;
    3.15 +our $Mywi_Socket;
    3.16  
    3.17  # vvv Инициализация переменных выполняется процедурой init_variables
    3.18  our @Day_Name;
    3.19 @@ -13,12 +19,18 @@
    3.20  our %Elements_Visibility;
    3.21  # ^^^
    3.22  
    3.23 +our %Stat;
    3.24 +our %CommandsFDistribution;	# Сколько раз в журнале встречается какая команда
    3.25 +
    3.26  sub search_buy;
    3.27  sub make_comment;
    3.28  sub load_command_lines_from_xml;
    3.29  sub print_command_lines;
    3.30 +sub sort_command_lines;
    3.31 +sub process_command_lines;
    3.32  sub init_variables;
    3.33  sub main;
    3.34 +sub collapse_list($);
    3.35  
    3.36  main();
    3.37  
    3.38 @@ -29,8 +41,12 @@
    3.39  	init_variables();
    3.40  	init_config();
    3.41  
    3.42 +	open_mywi_socket;
    3.43  	load_command_lines_from_xml($Config{"backend_datafile"});
    3.44 +	sort_command_lines;
    3.45 +	process_command_lines;
    3.46  	print_command_lines($Config{"output"});
    3.47 +	close_mywi_socket;
    3.48  }
    3.49  
    3.50  
    3.51 @@ -44,62 +60,139 @@
    3.52  				$Search_Machines{$sm}->{"icon"}."' border='0'/></a>";
    3.53  }
    3.54  
    3.55 +sub extract_from_cline
    3.56 +# Разобрать командную строку $_[1] и возвратить хэш, содержащий 
    3.57 +# номер первого появление команды в строке:
    3.58 +# 	команда => первая позиция
    3.59 +{
    3.60 +	my $what = $_[0];
    3.61 +	my $cline = $_[1];
    3.62 +	my @lists = split /\;/, $cline;
    3.63 +	
    3.64 +	
    3.65 +	my @commands = ();
    3.66 +	for my $list (@lists) {
    3.67 +		push @commands, split /\|/, $list;
    3.68 +	}
    3.69 +
    3.70 +	my %commands;
    3.71 +	my %args;
    3.72 +	my $i=0;
    3.73 +	for my $command (@commands) {
    3.74 +		$command =~ s@^\s*\S+/@@;
    3.75 +		$command =~ /\s*(\S+)\s*(.*)/;
    3.76 +		if ($1 && $1 eq "sudo" ) {
    3.77 +			$commands{"$1"}=$i++;
    3.78 +			$command =~ s/\s*sudo\s+//;
    3.79 +		}
    3.80 +		$command =~ s@^\s*\S+/@@;
    3.81 +		$command =~ /\s*(\S+)\s*(.*)/;
    3.82 +		if ($1 && !defined $commands{"$1"}) {
    3.83 +				$commands{"$1"}=$i++;
    3.84 +		};	
    3.85 +		if ($2) {
    3.86 +			my $args = $2;
    3.87 +			my @args = split (/\s+/, $args);
    3.88 +			for my $a (@args) {
    3.89 +				$args{"$a"}=$i++
    3.90 +					if !defined $args{"$a"};
    3.91 +			};	
    3.92 +
    3.93 +				
    3.94 +		}
    3.95 +	}
    3.96 +
    3.97 +	if ($what eq "commands") {
    3.98 +		return \%commands;
    3.99 +	} else {
   3.100 +		return \%args;
   3.101 +	}
   3.102 +	
   3.103 +}
   3.104 +
   3.105 +sub open_mywi_socket
   3.106 +{
   3.107 +	$Mywi_Socket = IO::Socket::INET->new(
   3.108 +				PeerAddr => $Config{mywi_server},
   3.109 +				PeerPort => $Config{mywi_port},
   3.110 +				Proto	 => "tcp",
   3.111 +				Type	 => SOCK_STREAM);
   3.112 +}
   3.113 +
   3.114 +sub close_mywi_socket
   3.115 +{
   3.116 +	close ($Mywi_Socket);
   3.117 +}
   3.118 +
   3.119 +
   3.120 +sub mywi_client
   3.121 +{
   3.122 +	my $query = $_[0];
   3.123 +	my $mywi;
   3.124 +
   3.125 +	open_mywi_socket;
   3.126 +	if ($Mywi_Socket) {
   3.127 +		local $| = 1;
   3.128 +		local $/ = "";
   3.129 +		print $Mywi_Socket $query."\n";
   3.130 +		$mywi = <$Mywi_Socket>;
   3.131 +		$mywi = "" if $mywi =~ /nothing app/;
   3.132 +	}
   3.133 +	close_mywi_socket;
   3.134 +	return $mywi;
   3.135 +}
   3.136 +
   3.137  sub make_comment
   3.138  {
   3.139 -	my $commands = $_[0];
   3.140 -	my $files = $_[1];
   3.141 -	chomp $commands;
   3.142 -	chomp $files;
   3.143 -	return if (!$commands && !$files);
   3.144 +	my $cline = $_[0];
   3.145 +	#my $files = $_[1];
   3.146  
   3.147 -	my $comment=""; 
   3.148 +	my @comments=(); 
   3.149 +	my @commands = keys %{extract_from_cline("commands", $cline)};
   3.150 +	my @args = keys %{extract_from_cline("args", $cline)};
   3.151 +	return if (!@commands && !@args);
   3.152 +	#return "commands=".join(" ",@commands)."; files=".join(" ",@files);
   3.153  
   3.154  	# Commands
   3.155 -	for my $command (split /\s+/,$commands) {
   3.156 +	for my $command (@commands) {
   3.157  		$command =~ s/'//g;
   3.158 -		my $description="";
   3.159 -		eval { $description=`/home/devi/bin/mywi-client '$command'`; } ;
   3.160 -		$description = join ("<br>\n", grep(/\([18]\)/, split(/\n/, $description)));
   3.161 -		$description =~ s/.*?-//;
   3.162 -		next if $description =~ /^\s*$/; 
   3.163 -		
   3.164 -		my $query=$command." ".$Config{"keywords"};
   3.165 -		$query =~ s/\ /+/g;
   3.166 -		my $search= 	search_by("opennet",$query).
   3.167 -				search_by("local",$command).
   3.168 -				search_by("google",$query);
   3.169 +		$CommandsFDistribution{$command}++;
   3.170 +		if (!$Commands_Description{$command}) {
   3.171 +			my $mywi;
   3.172 +			$mywi = mywi_client ($command);
   3.173 +			$mywi = join ("\n", grep(/\([18]\)/, split(/\n/, $mywi)));
   3.174 +			$mywi =~ s/\s+/ /;
   3.175 +			if ($mywi !~ /^\s*$/) {
   3.176 +				$Commands_Description{$command} = $mywi;
   3.177 +			}
   3.178 +			else {
   3.179 +				next;
   3.180 +			}
   3.181 +		}
   3.182  
   3.183 -		$comment .=     "<tr><td class='note_title'>$command</td>".
   3.184 -				"<td class='note_search'>$search</td>".
   3.185 -				"</tr><tr><td width='100%' colspan='2' class='note_text'>".
   3.186 -				"$description</td></tr><tr/>";
   3.187 +		push @comments, $Commands_Description{$command};
   3.188  	}
   3.189 +	return join("&#10;\n", @comments);
   3.190  	
   3.191  	# Files
   3.192 -	for my $file (split /\s+/,$files) {
   3.193 -		$file =~ s@.*/@@;
   3.194 -		$file =~ s/'//g;
   3.195 -		next if $file =~ /^\s*$/;
   3.196 -		next if $file =~ /^-/;
   3.197 -		
   3.198 -		my $description=`mywi '$file'`;
   3.199 -		$description = join ("<br>\n", grep(/\(5\)/, split(/\n/, $description)));
   3.200 -		next if $description =~ /^\s*$/; 
   3.201 +	for my $arg (@args) {
   3.202 +		$arg =~ s/'//g;
   3.203 +		if (!$Args_Description{$arg}) {
   3.204 +			my $mywi;
   3.205 +			$mywi = mywi_client ($arg);
   3.206 +			$mywi = join ("\n", grep(/\([5]\)/, split(/\n/, $mywi)));
   3.207 +			$mywi =~ s/\s+/ /;
   3.208 +			if ($mywi !~ /^\s*$/) {
   3.209 +				$Args_Description{$arg} = $mywi;
   3.210 +			}
   3.211 +			else {
   3.212 +				next;
   3.213 +			}
   3.214 +		}
   3.215  
   3.216 -		my $query=$file." ".$Config{"files_keywords"};
   3.217 -		$query =~ s/\ /+/g;
   3.218 -		my $search= 	search_by("opennet",$query).
   3.219 -				search_by("local",$file).
   3.220 -				search_by("google",$query);
   3.221 -
   3.222 -		$comment .=     "<tr><td class='note_title'>$file</td>".
   3.223 -				"<td class='note_search'>$search</td>".
   3.224 -				"</tr><tr><td width='100%' colspan='2' class='note_text'>".
   3.225 -				"$description</td></tr><tr/>";
   3.226 +		push @comments, $Args_Description{$arg};
   3.227  	}
   3.228  
   3.229 -
   3.230 -	return $comment;
   3.231  }
   3.232  
   3.233  =cut
   3.234 @@ -130,6 +223,123 @@
   3.235  	}
   3.236  }
   3.237  
   3.238 +sub sort_command_lines
   3.239 +{
   3.240 +	# Sort Command_Lines
   3.241 +	# Write Command_Lines to Command_Lines_Index
   3.242 +
   3.243 +	my @index;
   3.244 +	for (my $i=0;$i<=$#Command_Lines;$i++) {
   3.245 +		$index[$i]=$i;
   3.246 +	}
   3.247 +
   3.248 +	@Command_Lines_Index = sort {
   3.249 +		$Command_Lines[$index[$a]]->{"time"} <=> $Command_Lines[$index[$b]]->{"time"}
   3.250 +	} @index;
   3.251 +
   3.252 +}
   3.253 +
   3.254 +sub process_command_lines
   3.255 +{
   3.256 +	for my $i (@Command_Lines_Index) {
   3.257 +
   3.258 +		my $cl = \$Command_Lines[$i];
   3.259 +		@{${$cl}->{"new_commands"}} =();
   3.260 +		@{${$cl}->{"new_files"}} =();
   3.261 +		$$cl->{"class"} = ""; 
   3.262 +
   3.263 +		if ($$cl->{"err"}) {
   3.264 +			$$cl->{"class"}="wrong";
   3.265 +			$$cl->{"class"}="interrupted"
   3.266 +				if ($$cl->{"err"} eq 130);
   3.267 +		}	
   3.268 +		if (!$$cl->{"euid"}) {
   3.269 +			$$cl->{"class"}.="_root";
   3.270 +		}
   3.271 +		
   3.272 +#tab#		my @tab_words=split /\s+/, $$cl->{"output"};
   3.273 +#tab#		my $last_word= $$cl->{"cline"} =~ /(\S*)$/;
   3.274 +#tab#		$last_word =~ s@.*/@@;
   3.275 +#tab#		my $this_is_tab=1;
   3.276 +#tab#
   3.277 +#tab#		if ($last_word && @tab_words >2) {
   3.278 +#tab#			for my $tab_words (@tab_words) {
   3.279 +#tab#				if ($tab_words !~ /^$last_word/) {
   3.280 +#tab#					$this_is_tab=0;
   3.281 +#tab#					last;
   3.282 +#tab#				}
   3.283 +#tab#			}
   3.284 +#tab#		}	
   3.285 +#tab#		$$cl->{"class"}="tab" if $this_is_tab;
   3.286 +		
   3.287 +
   3.288 +#		if ( !$$cl->{"err"}) {
   3.289 +#			# Command does not contain mistakes
   3.290 +#			
   3.291 +#			my %commands = extract_from_cline("commands", ${$cl}->{"cline"});
   3.292 +#			my %files = extract_from_cline("files", ${$cl}->{"cline"});
   3.293 +#
   3.294 +#			# Searching for new commands only
   3.295 +#			for my $command (keys  %commands) {
   3.296 +#				if (!defined $Commands_Stat{$command}) {
   3.297 +#					push @{$$cl->{new_commands}}, $command;
   3.298 +#				}	
   3.299 +#				$Commands_Stat{$command}++;
   3.300 +#			}
   3.301 +#			
   3.302 +#			for my $file (keys  %files) {
   3.303 +#				if (!defined $Files_Stat{$file}) {
   3.304 +#					push @{$$cl->{new_files}}, $file;
   3.305 +#				}	
   3.306 +#				$Files_Stat{$file}++;
   3.307 +#			}
   3.308 +#		}	
   3.309 +
   3.310 +		if ($$cl->{cline}=~ m@cat[^#]*#([\^=v])\s*(.*)@) {
   3.311 +			if ($1 eq "=") {
   3.312 +				$$cl->{"class"} = "note";
   3.313 +				$$cl->{"note"} = $$cl->{"output"};
   3.314 +				$$cl->{"note_title"} = $2;
   3.315 +			}
   3.316 +			else {
   3.317 +				my $j = $i;
   3.318 +				if ($1 eq "^") {
   3.319 +					$j--;
   3.320 +					$j-- while ($j >=0  && $Command_Lines[$j]->{tty} ne $$cl->{tty} || !$Command_Lines[$j]);
   3.321 +				}
   3.322 +				elsif ($1 eq "v") {
   3.323 +					$j++;
   3.324 +					$j++ while ($j <= @Command_Lines  && $Command_Lines[$j]->{tty} ne $$cl->{tty} || !$Command_Lines[$j]);
   3.325 +				}
   3.326 +				$Command_Lines[$j]->{note_title}="$2";
   3.327 +				$Command_Lines[$j]->{note}=$$cl->{output};
   3.328 +				$$cl=0;
   3.329 +			}
   3.330 +		}
   3.331 +		elsif ($$cl->{cline}=~ /#([\^=v])(.*)/) {
   3.332 +			if ($1 eq "=") {
   3.333 +				$$cl->{"class"} = "note";
   3.334 +				$$cl->{"note"} = $2;
   3.335 +			}
   3.336 +			else {
   3.337 +				my $j=$i;
   3.338 +				if ($1 eq "^") {
   3.339 +					$j--;
   3.340 +					$j-- while ($j >=0  && (!$Command_Lines[$j] || $Command_Lines[$j]->{tty} ne $$cl->{tty}));
   3.341 +				}
   3.342 +				elsif ($1 eq "v") {
   3.343 +					$j++;
   3.344 +					$j++ while ($j <= @Command_Lines  && $Command_Lines[$j]->{tty} ne $$cl->{tty} || !$Command_Lines[$j]);
   3.345 +				}
   3.346 +				$Command_Lines[$j]->{note}="$2";
   3.347 +				$$cl=0;
   3.348 +			}
   3.349 +		}
   3.350 +	}	
   3.351 +
   3.352 +}
   3.353 +
   3.354 +
   3.355  =cut
   3.356  Процедура print_command_lines выводит HTML-представление
   3.357  разобранного lab-скрипта. 
   3.358 @@ -152,7 +362,8 @@
   3.359  	# Результат выполнения процедуры равен 
   3.360  	# join("", @Result{header,body,stat,help,about,footer})
   3.361  	my %Result;
   3.362 -	my $toc ="";  # Хранит оглавление по дням
   3.363 +	my @toc;  # Хранит оглавление
   3.364 +	my $note_number=0;
   3.365  
   3.366  	$Result{"body"} = "<table width='100%'>\n";
   3.367  	
   3.368 @@ -161,7 +372,12 @@
   3.369  	my $last_day="";
   3.370  	my $in_range=0;
   3.371  
   3.372 -	for my $cl (@Command_Lines) {
   3.373 +	my $i=0;
   3.374 +	for my $k (@Command_Lines_Index) {
   3.375 +
   3.376 +		my $cl=$Command_Lines[$Command_Lines_Index[$i++]];
   3.377 +		
   3.378 +		next unless $cl;
   3.379  
   3.380  		if ($Config{"from"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"from"}/) {
   3.381  			$in_range=1;
   3.382 @@ -182,6 +398,20 @@
   3.383  		#my @new_commands=@{$cl->{"new_commands"}};
   3.384  		#my @new_files=@{$cl->{"new_files"}};
   3.385  
   3.386 +		if ($cl->{class} eq "note") {
   3.387 +			my $note = $cl->{note};
   3.388 +			$note = join ("\n", map ("<p>$_</p>", split (/-\n/, $note)));
   3.389 +			$Result{"body"} .= "<tr><td colspan='6'>";
   3.390 +			$Result{"body"} .= "<h4 id='note$note_number'>".$cl->{note_title}."</h4>" if $cl->{note_title};
   3.391 +			$Result{"body"} .= "".$note."<p/><p/></td></td>";
   3.392 +
   3.393 +			if ($cl->{note_title}) {
   3.394 +				push @{$toc[@toc]},"<a href='#note$note_number'>".$cl->{note_title}."</a>";
   3.395 +				$note_number++;
   3.396 +			}
   3.397 +			next;
   3.398 +		}
   3.399 +
   3.400  		my $cl_class="cline";
   3.401  		my $out_class="output";
   3.402  		if ($cl->{"class"}) {
   3.403 @@ -233,6 +463,14 @@
   3.404  		# <command>
   3.405  
   3.406  		my ($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = localtime($cl->{time});
   3.407 +		$Stat{FirstCommand} = $cl->{time} unless $Stat{FirstCommand};
   3.408 +		$Stat{LastCommand} = 0 unless defined $Stat{LastCommand};	
   3.409 +		$Stat{TotalTime} += $cl->{time} - $Stat{LastCommand}
   3.410 +			if $cl->{time} - $Stat{LastCommand} < $Config{stat_inactivity_interval};
   3.411 +		$Stat{LastCommand} = $cl->{time};
   3.412 +		$Stat{TotalCommands} = 0 unless $Stat{TotalCommands};
   3.413 +		$Stat{TotalCommands}++;
   3.414 +
   3.415  		# Добавляем спереди 0 для удобочитаемости
   3.416  		$min = "0".$min if $min =~ /^.$/;
   3.417  		$hour = "0".$hour if $hour =~ /^.$/;
   3.418 @@ -241,6 +479,8 @@
   3.419  		$class=$cl->{"out_class"};
   3.420  		$class =~ s/output$//;
   3.421  
   3.422 +		$Stat{ErrorCommands}++
   3.423 +			if $class =~ /wrong/;
   3.424  		
   3.425  		$Result{"body"} .= "<tr class='command'>\n";
   3.426  						
   3.427 @@ -249,7 +489,7 @@
   3.428  		if ( $last_day ne $day) {
   3.429  			#$Result{"body"} .= "<td colspan='6'><p></p><h3>День ",$day,"</h4></td></tr><tr>";
   3.430  			$Result{"body"} .= "<td colspan='6'><p></p><h3 id='day$day'>".$Day_Name[$wday]."</h4></td></tr><tr>";
   3.431 -			$toc .= "<li><a href='#day$day'>".$Day_Name[$wday]."</a></li>\n";
   3.432 +			push @toc, "<a href='#day$day'>".$Day_Name[$wday]."</a>\n";
   3.433  			$last_day=$day;
   3.434  		}
   3.435  
   3.436 @@ -271,9 +511,12 @@
   3.437  		# COMMAND
   3.438  		$Result{"body"} .= "<td class='script'>\n";
   3.439  		$Result{"body"} .= "<pre class='${class}cline'>\n";
   3.440 -		my $cline = $cl->{"cline"};
   3.441 +		my $cline = $cl->{"prompt"}.$cl->{"cline"};
   3.442  		$cline =~ s/\n//;
   3.443 -		$Result{"body"} .= $cl->{"prompt"}.$cl->{"cline"};
   3.444 +		my $hint = make_comment($cl->{"cline"});
   3.445 +	#	join(" ",@new_commands), join (" ",@new_files));
   3.446 +		$cline = "<div title='$hint'>$cline</div>" if $hint;
   3.447 +		$Result{"body"} .= $cline;
   3.448  		$Result{"body"} .= "</pre>\n";
   3.449  
   3.450  		my $last_command = $cl->{"last_command"};
   3.451 @@ -294,10 +537,22 @@
   3.452  			$Result{"body"} .= $cl->{"diff"};
   3.453  			$Result{"body"} .= "</pre></td></tr></table>";
   3.454  		}
   3.455 +		
   3.456 +		#NOTES
   3.457 +		if ( $Config{"show_notes"} =~ /^y/i && $cl->{"note"}) {
   3.458 +			my $note=$cl->{"note"};
   3.459 +			$note =~ s/\n/<br\/>\n/msg;
   3.460 +		#	Ширину пока не используем
   3.461 +		#	$Result{"body"} .= "<table width='$Config{note_width}' class='note'>";
   3.462 +			$Result{"body"} .= "<table class='note'>";
   3.463 +			$Result{"body"} .= "<tr><td class='note_title'>".$cl->{note_title}."</td></tr>" if $cl->{note_title};
   3.464 +			$Result{"body"} .= "<tr><td width='100%' class='note_text'>".$note."</td></tr>";
   3.465 +			$Result{"body"} .= "</table>\n";
   3.466 +		}
   3.467  
   3.468  		# COMMENT
   3.469  		if ( $Config{"show_comments"} =~ /^y/i) {
   3.470 -			my $comment = make_comment(join(" ",@new_commands), join (" ",@new_files));
   3.471 +			my $comment = make_comment($cl->{"cline"});
   3.472  			if ($comment) {
   3.473  				$Result{"body"} .= "<table width='$Config{comment_width}'>".
   3.474  						"<tr><td width='5'/><td>";
   3.475 @@ -315,18 +570,100 @@
   3.476  
   3.477  	$Result{"body"} .= "</table>\n";
   3.478  
   3.479 -	$Result{"stat"} = "<hr/>";
   3.480 +	#$Result{"stat"} = "<hr/>";
   3.481 +
   3.482 +	%StatNames = (
   3.483 +		FirstCommand => "Время первой команды журнала",
   3.484 +		LastCommand => "Время последней команды журнала",
   3.485 +		TotalCommands => "Количество командных строк в журнале",
   3.486 +		ErrorsPercentage => "Процент команд с кодом ненулевым кодом завершения, %",
   3.487 +		TotalTime => "Суммарное время работы с терминалом <sup><font size='-2'>*</font></sup>, час",
   3.488 +		CommandsPerTime => "Количество командных строк в единицу времени, команда/мин",
   3.489 +		CommandsFDistribution => "Частота использования команд",
   3.490 +		CommandsFDistribution => "Частота использования команд",
   3.491 +		RareCommands	=> "Частота использования этих команд < 0.5%",
   3.492 +	);
   3.493 +	@StatOrder = (
   3.494 +		FirstCommand,
   3.495 +		LastCommand,
   3.496 +		TotalCommands,
   3.497 +		ErrorsPercentage,
   3.498 +		TotalTime,
   3.499 +		CommandsPerTime,
   3.500 +		CommandsFDistribution,
   3.501 +		RareCommands,
   3.502 +	);
   3.503 +
   3.504 +	# Подготовка статистики к выводу
   3.505 +	# Некоторые значения пересчитываются!
   3.506 +	# Дальше их лучше уже не использовать!!!
   3.507 +
   3.508 +	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($Stat{FirstCommand});
   3.509 +	$Stat{FirstCommand} = sprintf "%02i:%02i:%02i %04i-%2i-%2i", $hour, $min, $sec,  $year+1900, $mon+1, $mday;
   3.510 +	($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($Stat{LastCommand});
   3.511 +	$Stat{LastCommand} = sprintf "%02i:%02i:%02i %04i-%2i-%2i", $hour, $min, $sec,  $year+1900, $mon+1, $mday;
   3.512 +	$Stat{ErrorsPercentage} = sprintf "%5.2f", $Stat{ErrorCommands}*100/$Stat{TotalCommands}	
   3.513 +		if $Stat{TotalCommands};
   3.514 +	$Stat{CommandsPerTime} = sprintf "%5.2f", $Stat{TotalCommands}*60/$Stat{TotalTime}
   3.515 +		if $Stat{TotalTime};
   3.516 +	$Stat{TotalTime} = sprintf "%5.2f", $Stat{TotalTime}/60/24;
   3.517 +
   3.518 +	my $total_commands=0;
   3.519 +	for $command (keys %CommandsFDistribution){
   3.520 +		$total_commands += $CommandsFDistribution{$command};
   3.521 +	}
   3.522 +	if ($total_commands) {
   3.523 +		for $command (reverse sort {$CommandsFDistribution{$a} <=> $CommandsFDistribution{$b}} keys %CommandsFDistribution){
   3.524 +			my $percentage = sprintf "%5.2f",$CommandsFDistribution{$command}*100/$total_commands;
   3.525 +			if ($percentage < 0.5) {
   3.526 +				my $hint = make_comment($command);
   3.527 +				my $command_html = "$command";
   3.528 +				$command_html = "<span title='$hint' class='hint'>$command_html</span>" if $hint;
   3.529 +				my $command_html = "<tt>$command_html</tt>";
   3.530 +				$Stat{RareCommands} .= $command_html."<sub><font size='-2'>".$CommandsFDistribution{$command}."</font></sub> , ";
   3.531 +			}
   3.532 +			else {
   3.533 +				my $hint = make_comment($command);
   3.534 +				my $command_html = "$command";
   3.535 +				$command_html = "<span title='$hint' class='hint'>$command_html</span>" if $hint;
   3.536 +				my $command_html = "<tt>$command_html</tt>";
   3.537 +				$percentage = sprintf "%5.2f",$percentage;
   3.538 +				$Stat{CommandsFDistribution} .= "<tr><td>".$command_html."</td><td>".$CommandsFDistribution{$command}."</td>".
   3.539 +					"<td>|".("="x int($CommandsFDistribution{$command}*100/$total_commands))."| $percentage%</td></tr>";
   3.540 +			}
   3.541 +		}
   3.542 +		$Stat{CommandsFDistribution} = "<table>".$Stat{CommandsFDistribution}."</table>";
   3.543 +		$Stat{RareCommands} =~ s/, $//;
   3.544 +	}
   3.545 +
   3.546  	$Result{"stat"} .= "<h2 id='stat'>Статистика</h2>";
   3.547 -	$Result{"stat"} .= "Статистическая информация о журнале<br/>";
   3.548 -	$Result{"help"} .= "<hr/>";
   3.549 +	$Result{"stat"} .= "<table>";
   3.550 +	for my $stat (@StatOrder) {
   3.551 +	$Result{"stat"} .= "<tr valign='top'><td width='300'>".$StatNames{"$stat"}."</td><td>".$Stat{"$stat"}."</td></tr>";
   3.552 +	}
   3.553 +	
   3.554 +	$Result{"stat"} .= "</table>";
   3.555 +	$Result{"stat"} .= "<font size='-2'>____<br/>*) Интервалы неактивности длительностью ".($Config{stat_inactivity_interval}/60)." минут и более не учитываются</font></br>";
   3.556 +
   3.557 +	#$Result{"help"} .= "<hr/>";
   3.558  	$Result{"help"} .= "<h2 id='help'>Справка</h2>";
   3.559  	$Result{"help"} .= "$Html_Help<br/>";
   3.560 -	$Result{"about"} .= "<hr/>";
   3.561 +	#$Result{"about"} .= "<hr/>";
   3.562  	$Result{"about"} .= "<h2 id='about'>О программе</h2>";
   3.563  	$Result{"about"} .= "$Html_About";
   3.564  	$Result{"footer"} .= "</body>\n";
   3.565  	$Result{"footer"} .= "</html>\n";
   3.566  
   3.567 +	$Result{"title"} = "Журнал лабораторных работ";
   3.568 +	$Result{"title"}.= " -- ".$course_student if $course_student;
   3.569 +	if ($course_date) {
   3.570 +		$Result{"title"}.= " -- ".$course_date; 
   3.571 +		$Result{"title"}.= "/".$course_code if $course_code;
   3.572 +	}
   3.573 +	else {
   3.574 +		$Result{"title"}.= " -- ".$course_code if $course_code;
   3.575 +	}
   3.576 +
   3.577  	# Заголовок генерируется позже всего
   3.578  	# Тогда, когда известно уже, что должно быть написано в 
   3.579  	# оглавлении
   3.580 @@ -335,21 +672,27 @@
   3.581  	<head>
   3.582  	<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
   3.583  	<link rel='stylesheet' href='$Config{frontend_css}' type='text/css'/>
   3.584 +	<title>$Result{title}</title>
   3.585  	</head>
   3.586  	<body>
   3.587  	<script>
   3.588  	$Html_JavaScript
   3.589  	</script>
   3.590 -	<h2>Журнал лабораторных работ</h2>
   3.591 +	<h1>Журнал лабораторных работ</h1>
   3.592  
   3.593 -	<p>
   3.594 -	Выполнил $course_student<br/>
   3.595 -	Проверил $course_trainer <br/>
   3.596 -	Курс $course_name ($course_code),
   3.597 -	$course_date<br/>
   3.598 -	Учебный центр $course_center <br/>
   3.599 -	</p>
   3.600 +HEADER
   3.601 +	$Result{"header"} .= "<p>" if $course_student || $course_trainer || $course_name || $course_code || $course_date || $course_center;
   3.602 +	$Result{"header"} .= "Выполнил $course_student<br/>" if $course_student;
   3.603 +	$Result{"header"} .= "Проверил $course_trainer <br/>" if $course_trainer;
   3.604 +	$Result{"header"} .= "Курс " if $course_name || $course_code || $course_date;
   3.605 +	$Result{"header"} .= "$course_name " if $course_name;
   3.606 +	$Result{"header"} .= "($course_code)" if $course_code;
   3.607 +	$Result{"header"} .= ", $course_date<br/>" if $course_date;
   3.608 +	$Result{"header"} .= "Учебный центр $course_center <br/>" if $course_center;
   3.609 +	$Result{"header"} .= "</p>" if $course_student || $course_trainer || $course_name || $course_code || $course_date || $course_center;
   3.610  
   3.611 +	my $toc = collapse_list (\@toc);
   3.612 +	$Result{"header"} .= <<HEADER;
   3.613  	<ul>
   3.614  		<li><a href='#log'>Журнал</a></li>
   3.615  		<ul>$toc</ul>
   3.616 @@ -360,7 +703,7 @@
   3.617  
   3.618  	<h2 id="log">Журнал</h2>
   3.619  HEADER
   3.620 -	$Result{"header"} .= "<table class='visibility_form'><tr><td><form>\n";
   3.621 +	$Result{"header"} .= "<table id='visibility_form' class='visibility_form'><tr><td><form>\n";
   3.622  	for my $element (keys %Elements_Visibility)
   3.623  	{
   3.624  		my @e = split /\s+/, $element;
   3.625 @@ -385,13 +728,205 @@
   3.626  
   3.627  
   3.628  
   3.629 +sub collapse_list($)
   3.630 +{
   3.631 +	my $res = "";
   3.632 +	for my $elem (@{$_[0]}) {
   3.633 +		if (ref $elem eq "ARRAY") {
   3.634 +			$res .= "<ul>".collapse_list($elem)."</ul>";
   3.635 +		}
   3.636 +		else
   3.637 +		{
   3.638 +			$res .= "<li>".$elem."</li>";
   3.639 +		}
   3.640 +	}
   3.641 +	return $res;
   3.642 +}
   3.643 +
   3.644  
   3.645  
   3.646  
   3.647  sub init_variables
   3.648  {
   3.649  $Html_Help = <<HELP;
   3.650 -	Справка по использованию журнала
   3.651 +	Для того чтобы использовать LiLaLo, не нужно знать ничего особенного:
   3.652 +	всё происходит само собой.
   3.653 +	Однако, чтобы ведение и последующее использование журналов
   3.654 +	было как можно более эффективным, желательно иметь в виду следующее:
   3.655 +	<ul>
   3.656 +	<li><p> 
   3.657 +	В журнал автоматически попадают все команды, данные в любом терминале системы.
   3.658 +	</p></li>
   3.659 +	<li><p>
   3.660 +	Для того чтобы убедиться, что журнал на текущем терминале ведётся, 
   3.661 +	и команды записываются, дайте команду w.
   3.662 +	В поле WHAT, соответствующем текущему терминалу, 
   3.663 +	должна быть указана программа script.
   3.664 +	</p></li>
   3.665 +	<li><p>
   3.666 +	Команды, код завершения которых отличен от нуля, выделяются цветом.
   3.667 +	Если код завершения команды равен нулю, 
   3.668 +	команда была выполнена без ошибок.
   3.669 +<table>
   3.670 +<tr class='command'>
   3.671 +<td class='script'>
   3.672 +<pre class='wrong_cline'>
   3.673 +\$ l s-l</pre>
   3.674 +<pre class='wrong_output'>bash: l: command not found
   3.675 +</pre>
   3.676 +</td>
   3.677 +</tr>
   3.678 +</table>
   3.679 +<br/>
   3.680 +	</p></li>
   3.681 +	<li><p>
   3.682 +	Команды, ход выполнения которых был прерван пользователем, выделяются цветом.
   3.683 +<table>
   3.684 +<tr class='command'>
   3.685 +<td class='script'>
   3.686 +<pre class='interrupted_cline'>
   3.687 +\$ find / -name abc</pre>
   3.688 +<pre class='interrupted_output'>find: /home/devi-orig/.gnome2: Keine Berechtigung
   3.689 +find: /home/devi-orig/.gnome2_private: Keine Berechtigung
   3.690 +find: /home/devi-orig/.nautilus/metafiles: Keine Berechtigung
   3.691 +find: /home/devi-orig/.metacity: Keine Berechtigung
   3.692 +find: /home/devi-orig/.inkscape: Keine Berechtigung
   3.693 +^C
   3.694 +</pre>
   3.695 +</td>
   3.696 +</tr>
   3.697 +</table>
   3.698 +<br/>
   3.699 +	</p></li>
   3.700 +	<li><p>
   3.701 +	Команды, выполненные с привилегиями суперпользователя,
   3.702 +	выделяются слева красной чертой.
   3.703 +<table>
   3.704 +<tr class='command'>
   3.705 +<td class='script'>
   3.706 +<pre class='_root_cline'>
   3.707 +# id</pre>
   3.708 +<pre class='_root_output'>
   3.709 +uid=0(root) gid=0(root) Gruppen=0(root)
   3.710 +</pre>
   3.711 +</td>
   3.712 +</tr>
   3.713 +</table>
   3.714 +	<br/>
   3.715 +	</p></li>
   3.716 +	<li><p>
   3.717 +	Изменения, внесённые в текстовый файл с помощью редактора, 
   3.718 +	запоминаются и показываются в журнале в формате ed.
   3.719 +	Строки, начинающиеся символом "&lt;", удалены, а строки,
   3.720 +	начинающиеся символом "&gt;" -- добавлены.
   3.721 +<table>
   3.722 +<tr class='command'>
   3.723 +<td class='script'>
   3.724 +<pre class='cline'>
   3.725 +\$ vi ~/.bashrc</pre>
   3.726 +<table><tr><td width='5'/><td class='diff'><pre>2a3,5
   3.727 +&gt; 	if [ -f /usr/local/etc/bash_completion ]; then
   3.728 +&gt;         . /usr/local/etc/bash_completion
   3.729 +&gt;     	fi
   3.730 +</pre></td></tr></table></td>
   3.731 +</tr>
   3.732 +</table>
   3.733 +	<br/>
   3.734 +	</p></li>
   3.735 +	<li><p>
   3.736 +	Для того чтобы получить краткую справочную информацию о команде, 
   3.737 +	нужно подвести к ней мышь. Во всплывающей подсказке появится краткое
   3.738 +	описание команды.
   3.739 +	</p></li>
   3.740 +	<li><p>
   3.741 +	Время ввода команды, показанное в журнале, соответствует времени 
   3.742 +	<i>начала ввода командной строки</i>, которое равно тому моменту, 
   3.743 +	когда на терминале появилось приглашение интерпретатора
   3.744 +	</p></li>
   3.745 +	<li><p>
   3.746 +	Имя терминала, на котором была введена команда, показано в специальном блоке.
   3.747 +	Этот блок показывается только в том случае, если терминал
   3.748 +	текущей команды отличается от терминала предыдущей.
   3.749 +	</p></li>
   3.750 +	<li><p>
   3.751 +	Вывод не интересующих вас в настоящий момент элементов журнала,
   3.752 +	таких как время, имя терминала и других, можно отключить.
   3.753 +	Для этого нужно воспользоваться <a href='#visibility_form'>формой управления журналом</a>
   3.754 +	вверху страницы.
   3.755 +	</p></li>
   3.756 +	<li><p>
   3.757 +	Небольшие комментарии к командам можно вставлять прямо из командной строки.
   3.758 +	Комментарий вводится прямо в командную строку, после символов #^ или #v.
   3.759 +	Символы ^ и v показывают направление выбора команды, к которой относится комментарий:
   3.760 +	^ - к предыдущей, v - к следующей.
   3.761 +	Например, если в командной строке было введено:
   3.762 +<pre class='cline'>
   3.763 +\$ whoami
   3.764 +</pre>
   3.765 +<pre class='output'>
   3.766 +user
   3.767 +</pre>
   3.768 +<pre class='cline'>
   3.769 +\$ #^ Интересно, кто я?
   3.770 +</pre>
   3.771 +	в журнале это будет выглядеть так:
   3.772 +	
   3.773 +<pre class='cline'>
   3.774 +\$ whoami
   3.775 +</pre>
   3.776 +<pre class='output'>
   3.777 +user
   3.778 +</pre>
   3.779 +<table class='note'><tr><td width='100%' class='note_text'>
   3.780 +<tr> <td> Интересно, кто я?<br/> </td></tr></table> 
   3.781 +	</p></li>
   3.782 +	<li><p>
   3.783 +	Если комментарий содержит несколько строк,
   3.784 +	его можно вставить в журнал следующим образом:
   3.785 +<pre class='cline'>
   3.786 +\$ whoami
   3.787 +</pre>
   3.788 +<pre class='output'>
   3.789 +user
   3.790 +</pre>
   3.791 +<pre class='cline'>
   3.792 +\$ cat > /dev/null #^ Интересно, кто я?
   3.793 +</pre>
   3.794 +<pre class='output'>
   3.795 +Программа whoami выводит имя пользователя, под которым 
   3.796 +мы зарегистрировались в системе.
   3.797 +-
   3.798 +Она не может ответить на вопрос о нашем назначении 
   3.799 +в этом мире.
   3.800 +</pre>
   3.801 +	В журнале это будет выглядеть так:
   3.802 +<table>
   3.803 +<tr class='command'>
   3.804 +<td class='script'>
   3.805 +<pre class='cline'>
   3.806 +\$ whoami</pre>
   3.807 +<pre class='output'>user
   3.808 +</pre>
   3.809 +<table class='note'><tr><td class='note_title'>Интересно, кто я?</td></tr><tr><td width='100%' class='note_text'>
   3.810 +Программа whoami выводит имя пользователя, под которым<br/>
   3.811 +мы зарегистрировались в системе.<br/>
   3.812 +<br/>
   3.813 +Она не может ответить на вопрос о нашем назначении<br/>
   3.814 +в этом мире.<br/>
   3.815 +</td></tr></table>
   3.816 +</td>
   3.817 +</tr>
   3.818 +</table>
   3.819 +	Для разделения нескольких абзацев между собой
   3.820 +	используйте символ "-", один в строке.
   3.821 +	<br/>
   3.822 +</p></li>
   3.823 +	<li><p>
   3.824 +	Комментарии, не относящиеся непосредственно ни к какой из команд, 
   3.825 +	добавляются точно таким же способом, только вместо симолов #^ или #v 
   3.826 +	нужно использовать символы #=
   3.827 +	</p></li>
   3.828 +</ul>
   3.829  HELP
   3.830  
   3.831  $Html_About = <<ABOUT;
     4.1 --- a/l3config.pm	Tue Nov 08 12:16:20 2005 +0200
     4.2 +++ b/l3config.pm	Fri Nov 11 21:29:49 2005 +0200
     4.3 @@ -22,18 +22,19 @@
     4.4  		"suppress_pagers"		=>	"yes",
     4.5  		"suppress_terminal"		=>	"yes",
     4.6  
     4.7 -		"terminal_width"		=> 	100,
     4.8 +		"terminal_width"		=> 	400,
     4.9  		"terminal_height"		=> 	100,
    4.10  		"verbose"			=>	"yes",
    4.11  
    4.12  		"head_lines"			=> 	5,
    4.13  		"tail_lines"			=>	5,
    4.14 -		"cache_head_lines"			=> 	5,
    4.15 -		"cache_tail_lines"			=>	5,
    4.16 +		"cache_head_lines"			=> 	50,
    4.17 +		"cache_tail_lines"			=>	50,
    4.18  		"skip_text"			=>	"...",
    4.19  		"show_time"			=>	"yes",
    4.20  		"show_diffs"			=>	"yes",
    4.21  		"show_comments"			=>	"yes",
    4.22 +		"show_notes"			=>	"yes",
    4.23  
    4.24  		"input"				=>	"/root/.labmaker",
    4.25  		"diffs"				=>	"",
    4.26 @@ -54,6 +55,11 @@
    4.27  		"frontend_opennet_ico"		=>	"/l3/opennet.ico",
    4.28  		"frontend_local_ico"		=>	"/l3/freebsd.ico",
    4.29  
    4.30 +		"mywi_server"			=>	"127.0.0.1",
    4.31 +		"mywi_port"			=> 	"19800",
    4.32 +
    4.33 +		"stat_inactivity_interval"	=>	"1800",
    4.34 +		
    4.35  		"signature"			=>	"#lm:",
    4.36  		"from"				=>	"",
    4.37  		"to"				=>	"",
    4.38 @@ -62,6 +68,7 @@
    4.39  		"files_keywords"		=>	"linux file",
    4.40  		
    4.41  		comment_width			=>	"300",
    4.42 +		note_width			=>	"500",
    4.43  		time_width			=>	"60",
    4.44  		
    4.45  		"mode"				=>	"daemon",		# daemon | normal