chiark / gitweb /
wip commod-email-processor: can parse, now need to make it handle database
[ypp-sc-tools.db-test.git] / pctb / Commods.pm
1
2 package Commods;
3 use IO::File;
4 use HTTP::Request::Common ();
5
6 use strict;
7 use warnings;
8
9 BEGIN {
10     use Exporter ();
11     our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
12     $VERSION     = 1.00;
13     @ISA         = qw(Exporter);
14     @EXPORT      = qw(&parse_masters %oceans %commods %clients
15                       &parse_pctb_commodmap %pctb_commodmap @pctb_commodmap
16                       &get_our_version &check_tsv_line
17                       &pipethrough_prep &pipethrough_run
18                       &pipethrough_run_along &pipethrough_run_finish
19                       &pipethrough_run_gzip
20                       &cgipostform);
21     %EXPORT_TAGS = ( );
22
23     @EXPORT_OK   = qw();
24 }
25
26 our %oceans; # eg $oceans{'Midnight'}{'Ruby'}{'Eta Island'}= $sources;
27 our %commods; # eg $commods{'Fine black cloth'}= $sources;
28 our %clients; # eg $clients{'ypp-sc-tools'}= [ qw(last-page) ];
29 # $sources = 's[l]b';
30 #       's' = Special Circumstances; 'l' = local ; B = with Bleach
31
32 our (%pctb_commodmap,@pctb_commodmap);
33
34 my %colours; # eg $colours{'c'}{'black'}= $sources
35 my @rawcm; # eg $rawcm[0]='fine rum'; $rawcm[1]='fine %c cloth'
36
37 sub parse_master_master1 ($$) {
38     my ($mmfn,$src)= @_;
39     my $mm= new IO::File $mmfn, 'r' or die "$mmfn $!";
40     my @ctx= ();
41     while (<$mm>) {
42         next if m/^\s*\#/;
43         next unless m/\S/;
44         s/\s+$//;
45         if (m/^\%(\w+)$/) {
46             my $colourkind= $1;
47             @ctx= (sub { $colours{$colourkind}{lc $_} .= $src; });
48         } elsif (m/^commods$/) {
49             @ctx= (sub { push @rawcm, lc $_; });
50         } elsif (m/^ocean (\w+)$/) {
51             my $ocean= $1;
52             @ctx= (sub {
53                 $ocean or die; # ref to $ocean needed to work
54                                # around a perl bug
55                 my $arch= $_;
56                 $ctx[1]= sub {
57                     $oceans{$ocean}{$arch}{$_} .= $src;
58                 };
59             });
60         } elsif (m/^client (\S+.*\S)$/) {
61             my $client= $1;
62             $clients{$client}= [ ];
63             @ctx= (sub {
64                 my $bug= $_;
65                 push @{ $clients{$client} }, $bug;
66             });
67         } elsif (s/^ +//) {
68             my $indent= length $&;
69             die "wrong indent $indent" unless defined $ctx[$indent-1];
70             &{ $ctx[$indent-1] }();
71         } else {
72             die "bad syntax";
73         }
74     }
75     $mm->error and die $!;
76     close $mm or die $!;
77
78 #print Dumper(\%oceans);
79 #print Dumper(\@rawcm);
80         
81     %commods= ();
82     my $ca;
83     $ca= sub {
84         my ($s,$ss) = @_;
85 #print "ca($s)\n";
86         if ($s !~ m/\%(\w+)/) { $commods{ucfirst $s} .= $ss; return; }
87         die "unknown $&" unless defined $colours{$1};
88         foreach my $c (keys %{ $colours{$1} }) {
89             &$ca($`.$c.$', $ss .'%'. $colours{$1}{$c});
90         }
91     };
92     foreach (@rawcm) { &$ca($_,$src); }
93 }
94
95 sub parse_masters () {
96     parse_master_master1('master-master.txt','s');
97 }
98
99 sub parse_pctb_commodmap () {
100     undef %pctb_commodmap;
101     foreach my $commod (keys %commods) { $commods{$commod} =~ s/b//; }
102
103     my $c= new IO::File '_commodmap.tsv' or die $!;
104     if (!$c) { $!==&ENOENT or die $!; return 0; }
105
106     while (<$c>) {
107         m/^(\S.*\S)\t(\d+)\n$/ or die "$_";
108         die if defined $pctb_commodmap{$1};  $pctb_commodmap{$1}= $2;
109         die if defined $pctb_commodmap[$2];  $pctb_commodmap[$2]= $1;
110         $commods{$1} .= 'b';
111     }
112     $c->error and die $!;
113     close $c or die $!;
114     return 1;
115 }
116
117 sub get_our_version ($$) {
118     my ($aref,$prefix) = @_;
119     $aref->{"${prefix}name"}= 'ypp-sc-tools yarrg';
120     $aref->{"${prefix}fixes"}= 'lastpage';
121
122     my $version= `git-describe --tags HEAD`; $? and die $?;
123     chomp($version);
124     $aref->{"${prefix}version"}= $version;
125     return $aref;
126 }
127
128 sub pipethrough_prep () {
129     my $tf= IO::File::new_tmpfile() or die $!;
130     return $tf;
131 }
132
133 sub pipethrough_run_along ($$$@) {
134     my ($tf, $childprep, $cmd, @a) = @_;
135     $tf->flush or die $!;
136     $tf->seek(0,0) or die $!;
137     my $fh= new IO::File;
138     my $child= $fh->open("-|"); defined $child or die $!;
139     if (!$child) {
140         open STDIN, "<&", $tf;
141         &$childprep() if defined $childprep;
142         exec $cmd @a; die $!;
143     }
144     return $fh;
145 }
146 sub pipethrough_run_finish ($) {
147     my ($fh)= @_;
148     $fh->error and die $!;
149     close $fh or die "$! $?";  die $? if $?;
150 }
151
152 sub pipethrough_run ($$$@) {
153     my ($tf, $childprep, $cmd, @a) = @_;
154     my $pt= pipethrough_run_along($tf,$childprep,$cmd,@a);
155     my $r;
156     { undef $/; $!=0; $r= <$pt>; }
157     defined $r or die $!;
158     pipethrough_run_finish($pt);
159     return $r;
160 }
161 sub pipethrough_run_gzip ($) {
162     pipethrough_run($_[0],undef,'gzip','gzip');
163 }
164
165 sub cgipostform ($$$) {
166     my ($ua, $url, $form) = @_;
167     my $req= HTTP::Request::Common::POST($url,
168                                          Content => $form,
169                                          Content_Type => 'form-data');
170     if ($url =~ m,^\.?/,) {
171         my $tf= pipethrough_prep();
172         print $tf $req->content() or die $!;
173 #print STDERR "[[[",$req->content(),"]]]";
174         my $out= pipethrough_run($tf, sub {
175             $ENV{'REQUEST_METHOD'}= 'POST';
176             $ENV{'QUERY_STRING'}= '';
177             $ENV{'PATH_TRANSLATED'}= $url;
178             $ENV{'PATH_INFO'}= '';
179             $ENV{'HTTP_HOST'}= 'localhost';
180             $ENV{'REMOTE_ADDR'}= '127.0.0.1';
181             $ENV{'GATEWAY_INTERFACE'}= 'CGI/1.1';
182             $ENV{'DOCUMENT_ROOT'}= '.';
183             $ENV{'SCRIPT_FILENAME'}= $url;
184             $ENV{'SCRIPT_NAME'}= $url;
185             $ENV{'HTTP_USER_AGENT'}= 'Commods.pm local test';
186
187             foreach my $f (qw(Content_Length Content_Type)) {
188                 $ENV{uc $f}= $req->header($f);
189             }
190 #system 'printenv >&2';
191         }, "$url", "$url");
192         $out =~ s/\r\n/\n/g;
193         $out =~ m,^Content-Type: text/plain.*\n\n, or die "$out ?";
194         return $';
195     } else {
196         my $resp= $ua->request($url,$req);
197         die $resp->status_line unless $resp->is_success;
198         return $resp->content();
199     }
200 }
201
202 our %check_tsv_done;
203
204 sub check_tsv_line ($$) {
205     my ($l, $bad_data_callback) = @_;
206     my $bad_data= sub { &$bad_data_callback("bad data: line $.: $_[0]"); };
207     
208     chomp($l) or &$bad_data('missing end-of-line');
209
210     $l !~ m/\P{IsPrint}/ or &$bad_data('nonprinting char(s)');
211     $l !~ m/\\/ or &$bad_data('data contains backslashes');
212     my @v= split /\t/, $l, -1;
213     @v==6 or &$bad_data('wrong number of fields');
214     my ($commod,$stall) = @v;
215
216     !keys %commods or
217         defined $commods{$commod} or
218         &$bad_data("unknown commodity \`$commod'");
219     
220     $stall =~ m/^\p{IsUpper}|^[0-9]/ or &$bad_data("stall not capitalised");
221     !exists $check_tsv_done{$commod,$stall} or &$bad_data("repeated data");
222     $check_tsv_done{$commod,$stall}= 1;
223     foreach my $i (2..5) {
224         my $f= $v[$i];
225         $f =~ m/^(|0|[1-9][0-9]{0,5}|\>1000)$/ or &$bad_data("bad field $i");
226         ($i % 2) or ($f !~ m/\>/) or &$bad_data("> in field $i price");
227     }
228     return @v;
229 }
230
231 1;