]> git.donarmstrong.com Git - bin.git/blob - make_invoice
add make invoice command
[bin.git] / make_invoice
1 #! /usr/bin/perl
2 # make_invoice makes latex invoices, and is released
3 # under the terms of the GPL version 2, or any later version, at your
4 # option. See the file README and COPYING for more information.
5 # Copyright 2008 by Don Armstrong <don@donarmstrong.com>.
6 # $Id: perl_script 495 2006-08-10 08:02:01Z don $
7
8
9 use warnings;
10 use strict;
11
12 use Getopt::Long;
13 use Pod::Usage;
14
15 =head1 NAME
16
17 make_invoice - makes invoices using latex
18
19 =head1 SYNOPSIS
20
21  make_invoice [options]
22
23  Options:
24   --log, the log file to use to make the invoice
25   --template, the template to use to make the invoice
26   --svn, whether to use subversion or not
27   --debug, -d debugging level (Default 0)
28   --help, -h display this help
29   --man, -m display manual
30
31 =head1 OPTIONS
32
33 =over
34
35 =item B<--log, -l>
36
37 The log file to use to generate the invoice
38
39 =item B<--template, -t>
40
41 The tex template to use to generate the invoice
42
43 =item B<--svn, -s>
44
45 Whether to use subversion or not; defaults to yes if .svn exists in
46 the current directory.
47
48 =item B<--invoice,-i>
49
50 Invoice directory to place invoice in (automatically calculated if not
51 passed.)
52
53 =item B<--min-time-interval, -m>
54
55 Minimum time interval to bill, defaults to 0.
56
57 =item B<--time-granularity, -g>
58
59 Time granularity, defaults to 0.
60
61 =item B<--hourly-fee,-f>
62
63 Hourly fee, defaults to 50.00
64
65 =item B<--debug, -d>
66
67 Debug verbosity. (Default 0)
68
69 =item B<--help, -h>
70
71 Display brief useage information.
72
73 =item B<--man, -m>
74
75 Display this manual.
76
77 =back
78
79 =head1 EXAMPLES
80
81
82 =cut
83
84
85 use vars qw($DEBUG);
86
87 use Date::Manip;
88 use POSIX qw(ceil);
89 use Cwd qw(cwd);
90 use Text::Template;
91
92 my %options = (log             => undef,
93                template        => undef,
94                svn             => undef,
95                invoice         => undef,
96                time_interval   => 0,
97                time_granularity => 0,
98                hourly_fee      => '50.00',
99                debug           => 0,
100                help            => 0,
101                man             => 0,
102                );
103
104 GetOptions(\%options,
105            'log|l=s','template|t=s','invoice|i=s','svn|s!',
106            'time_granularity|time-granularity|g=s',
107            'time_interval|min-time-interval|T=s',
108            'hourly_fee|hourly-fee|f=s',
109            'debug|d+','help|h|?','man|m');
110
111 pod2usage() if $options{help};
112 pod2usage({verbose=>2}) if $options{man};
113
114 $DEBUG = $options{debug};
115
116 my @USAGE_ERRORS;
117 if (not defined $options{log}) {
118      push @USAGE_ERRORS,"You must pass a log file with --log";
119 }
120 if (not defined $options{template}) {
121      push @USAGE_ERRORS,"You must pass a template file with --template";
122 }
123
124 pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
125
126 if (not defined $options{svn}) {
127      $options{svn} = -e '.svn';
128 }
129
130
131 my $log_fh = IO::File->new($options{log},'r') or
132      die "Unable to open $options{log} for reading: $!";
133 my $template_fh = IO::File->new($options{template},'r') or
134      die "Unable to open $options{template} for reading: $!";
135
136
137 my $calc_log = '';
138 my $tex_log = '';
139 my $totaldelta = undef;
140
141 my $first_date = undef;
142 my $last_date = undef;
143 my $time = undef;
144 my $date = undef;
145 my $date2 = undef;
146 my @events;
147
148 while (<$log_fh>) {
149      chomp;
150      if (/^\s*\* /) {
151           if (defined $time) {
152                $tex_log .= format_events($date,$date2,$time,@events);
153                @events = ();
154                $date = undef;
155                $time = undef;
156           }
157           my $string = $_;
158           my ($d1,$d2) = map {s/^\s*\*\s*//;
159                              ParseDate($_)
160                         } split /\s*-\s*/;
161           if (not defined $first_date) {
162                $first_date = $d1;
163           }
164           $last_date = $d2;
165           my $delta = DateCalc($d1,$d2);
166           my $hours = Delta_Format($delta,0,'%ht');
167           if ($hours < $options{time_interval}) {
168                $hours = $options{time_interval}
169           }
170           if ($options{time_granularity}) {
171                $hours = ceil($hours / $options{time_granularity})*$options{time_granularity};
172           }
173           $delta = ParseDateDelta($hours * 60 * 60 . ' sec');
174           ($date,$date2) = ($d1,$d2);
175           $time = $hours;
176           $totaldelta = defined($totaldelta)?DateCalc($delta,$totaldelta):$delta;
177           $calc_log .= qq($string [).Delta_Format($delta,2,q(%ht)).qq(] [).Delta_Format($totaldelta,2,q(%ht)).qq(]\n);
178      }
179      elsif (/^\s+-\s*(.+)/) {
180           my $event = $1;
181           chomp $event;
182           push @events,$event;
183           $calc_log .= $_.qq(\n);
184      }
185      else {
186           $calc_log .= $_.qq(\n);
187      }
188 }
189 $calc_log .= "\nTotal: ".Delta_Format($totaldelta,2,q(%ht)).qq(\n);
190 if (defined $time) {
191      $tex_log .= format_events($date,$date2,$time,@events);
192      @events = ();
193      $date = undef;
194      $date2 = undef;
195      $time = undef;
196 }
197
198
199 my $template;
200 {
201      local $/;
202      $template = <$template_fh>;
203 }
204
205 my $invoice_start = UnixDate($first_date,'%B %e, %Y');
206 my $invoice_stop = UnixDate($last_date,'%B %e, %Y');
207
208 my $tt = Text::Template->new(TYPE=>'string',
209                              SOURCE => $template,
210                              DELIMITERS => ['{--','--}'],
211                             );
212 my $tex_invoice = $tt->fill_in(HASH=>{start => $invoice_start,
213                                       stop  => $invoice_stop,
214                                       log   => $tex_log,
215                                      }
216                               );
217 if (not defined $tex_invoice) {
218      die $Text::Template::ERROR;
219 }
220
221 my $invoice_date = UnixDate($last_date,'%m_%d_%Y');
222 my $invoice_dir = "invoice_$invoice_date";
223
224 if (not -d $invoice_dir) {
225      if ($options{svn}) {
226           system('svn','mkdir',$invoice_dir) == 0 or
227                die "Unable to create invoice directory $invoice_dir";
228      }
229      else {
230           system('mkdir','-p',$invoice_dir) == 0 or
231                die "Unable to create invoice directory $invoice_dir";
232      }
233 }
234
235 my $cwd = cwd;
236 if (-e 'common_makefile' and not -e '$invoice_dir/Makefile') {
237      chdir($invoice_dir);
238      system('ln','-sf','../common_makefile','Makefile') == 0 or
239           die "Unable to link common_makefile to Makefile";
240      if ($options{svn}) {
241           system('svn','add','Makefile') == 0 or
242                die "Unable to add Makefile";
243      }
244      chdir($cwd);
245 }
246
247 # now we write stuff out
248 chdir($invoice_dir);
249 my $calc_log_fh = IO::File->new("log_${invoice_date}",'w') or
250      die "Unable to open log_${invoice_date} for writing: $!";
251 print {$calc_log_fh} $calc_log;
252 close($calc_log_fh);
253
254 my $tex_invoice_fh = IO::File->new("invoice_${invoice_date}.tex",'w') or
255      die "Unable to open log_${invoice_date} for writing: $!";
256 print {$tex_invoice_fh} $tex_invoice;
257 close($tex_invoice_fh);
258
259 if ($options{svn}) {
260      system('svn','add',
261             "log_${invoice_date}",
262             "invoice_${invoice_date}.tex",
263            ) == 0 or die "Unable to add log and invoice to svn";
264      system('svn','propset','svn:ignore',
265             "*.aux\n*.log\n*.dvi\n*.ps\n*.pdf\n",
266             '.'
267            ) == 0 or die "Unable to set svn:ignore";
268 }
269
270
271 sub format_events{
272      my ($date,$date2,$time,@events) = @_;
273      my $output = '        \Fee{'.UnixDate($date,'%A, %B %e, %H:%M').
274           ' to '.UnixDate($date2,'%H:%M %Z')."\n".
275           '         \begin{itemize*}'."\n";
276      $output .= join('',map {"           \\item $_\n"} @events);
277      $output .= '         \end{itemize*}}{50.00}{'.$time.'}'."\n";
278      return $output;
279 }
280
281
282 __END__