#!/usr/bin/perl -w
#
# tcams --crb3 16Feb07/19feb07
#
# collect and graph camera-time values for the chapters of a
# multichapter fic assembled using tpp. 'cameras' are
# designators for character appearance and viewpoint (assigned
# cameras); the idea is to graph character appearances in such a
# way as to help the writer manage them for dramatic flow.
# numbers are first generated in bytecounts, then converted to
# percentage of chapter, and graphed that way in LinePoint
# format.
#
# for starts, tuned to the specific tpp decorations I use. then
# to be generalized, maybe, but count on using tpp's tagged output
# (-t -u).
#
# multicamera options allowed (#cam=hp,nt).
#
# works with Chart-0.99, which uses older GD producing GIFs. There are
# hints throughout as to how to adapt to newer versions which produce
# PNGs.
#
# the -g option causes the script to print image sizes to stdout
# in a raw format suitable for insertion into an <IMG> tag, in,
# say, a tindexer-generated index.
#
use Data::Dumper;
use Chart::LinesPoints;

$cwd=`pwd`;
chomp $cwd;

our($val,$yval,$arb4cam,$xmin,$ymin,$xfudge,$yfudge);

$xval=400;
$yval=180;
$arb4cam=17;
$xmin=400;
$ymin=225;
$xfudge=0;
$yfudge=225;
$do_absolutes=0;

$select='*';                    # string of CSV cameras to display, unless '*'
$subtitle="";
$title="Cameras";
$ftyp='*';
$issub=$istit=$isx=$isy=$chatty=$showit=$debug=0;
while(defined($ARGV[0]) and index($ARGV[0],'-')==0){
  $arg=shift(@ARGV);                    # get any the switches
  $key=substr($arg,1,1);                # get no-arg switches first
  substr($arg,0,2)="";
  if($key eq 'v'){                      # verbose?
    $chatty ^= 1;
    next;
  }elsif($key eq 'S'){                  # show gif once made?
    $showit^=1;
    next;
  }elsif($key eq 'D'){                  # debug
    $debug^=1;
    next;
  }elsif($key eq 'a'){                  # emit byct as well as % graphs?
    $do_absolutes^=1;
    next;
  }elsif($key eq 'g'){                  # print gif sizes to stdout
    $xandy^=1;
    next;
  }elsif($key eq 'h' or $key eq '?'){   # show helps and leave.
    print <<EOT;
 tcams GIF-graphs char vp as % of chapter size --crb3 16Feb07/19feb07
     tcams [options] tpp-tagged-text-infile
   Options:
       -a               toggle: emit absolute-byct graphs as well as % (default: off)
       -c <regex>       filename filter for chapters (def: .*)
       -C aa,bb,cc,dd   CSV, what cameras to show
       -d /subdir       relative path to subdir where chapter txt files are
       -f txt           filetype of chapter files (def: txt)
       -g               toggle: emit gif sizes to stdout (off)
       -m <mountpoint>  where chosen floppy mounts
       -o <outfname>    output filename
       -s <subtitle>    subtitle to put at top of graph (if not deprecated)
       -t <title>       title to put at top of graph
       -x               override graph width in pixels
       -y               override graph height in pixels
       -v               toggle: script gets chatty
       -D               toggle: turn on ad-hoc debug tracers
       -h,-?            show helps, then leave
EOT
    exit(0);

  }
  $arg =~ s/^\=//;                      # handles switch=arg
  $arg=shift(@ARGVif($arg eq "" and ($ARGV[0] !~ /^\-\w/) );
#                     # handles space-separated switch/arg
  if($key eq 'o'){                      # output filename
    $ofile=$arg;
  }elsif($key eq 'f'){                  # filetype
    $ftyp=$arg;                         # 'txt'
  }elsif($key eq 'x'){                  # override x dimension
    $xval=$arg;
    $isx=1;
  }elsif($key eq 'y'){                  # override y dimension
    $yval=$arg;
    $isy=1;
  }elsif($key eq 'd'){                  # what subdir are text chapters in
    $chapdir=$arg;                      # 'ch'
  }elsif($key eq 't'){                  # title to put at top of graph
    $title=$arg;                        # 'Name of Story'
    $istit=1;
  }elsif($key eq 's'){                  # subtitle to put at top of graph
    $subtitle=$arg;                     #  (might be deprecated out in your pkgs)
    $issub=1;
  }elsif($key eq 'c'){
    $chapfiles=$arg;                    # 'sottc\.\d{6}\.txt'   --regex
  }elsif($key eq 'C'){
    $select=$arg;                       # CSV, what cameras to show
  }else{
    warn "$0: unrecognized option -$key $arg\n";
  }
}
die "no tpp-tagged-format infile specified\n" unless defined $ARGV[0];
$inf=shift(@ARGV);

unless($select eq '*'){
  foreach $cm (split(',',$select)){
    $camsels{$cm}=1;                    # build a quick-check hash
  }
}

$|=1 if $debug;
$camera='none';
$chapnum='0';
$d={};
$firstchap=$chapcount=-1;
open(T,"<$inf"or die "can't open infile $inf\n";
while(defined($ln=<T>)){

  if(index($ln,'#cam=')==0){

    chomp $ln;
    $camera=(split('=',$ln))[1];
    $camera =~ s/\s+$//;
    push(@cfiles,$cfile);               # for checking for dual-cam instances

    if($ftyp ne '*'){
      next unless $cfile =~ /\.$ftyp$/;
    }
    if(index($camera,',')>-1){
      foreach $cam (split(',',$camera)){
        unless($select eq '*'){
          next unless exists $camsels{$cam};
        }
        $cams{$cam}=1;
        $d->{$chapnum}->{$cam}=0 unless exists $d->{$chapnum}->{$cam};
        $d->{$chapnum}->{$cam} += (-s $cfile);
      }
    }else{
      unless($select eq '*'){
        next unless exists $camsels{$camera};
      }
      $cams{$camera}=1;
      $d->{$chapnum}->{$camera}=0 unless exists $d->{$chapnum}->{$camera};
      $d->{$chapnum}->{$camera} += (-s $cfile);
    }
    $firstchap=$chapnum if $firstchap==-1;
    $chapcount=0+$chapnum;              # max


  }elsif(index($ln,'===[')==0){         # switch to new chunk filename

    chomp $ln;
    substr($ln,0,4)="";                 # whack off ===[
    ($cfile,$chapnum)=split(/\]\[/,$ln,2);
    $chapnum =~ s/\].*$//;

  }


}
close(T);

for($cx=0;$cx<$#cfiles;$cx++){
  if($cfiles[$cxeq $cfiles[$cx+1]){
    warn "midfile camera switch: $cfiles[$cx]\n";
  }
}

if($debug){
  $dumped=Dumper($d);
  if(defined($ofile)){
    open(O,">$ofile.DUMP"or die "can't make output dumpfile.\n";
    print O $dumped;
    close(O);
  }else{
    print $dumped,"\n";
  }
}

$e->{_chapters_}=$chapcount;
$e->{_firstchapter_}=$firstchap;
foreach $cam (keys %cams){
  foreach $ch ($firstchap .. $chapcount){
    push@{$e->{$cam}},
          ( exists($d->{$ch}->{$cam}) ? $d->{$ch}->{$cam} : 0 )
        );
  }
}

if($debug){
  $dumped=Dumper($e);
  if(defined($ofile)){
    open(O,">$ofile"or die "can't make output dumpfile.\n";
    print O $dumped;
    close(O);
  }else{
    print $dumped,"\n";
  }
}

foreach $ch ($firstchap .. $chapcount){
  push(@chlbls,sprintf("%2.02d",$ch));
}
push(@gifdata,\@chlbls);

foreach  $cam (sort keys %cams){
  my @dataset;
  next if index($cam,',')>-1;
  next if $cam eq 'none';
  push(@labels,$cam);                   # for graphing
  foreach $ch ($firstchap .. $chapcount){
    push@dataset$e->{$cam}->[$ch-1] );
  }
  push(@gifdata,\@dataset);
}


#
# graphing camera-time in kilobytes per chapter
# isn't all that useful, but here's the code;
# see for yourself.
#
if($do_absolutes){
  %gv=(   'title' => 'Cameras by chapter bytecounts',
          'grid_lines' => 'true',
          'stagger_x_labels' => 'true',
  #        'skip_x_ticks' => 5,
          'legend' => 'bottom',
          'legend_labels' => \@labels,
  #        'max_val' => 110,
  #        'y_ticks' => 11,
          'grey_background' => 'false',
          'x_label' => "chapter",
          'y_label' => "Kbytes",
      );

  $gifname="$ofile.gif";
  $gif = Chart::LinesPoints->new($xval,$yval);
  $gif->set%gv );
  $gif->gif($gifname,\@gifdata);
  undef $gifundef %gv;
  system("gifview $gifname &"if $showit;
}
#
# datasets are more useful as % of chapter than as bycts...
# to derive that, we need chapter sizes. in a tpp build, the
# text chapters are probably in a separate subdir, split out for
# generating HTML chapters.
#
# if no regex given, assume all *.txt are involved
#
$chapfiles='.txt$' unless defined $chapfiles;

$chapdir='.' unless defined $chapdir;
opendir(DIR,$chapdiror die "can't open chapters dir $chapdir\n";
(@files)=sort grep(/$chapfiles/,(readdir(DIR)));
closedir(DIR);

foreach $fl (@files){
  $fl =~ /(\d+)/;       # grab just contiguous numbers. chaps MUST be enumerated.
  $chnum = $&;
  $chsizes[$chnum-1]= -s "$chapdir/$fl";
}
#
# now we can convert all those previously-collected bytecounts to percentages.
# (portion / whole) * 100
#
$first=1;
foreach $gd (@gifdata){
  if($first){
    $first=0next;
  }
  for($cx=0;$cx<$chapcount;$cx++){
    $gd->[$cx] = ( $gd->[$cx] / $chsizes[$cx] ) * 100;
  }
}

%gv=(   'title' => "$title by chapter %",
        'grid_lines' => 'true',
        'stagger_x_labels' => 'false',
#        'skip_x_ticks' => 5,
        'legend' => 'bottom',
        'legend_labels' => \@labels,
        'max_val' => 100,
        'y_ticks' => 6,
        'min_val' => 0,
        'gif_border' => 5,
#        'text_space' => 1,     ...buggy, causes right-aligned titles
        'dashed_lines' => undef,
        'grey_background' => 'false',
        'x_label' => "chapter",
        'y_label' => "percent",
    );
$gv{'sub_title'} = $subtitle if length($subtitle);

$gifname="$ofile.percent.gif";          # newer: png, not gif

$gif = Chart::LinesPoints->new($xval,$yval);

$gif->set%gv );

$gif->gif($gifname,\@gifdata);          # newer: ->png

undef $gif;

system("gifview $gifname &"if $showit;        # use ImageMagick's 'display" for png?

if($xandy){
  print "width=\"$xval\" height=\"$yval\"\n";
}

Grab a
gzipped
copy
here
 
Syntax highlighting using Syntax::Highlight::Engine::Kate