#!/usr/bin/perl
##########################################################################
# onis 0.8.2 2005-06-07 #
#---=============--------------------------------------------------------#
# Language: Perl #
# Purpose: Generating statistics #
# Input: IRC-Logfiles #
# Output: One HTML file #
# Version: 0.8.2 (unstable) #
# License: GPL #
# Homepage: http://verplant.org/onis/ #
# Authors: Florian octo Forster <octo@verplant.org> #
# Contributions are listed in THANKS #
##########################################################################
BEGIN
{
if ($0 =~ m#^(.*)[/\\]#) { chdir ($1); }
unshift (@INC, 'lib');
# 0x0010 Language (make not-translated lines red/yellow)
# 0x0020 Parser (dropped lines)
# 0x0040 Parser (time information)
# 0x0100 Data::Core (host unsharp)
# 0x0200 Data::Persistent
# 0x0400 Data::Core (dump incoming data to stderr)
# 0x0800 Data::Core (initializing)
# 0x1000 Onis::Users
$::DEBUG = 0x0000;
}
use strict;
use warnings;
use vars qw/$VERSION $REVISION/;
use Onis::Config qw/get_config parse_argv read_config/;
use File::Basename qw/dirname/;
use Fcntl qw/:flock/;
=head1 NAME
onis - onis not irs stats
=head1 SYNOPSIS
B<onis> [I<options>] I<logfile>...
=head1 DESCRIPTION
onis is a script that converts IRC logfiles into an HTML statistics page. It
provides information about daily channel usage, user activity, and channel
trivia. It provides a configurable customization and supports Dancer,
dircproxy, eggdrop, irssi, mIRC, and XChat logs. Persistent data (history
files) and automatic log purging make onis applicable for a large number of
logfiles. It also features a powerful translation infrastructure.
=cut
$VERSION = '0.8.2';
$REVISION = '$LastChangedRevision: 121 $';
if (!$VERSION)
{
$VERSION = $REVISION;
$VERSION =~ s/^\D*(\d+).*/r$1/;
}
print STDERR $/, __FILE__, ': $Id: onis 121 2005-06-07 19:42:47Z octo $' if ($::DEBUG);
our $FileInfo;
our $PurgeLogs = 0;
parse_argv (@ARGV);
read_config (get_config ('config') ? get_config ('config') : 'onis.conf');
read_config (scalar get_config ('theme')) if (get_config ('theme'));
my $output = get_config ('output');
if (!$output)
{
$output = "reports/onis.html";
}
foreach ('Core', get_config ('plugin'))
{
my $module = ucfirst (lc ($_));
require "Onis/Plugins/$module.pm";
}
if (!get_config ('input'))
{
print STDERR <<EOF;
Usage: $0 [options] <logfile> [logfile logfile ..]
Options:
--config Specify alternate config file
--output <file> Defines the file to write the HTML to.
--overwrite <bool> Overwrites files without prompting.
--channel <channel> Defines the channel's name.
--logtype <type> Defines the logfile's type.
See 'config' for a complete list.
--user <name> Define's the generator's name.
For a full list of all options please read the onis(1) manpage.
EOF
exit (1);
}
if (-e $output)
{
my $overwrite = 0;
if (get_config ('overwrite'))
{
my $tmp = lc (get_config ('overwrite'));
if ($tmp eq 'true' or $tmp eq 'yes' or $tmp eq 'on')
{
$overwrite = 1;
}
}
if (!$overwrite)
{
print STDERR <<MESSAGE;
WARNING: The output file ``$output'' already exists
You can set the ``overwrite'' option in the config
file to disable this dialog.
MESSAGE
print STDERR 'Are you sure you want to overwrite it? [Y|n] ';
my $answer = <STDIN>;
exit (1) if ($answer =~ m/n/i);
}
}
my $logtype = 'Eggdrop';
if (get_config ('logtype'))
{
$logtype = ucfirst (lc (get_config ('logtype')));
}
require "Onis/Parser/$logtype.pm";
require Onis::Parser::Persistent;
require Onis::Data::Persistent;
import Onis::Parser (qw(parse last_date));
import Onis::Parser::Persistent (qw(newfile));
import Onis::Data::Persistent ();
$FileInfo = Onis::Data::Persistent->new ('FileInfo', 'inode', qw(mtime));
if (get_config ('purge_logs'))
{
my $temp = lc (get_config ('purge_logs'));
if (($temp eq 'truncate') or ($temp eq 'shorten'))
{
$PurgeLogs = 1;
}
elsif (($temp eq 'delete') or ($temp eq 'remove')
or ($temp eq 'del'))
{
$PurgeLogs = 2;
}
}
for (get_config ('input'))
{
my $file = $_;
my $logfile;
my $status = 4;
my $position = 0;
my $mtime;
my $size;
my $inode;
($inode, $size, $mtime) = (stat ($file))[1,7,9];
print STDERR $/, $/, __FILE__, " --- New File ``$file'' ---" if ($::DEBUG & 0x200);
if (!defined ($mtime))
{
print STDERR $/, __FILE__, ": Unable to stat file ``$file''";
next;
}
else
{
my ($old_mtime) = $FileInfo->get ($inode);
print STDERR $/, __FILE__, ": ``$file'': " if ($::DEBUG & 0x200);
if (defined ($old_mtime))
{
if ($old_mtime == $mtime)
{
print STDERR "File did not change. Skipping." if ($::DEBUG & 0x200);
next;
}
elsif ($old_mtime < $mtime)
{
print STDERR "File changed. Reading it again." if ($::DEBUG & 0x200);
}
else
{
print STDERR "File ``$file'' is older than expected. There might be a problem!";
}
}
else
{
print STDERR "File appears to be new. Reading it." if ($::DEBUG & 0x200);
}
$FileInfo->put ($inode, $mtime);
}
# truncate
if ($PurgeLogs == 1)
{
unless (open ($logfile, '+< ' . $file))
{
print STDERR $/, __FILE__, ": Unable to open file ``$file'': $!";
next;
}
}
else
{
unless (open ($logfile, '< ' . $file))
{
print STDERR $/, __FILE__, ": Unable to open file ``$file'': $!";
next;
}
}
if ($PurgeLogs)
{
unless (flock ($logfile, LOCK_EX))
{
print STDERR $/, __FILE__, ": Unable to get an exclusive lock for file ``$file'': $!";
close ($logfile);
next;
}
}
else
{
unless (flock ($logfile, LOCK_SH))
{
print STDERR $/, __FILE__, ": Unable to get a shared lock for file ``$file'': $!";
close ($logfile);
next;
}
}
newfile ($inode);
while (<$logfile>)
{
s/\n|\r//g;
$status = parse ($_);
# 0 == rewind file
# 1 == line parsed
# 2 == unable to parse
# 3 == line old
# 4 == don't have date
if ($status == 0)
{
print STDERR $/, __FILE__, ": Rewinding file ``$file''" if ($::DEBUG & 0x200);
seek ($logfile, 0, 0);
$position = 0;
}
elsif (($status == 1) or ($status == 2)
or ($status == 3))
{
$position = tell ($logfile);
}
elsif ($status == 4)
{
# void
}
else
{
print STDERR $/, __FILE__, ": Parser returned unknown status code: ``$status''";
}
}
if ($PurgeLogs and (($status == 1)
or ($status == 2)
or ($status == 3)))
{
if (($PurgeLogs > 1)
#and (($position + 1) >= $size)
)
{
# delete file
print STDERR $/, __FILE__, ": Deleting empty file ``$file''" if ($::DEBUG & 0x200);
close ($logfile);
if (-w $file)
{
unless (unlink ($file))
{
print STDERR $/, __FILE__, ": Unable to delete empty file ``$file'': $!";
}
delete ($FileInfo->{$inode});
}
else
{
print STDERR $/, __FILE__, ": Won't delete ``$file''. Set it to writeable first!";
}
}
else
{
seek ($logfile, 0, 0);
if (truncate ($logfile, 0))
{
print $logfile &last_date ();
print STDERR $/, __FILE__, ": Truncated ``$file''" if ($::DEBUG & 0x200);
}
else
{
print STDERR $/, __FILE__, ": Couldn't truncate file ``$file'': $!";
}
close ($logfile);
}
}
else
{
close ($logfile);
}
}
require Onis::Data::Core;
require Onis::Html;
import Onis::Data::Core qw#print_output#;
import Onis::Html qw#open_file close_file#;
if (open_file ($output))
{
print_output ();
close_file ();
}
else
{
# Fail and make noise! ;)
print STDERR <<MESSAGE;
ERROR: Unable to open output file
The output file ``$output'' could not be opened. Please make sure to set
the permissions right and try again.
MESSAGE
exit (1);
}
exit (0);
END
{
print $/ if ($::DEBUG);
}
=head1 OPTIONS
=head2 Core options
=over 4
=item B<config>: I<file>;
Load the config from this file. B<(command line only)>
=item B<users_config>: I<file>;
Sets the file from which to read the user configuration.
=item B<language_file>: I<file>;
Sets the language file/translation to use.
=item B<plugin>: I<string>;
Sets the plugins to load. The plugin B<Core> will always be loaded.
=item B<input>: I<file>;
Read and parse this file(s). B<(config file only)>
=item B<logtype>: I<string>;
Sets the parser to use for parsing the input file.
=item B<output>: I<file>;
Write the generated output to this file.
=item B<overwrite>: I<bool>;
Sets wether or not to overwrite the output-file if it exists.
=item B<purge_logs>: "I<false>" | "I<truncate>" | "I<delete>";
Sets wether logs should be truncated or even removes after they have been
parsed.
=item B<user>: I<string>;
Sets the user that created the page. Defaults to the environment variable
B<USER> or "onis", if it is not set.
=item B<channel>: I<string>;
Sets the name of the channel being parsed. Normally this is auto-detected.
=item B<unsharp>: "I<none>" | "I<light>" | "I<medium>" | "I<hard>";
Sets how to do unsharping. What each setting actually does is described in the
readme and in L<Onis::Data::Core>.
=back
=head2 Appearance
=over 4
=item B<theme>: I<file>;
Theme file to load.
=item B<stylesheet>: I<file>;
Sets the stylesheet to use. This is included in the HTML-file as-is, so you
have to take care of absolute/relative paths yourself..
=item B<color_codes>: I<bool>;
Wether or not to print the color codes (introduced by mIRC, used by idiots and
ignored by the rest) in the generated HTML-file. Of course this defaults to not
print the codes..
=item B<display_images>: I<bool>;
Sets if user-images should be displayed.
=item B<default_image>: I<file>;
Sets the default image to use if no user-defined image is available.
=item B<display_lines>: "I<none>" | "I<number>" | "I<bar>" | "I<both>";
=item B<display_words>: "I<none>" | "I<number>" | "I<bar>" | "I<both>";
=item B<display_chars>: "I<none>" | "I<number>" | "I<bar>" | "I<both>";
Sets if and how lines, words and/or characters should be displayed.
=item B<sort_by>: "I<lines>" | "I<words>" | "I<chars>";
Sets wether to sort by lines, words or characters written.
=item B<display_times>: I<bool>;
Wether or not to display a fixed width bar that shows when a user is most
active.
=item B<horizontal_images>: I<file>, I<file>, I<file>, I<file>;
=item B<vertical_images>: I<file>, I<file>, I<file>, I<file>;
Sets the images to use for horizontal and vertical bars. This should be used in
the theme-file.
=item B<encoding>: I<string>;
Sets the encoding to include in the HTML-file. If you don't know what this is,
don't change it..
=item B<public_page>: I<bool>;
Wether or not this is a public page. Public pages may be linked on the onis
homepage at some point in the fututre..
=back
=head2 Storage / Persistency
=over 4
=item B<storage_module>: I<string>;
Sets the storage-module to use.
=item B<storage_dir>: I<directory>;
Sets the directory to store persistency information under.
=item B<storage_file>: I<file>;
Sets the file to write persistency data to, if applicable by the
storage-module.
=back
=head2 Plugins
=over 4
=item B<min_word_length>: I<number>;
Substring containing only word-characters needs to be this long to be
considered a word.
=item B<plugin_max>: I<number>;
Sets the number of "most referenced nicks", "most used words" and the like to
be displayed. This option will be removed in the future.
=item B<longlines>: I<number>;
=item B<shortlines>: I<number>;
The number of lines in the big and the small table. While in the big table one
line is dedicated for one person the small table displays six persons per line.
=item B<quote_cache_size>: I<number>;
Sets how many quotes are to be cached for each nick. At the end of the run one
of the quotes in the cache will be chosen at random and displayed.
=item B<quote_min>: I<number>;
=item B<quote_max>: I<number>;
Sets the minimum and maximum length of a quote. Too short quotes may be not
very typical for a person, too long quotes may clutter the layout.
=item B<conversations_number>: I<number>;
=item B<userdetails_conversations_number>: I<number>;
Number of conversations partners to include in the output (or in the
conversations section of the userdetails plugin).
=item B<soliloquies_count>: I<number>;
Sets how many lines without interruption are considered a soliloquy.
=item B<longterm_days>: I<number>;
=item B<userdetails_longterm_days>: I<number>;
Sets the number of days shown in the longterm-plugin (or the longter-section of
the userdetails-plugin).
=item B<ignore_words>: I<number>;
The Words-Plugin will ignore words with less than this characters.
=item B<userdetails_number>: I<number>;
The number of nicks to print userdetails for.
=back
=head1 AUTHOR
Florian Forster E<lt>octo at verplant.orgE<gt>
=cut
syntax highlighted by Code2HTML, v. 0.9.1