#!/usr/bin/env perl use strict; use warnings; use DateTime; use DateTime::Format::Natural; use Getopt::Long::Descriptive qw(describe_options prog_name); use WWW::Mechanize; # Check command line syntax. my ($opt, $usage) = describe_options( '%c %o ', [ 'csv' => 'Brief CSV output instead of the usual report.' ], [ 'version' => 'Output program version and exit.' ], [ 'help' => 'Print usage message and exit.' ] ); my $prog_name = prog_name(); print(<help; $prog_name: Report the number of Trac tickets opened and closed in the given time period. Usage: $usage Base Trac URL is the location of Trac installation, the start and end dates define the period to report for, in any common date format. For example: $prog_name https://trac.example.com/ 2011-07-24 2011-07-31 or try a more relaxed $prog_name trac.example.com "last monday" today form. END print("$prog_name version 1.0.\n"), exit if $opt->version; die "Usage: $usage\n" if @ARGV != 3; # Extract parameters. my $timeline_url = $ARGV[0]; $timeline_url = "http://$timeline_url" if $timeline_url !~ m@^\w+://@; $timeline_url .= '/' unless $timeline_url =~ m@/$@; $timeline_url .= 'timeline'; my $parser = DateTime::Format::Natural->new; my $start_date = $parser->parse_datetime($ARGV[1]); die "Invalid start date: " . $parser->error() if !$parser->success; my $end_date = $parser->parse_datetime($ARGV[2]); die "Invalid end date: " . $parser->error() if !$parser->success; # Transform them to Trac weird (end, days back) input format. my $trac_days_back = $end_date->delta_days($start_date)->delta_days; # NB: Trac always seems to use US date format with 2 digit years currently. my $trac_end_date = $end_date->strftime("%m/%d/%y"); # Do get the timeline. Unfortunately this requires loading the page twice, # first time to get the form and the second one to submit it. It would be nice # to avoid this but I don't see how. my $mech = WWW::Mechanize->new(autocheck => 1); $mech->get($timeline_url); my $response = $mech->submit_form( form_id => 'prefs', fields => { from => $trac_end_date, daysback => $trac_days_back, ticket => 'on', ticket_details => undef, changeset => undef, milestone => undef, wiki => undef, } ); my $timeline_dump = $response->content; # Don't bother really parsing HTML, just count the lines. # Use an array to have a well-defined order of ticket kinds in the report # output below. my @ticket_kinds = qw(new closed reopened); my %counters = map { $_ => 0 } @ticket_kinds; for (split /\n/, $timeline_dump) { next unless /
/; if ( !exists($counters{$1}) ) { warn qq{Unknown timeline ticket entry "$1ticket".\n}; next; } $counters{$1}++; } # Finally output the results in either human-readable report or # machine-readable CSV format. if ( $opt->csv ) { print $start_date->strftime('%F'), ',', $end_date->strftime('%F'), ',', $counters{new}, ',', $counters{closed}, ',', $counters{reopened}, "\n"; } else { print "Ticket statistics for $timeline_url" . ' from ' . $start_date->strftime('%F') . ' to ' . $end_date->strftime('%F') . ":\n"; print '-' x 20 . "\n"; for (@ticket_kinds) { printf "%-10s%10d\n", ucfirst, $counters{$_}; } print '-' x 20 . "\n"; printf "%-10s%10d\n", 'Delta', $counters{new} + $counters{reopened} - $counters{closed}; } exit 0