Subversion Repositories OpenARM Single-board Computer

Rev

Rev 325 | Rev 327 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
280 agaran 1
#!/usr/bin/perl -w
2
use strict;
293 agaran 3
# $Id: inventory.pl 326 2008-12-29 17:18:46Z agaran $
280 agaran 4
# Thu, 13 Nov 2008 21:06:23 +0100
5
# Maciej 'agaran' Pijanka <agaran@pld-linux.org>
6
# for OpenARM SBC Project
7
# license: gpl v3
8
 
293 agaran 9
 
280 agaran 10
use Getopt::Long qw//;
293 agaran 11
use File::Basename qw/basename/;
322 agaran 12
use IO::Dir;
13
use IO::File;
280 agaran 14
 
325 agaran 15
use lib File::Basename::dirname($0).'/lib';
326 agaran 16
use Text::Table;
325 agaran 17
 
18
 
313 agaran 19
my %Config;
280 agaran 20
 
313 agaran 21
# ==================================================
22
$Config{docdir} = '.';
23
$Config{bomdir} = '.';
325 agaran 24
$Config{outdir} = '.';
313 agaran 25
$Config{verbose} = 1;
280 agaran 26
 
313 agaran 27
# 0 mean not show, -1 show all, positive value limits depth of shown
28
$Config{dbg_showdirs} = 0;
280 agaran 29
 
313 agaran 30
 
31
# modes
32
my $build_inventory = 0;
33
my $build_bom = 0;
34
my $show_conf = 0;
35
my $show_help = 0;
36
 
37
# ==================================================
38
 
39
my %Inv_By_PartNo;
40
my @Inv;
325 agaran 41
my %BomData;
313 agaran 42
 
293 agaran 43
sub err_printf($@) {
44
        my ($format, @args) = @_;
280 agaran 45
 
313 agaran 46
        printf STDERR "-E- ".$format."\n", @args;
47
        # exit? or fail-exit here
280 agaran 48
}
49
 
293 agaran 50
sub wrn_printf($@) {
51
        my ($format, @args) = @_;
52
 
53
        printf STDERR "-W- ".$format."\n", @args;
54
}
55
 
56
sub inf_printf($@) {
57
        my ($format, @args) = @_;
58
 
59
        printf STDERR "-I- ".$format."\n", @args;
60
}
61
 
313 agaran 62
sub Config_Show {
63
        printf "Config for %s\n----------------------------------------\n", basename($0);
64
        foreach my $name (sort keys %Config) {
65
                printf "%-20s %s\n", $name, $Config{$name};
66
        }
67
}
68
 
69
sub Help_Show {
70
        printf "Help for %s\n----------------------------------------\n", basename($0);
71
        unless (defined($_[1]) && length($_[1]) != 0) {
72
                print "Basic help\n\t--showrc|showconf shows current configuration\n".
73
                        "\t--docdir|-d <dir> tells script where information.txt files should be searched\n".
74
                        "\t--define <something>=<somethingelseornot> defines some configration value\n".
319 agaran 75
                        "\t--verbose|-v [level] sets verbosity level\nOrder of options DOES matter\n".
76
                        "\t--bomdir|-b <dir> tells script where boms should be searched\n";
77
 
78
                print "\t\e[0;31mThis Help Is created especially for \e[0;33mJelle\e[0;29m\n".
79
                        "\tThis script search all directories below specified ones to find interesting files\n";
313 agaran 80
                return;
81
        }
82
        if ($_[1] =~ /^foo$/) {
83
                print "Noo, there is no foo's here\n";
84
        } else {
85
                printf "Sorry, help for `%s' don't exist (eventually) yet\n", $_[1];
86
        }
87
        exit;
88
}
89
 
90
sub fix_dir ($) {
91
        my $dir = shift @_;
92
 
93
        $dir =~ s/\/$//;
94
 
95
        if (! -d $dir) {
96
                err_printf("Sorry `%s' is not valid directory, exiting", $dir);
97
        }
98
 
99
        return $dir;
100
}
101
 
102
sub shortdir ($) {
103
        my $path = shift @_;
104
 
105
        return substr($path, 2) if ($path =~ /^\.\//) ;
106
        return $path;
107
}
108
 
109
 
110
sub trim($) {
111
        my ($value) = @_;
112
 
113
        $value =~ s/^ +//;
114
        $value =~ s/ +$//;
115
        return $value;
116
}
117
 
118
sub etrim($) {
119
        my ($value) = @_;
120
 
121
        $value =~ s/^[  ]+//;
122
        $value =~ s/[   ]+$//;
123
        return $value;
124
}
125
 
325 agaran 126
sub shortstring($$) {
127
        my ($str,$lim) = @_;
128
        return substr($str,0,$lim-4).'(..)' if (length ($str) > $lim);
129
        return $str;
130
}
131
 
313 agaran 132
# this subroutine is used as callback function
133
# executed by file_lookup
280 agaran 134
sub parse_ifile($) {
293 agaran 135
        my ($filepath) = @_;
280 agaran 136
 
318 agaran 137
 
293 agaran 138
        open(IN, $filepath) or return 1;
280 agaran 139
 
293 agaran 140
        my %data;
280 agaran 141
 
293 agaran 142
        while (not eof IN) {
143
                my $line = <IN>;
280 agaran 144
 
293 agaran 145
                chomp $line;
280 agaran 146
 
293 agaran 147
                next if ($line =~ /^[   ]*$/);
148
                next if ($line =~ /^;/);
149
 
150
                last if ($line =~ /^--/);
151
 
152
                if ($line =~ /^([A-Za-z ]+):(.*)$/) {
313 agaran 153
                        my ($name,$value) = (lc etrim($1),etrim($2));
293 agaran 154
 
155
                        if ($name =~ /^price$/) {
156
                                $value =~ s/[^0-9\.\,]//g;
157
                                if ($value =~ s/^([0-9]+)[,.]([0-9]+)$/$1.$2/) {
158
                                        # printf STDERR "Price %.3f\n", $value;
159
                                        if (!defined($data{price})) {
160
                                                $data{price} = $value;
161
                                        } else {
313 agaran 162
                                                wrn_printf("Duplicated price field in file %s", shortdir($filepath));
293 agaran 163
                                        }
164
                                } else {
313 agaran 165
                                        err_printf("Bad price field in file %s", shortdir($filepath));
293 agaran 166
                                }
167
                        } elsif ($name =~ /^manufacturer$/i) {
168
                                # printf STDERR "Manufacturer %s\n", $value;
169
                                if (!defined($data{manufacturer})) {
170
                                        $data{manufacturer} = $value;
171
                                } else {
313 agaran 172
                                        wrn_printf("Duplicated manufacturer field in file %s", shortdir($filepath));
293 agaran 173
                                }
174
                        } elsif ($name =~ /^manufacturer part no$/ ) {
175
                                # printf STDERR "ManPartNo %s\n", $value;
176
                                if (!defined($data{manufact_partno})) {
313 agaran 177
                                        $data{manufact_partno} = trim($value);
293 agaran 178
                                } else {
179
                                        wrn_printf("Duplicated manufacturer part no field in file %s",
313 agaran 180
                                                shortdir($filepath));
293 agaran 181
                                }
182
                        } elsif ($name =~ /^description$/i) {
313 agaran 183
                                if (!defined($data{desc})) {
184
                                        $data{desc} = $value;
185
                                } else {
186
                                        wrn_printf("Duplicated description no field in file %s",
187
                                                shortdir($filepath));
188
                                }
293 agaran 189
                        } elsif ($name =~ /^datasheet$/i) {
190
                                $data{datasheet} = [] unless defined $data{datasheet};
191
                                push @{$data{datasheet}}, $value;
192
                                # printf STDERR "Datasheet %s\n", $value;
193
                        } elsif ($name =~ /^supplier$/i) {
194
                                # printf STDERR "Supplier %s\n", $value;
195
                                if (!defined($data{supplier})) {
196
                                        $data{supplier} = $value;
197
                                } else {
198
                                        wrn_printf("Duplicated supplier field in file %s",
313 agaran 199
                                                shortdir($filepath));
293 agaran 200
                                }
201
                        } elsif ($name =~ /^order code$/) {
202
                                # printf STDERR "Order Code %s\n", $value;
203
                                if (!defined($data{ordercode})) {
204
                                        $data{ordercode} = $value;
205
                                } else {
206
                                        wrn_printf("Duplicated order code field in file %s",
313 agaran 207
                                                shortdir($filepath));
293 agaran 208
                                }
209
                                #push @DATA, { $name => $value };
210
                        } elsif ($name =~ /^url .*$/) {
211
                                # printf STDERR "URL %s\n", $value;
212
                        } elsif ($name =~ /^catalog(ue|) page$/) {
213
                                # printf STDERR "Catalogue Page %s\n", $value;
214
                        } else {
215
                                err_printf("Unhandled field %s in file %s", $name,
313 agaran 216
                                        shortdir($filepath));
293 agaran 217
                        }
218
 
219
                } else {
220
                        wrn_printf("Unparseable line `%s', forgot ; to set it as comment in file %s\n", $line,
313 agaran 221
                                shortdir($filepath));
293 agaran 222
                }
223
        }
224
        close(IN);
225
 
226
        if (scalar keys %data == 0) {
227
                inf_printf("Skipping file %s because contain no data for me",
313 agaran 228
                        shortdir($filepath));
293 agaran 229
                return;
230
        }
231
 
232
        unless (defined ($data{price}) && $data{price} != 0) {
233
                wrn_printf("Missing Price in file %s",
313 agaran 234
                        shortdir($filepath));
293 agaran 235
        }
236
 
237
        unless (defined($data{manufact_partno})) {
238
                inf_printf("Missing Manufacturer Part No in file %s",
313 agaran 239
                        shortdir($filepath));
293 agaran 240
        }
241
 
242
        unless (defined($data{ordercode})) {
243
                wrn_printf("Missing Order Code in file %s",
313 agaran 244
                        shortdir($filepath));
293 agaran 245
        }
246
 
313 agaran 247
 
293 agaran 248
        use Data::Dumper qw/Dumper/;
313 agaran 249
 
250
        my $id = scalar @Inv;
251
 
252
        $Inv[$id] = {};
253
 
254
        $Inv[$id]{Datasheet} = delete $data{'datasheet'} if defined $data{'datasheet'};
255
        $Inv[$id]{Manufacturer} = delete $data{'manufacturer'} if defined $data{'manufacturer'};
256
        $Inv[$id]{Description} = delete $data{'desc'} if defined $data{'desc'};
257
        $Inv[$id]{Price} = delete $data{'price'} if defined $data{'price'};
258
        $Inv[$id]{Ordercode} = delete $data{'ordercode'} if defined $data{'ordercode'};
259
        $Inv[$id]{Manufacturer_Partno} = delete $data{'manufact_partno'} if defined $data{'manufact_partno'};
260
        $Inv[$id]{Supplier} = delete $data{'supplier'} if defined $data{'supplier'};
261
 
262
        unless (defined ($Inv_By_PartNo{$Inv[$id]{Manufacturer_Partno}})) {
263
                $Inv_By_PartNo{$Inv[$id]{Manufacturer_Partno}} = $id;
264
        } else {
265
                wrn_printf("PartNumber %s happened more than once, using first occurence (id:%d)",
266
                        $Inv[$id]{Manufacturer_Partno}, $id);
267
        }
325 agaran 268
 
313 agaran 269
        #inf_printf("Part %s defined in file %s", $Inv[$id]{Manufacturer_Partno}, shortdir($filepath));
293 agaran 270
 
313 agaran 271
        wrn_printf("Unhandled data from parsing: %s", Dumper(\%data)) if (scalar keys %data > 0);
280 agaran 272
}
273
 
274
 
313 agaran 275
sub parse_bom ($) {
276
        my ($filepath) = @_;
280 agaran 277
 
313 agaran 278
        open(IN, $filepath) or return 1;
279
 
280
        #wrn_printf("GotARg: %s", shortdir($filepath));
281
 
282
        my @Fields;
283
 
284
        my %data;
285
        my $v = '';
286
        while (not eof IN) {
287
                my $line = <IN>;
288
 
289
                chomp $line;
290
 
291
                if ($line =~ /^\.START$/) {
292
                        $v = 'boms';
293
                        next;
294
                }
295
 
296
                if ($line =~ /^\.END$/) {
297
                        $v = '';
298
                        next;
299
                }
300
 
301
                if ($v eq 'boms') {
302
                        @Fields = split(/\t/, substr($line,2));
303
                        $v = 'bom';
304
                        # some funny way to generate field-map
305
                        # that if someone reorder bom file columns we are still on place
306
                        next;
307
                }
308
 
309
                if ($v eq 'bom') {
310
                        my ($refdes, $device, $value, $footprint, $quantity) = split (/\t/, $line);
311
 
312
                        $device = trim($device);
313
 
314
#                       wrn_printf("Device: %s (value: %s) at refdes %s, %d pcs", $device, $value, $refdes, $quantity);
315
 
316
                        if (!defined $Inv_By_PartNo{$device}) {
317
                                wrn_printf("Device %s not found in inventory in file %s", $device, shortdir($filepath));
318
                                next;
319
                        }
320
                        my $id = $Inv_By_PartNo{$device};
321
                        next if ($Inv[$id]{Manufacturer} =~ /none/i); # skip parts whose manufacturer is none
322
                        #inf_printf("Found in Inventory at %d %s", $id, $Inv[$id]{Manufacturer_Partno});
323
                        push @{$data{$id}{RefDes}}, $refdes;
324
                }
325
        }
326
        close(IN);
327
 
328
        if (scalar keys %data == 0) {
329
                inf_printf("Skipping file %s because contain no data for me",
330
                        shortdir($filepath));
331
                return;
332
        }
333
 
334
        foreach my $id (keys %data) {
335
                my %tmp;
336
                map { $tmp{$_} = 1 } @{$data{$id}{RefDes}};
337
                @{$data{$id}{RefDes}} = keys %tmp;
338
                my $cnt = scalar @{$data{$id}{RefDes}};
339
                if (!defined $Inv[$id]{Price}) {
340
                        wrn_printf("%s has no price, setting to 0.0", $Inv[$id]{Manufacturer_Partno});
341
                        $Inv[$id]{Price} = 0;
342
                }
318 agaran 343
                push @{$BomData{$id}{RefDes}}, @{$data{$id}{RefDes}};
344
 
313 agaran 345
#               printf "%-20s %.4f %s\n", $Inv[$id]{Manufacturer_Partno}, $cnt, $icost, join (', ', @{$data{$id}{RefDes}});
346
#               %BomData{ById}{$id}{RefDes}
347
        }
348
}
349
 
350
sub file_lookup ($$$$) ;
351
sub file_lookup ($$$$) {
352
        my ($dir, $depth, $regexp, $callback) = @_;
353
 
354
        err_printf("Sorry, callback must be CODE ref") unless (ref $callback eq 'CODE');
355
 
293 agaran 356
        if ( -d $dir) {
322 agaran 357
                my $d = IO::Dir->new($dir);
358
                return 1 if (!defined $d);
359
 
360
 
361
                while (defined(my $e = $d->read)) {
293 agaran 362
                        my $fe = $dir .'/'. $e;
363
                        if ( -f $fe) {
313 agaran 364
                                if ($fe =~ $regexp) {
365
                                        &$callback($fe);
293 agaran 366
                                }
367
                        } elsif (-d $fe) { # now its dir...
368
                                if ($e eq '.svn') { # if entry name is equal to svn
369
                                        next; # go to next entry in foreach loop
370
                                }
371
                                next if ($e eq '.' or $e eq '..'); # skip to next if dir entry is . or ..
281 jelle 372
 
313 agaran 373
                                if ($Config{dbg_showdirs} == -1 or $Config{dbg_showdirs} > $depth) {
374
                                        printf STDERR "Entering directory %s\n", shortdir($fe);
293 agaran 375
                                }
313 agaran 376
                                return 1 if (file_lookup($fe, $depth+1, $regexp, $callback) == 1);
293 agaran 377
                        } else {
313 agaran 378
                                # symlink or other mysterius beast
293 agaran 379
                        }
380
                }
381
        }
313 agaran 382
        return 0;
280 agaran 383
}
384
 
293 agaran 385
Getopt::Long::Configure("bundling");
280 agaran 386
 
319 agaran 387
if (scalar @ARGV == 0) {
388
        Help_Show();
389
        exit;
390
}
391
 
313 agaran 392
my $result = Getopt::Long::GetOptions (
393
        "showrc|showconf" => sub { $show_conf = 1 },
394
        "docdir|d=s" => sub { $Config{docdir} = $_[1]; },
395
        # not sure if bomdir or SCH dir
396
        "bomdir|b=s" => sub { $Config{$_[0]} = $_[1]; },  # $_[0] contain basename of option, so in few cases could be (ab)used
397
        "define|D=s" => sub { my ($p,$q) = split(/=/,$_[1],2); $Config{$p} = $q; },
398
        "verbose|v:+" => sub { $Config{$_[0]} = ($_[1]>1?0:$Config{$_[0]}) + $_[1]; },
399
        "help|h|?:s" => sub { $show_help = 1 },
325 agaran 400
        "outdir|o=s" => sub { $Config{$_[0]} = $_[1]; },
313 agaran 401
 
402
        # options 
293 agaran 403
);
404
if (!$result) {
405
        printf "Usage: %s [-d directory] [-v]\n",basename($0);
406
        exit;
407
}
313 agaran 408
 
409
# ==================================================
410
# processing of options/config values, checking ranges etc
411
# 
412
 
413
$Config{docdir} = fix_dir ($Config{docdir});
414
$Config{bomdir} = fix_dir ($Config{bomdir});
415
 
416
if ( $show_help == 1) {
417
        Help_Show();
418
        exit;
293 agaran 419
}
420
 
313 agaran 421
if ( $show_conf == 1) {
422
        Config_Show();
423
        exit;
424
}
293 agaran 425
 
313 agaran 426
# make Inventory
427
file_lookup($Config{docdir}, 0, qr/\/information.txt$/, \&parse_ifile);
428
 
429
# process BOM files
430
file_lookup($Config{bomdir}, 0, qr/\.bom$/, \&parse_bom);
431
 
318 agaran 432
my $n = 0;
433
my $cost = 0.0;
434
 
435
 
325 agaran 436
my $out = new IO::File $Config{outdir}.'/output.txt', 'w';
318 agaran 437
 
438
open (PARTMAP, '>output.map') || die 'cant open output.map: $!';
439
 
440
printf PARTMAP "%-35s| refdes\n\n", 'part';
441
 
325 agaran 442
my $bomtable = Text::Table->new(
443
        { title => '|', is_sep => 1 },
444
        { title => 'item', align => 'right', align_title => 'center' },
445
        { title => '|', is_sep => 1 },
446
        { title => 'description', align => 'left', align_title => 'center' },
447
        { title => '|', is_sep => 1 },
448
        { title => 'manufacturer partno', align => 'left', align_title => 'center' },
449
        { title => '|', is_sep => 1 },
450
        { title => 'manufacturer', align => 'left', align_title => 'center' },
451
        { title => '|', is_sep => 1 },
452
        { title => 'order code', align => 'left', align_title => 'center' },
453
        { title => '|', is_sep => 1 },
454
        { title => 'quantity', align => 'right', align_title => 'center' },
455
        { title => '|', is_sep => 1 },
456
        { title => 'unit price', align => 'auto', align_title => 'center' },
457
        { title => '|', is_sep => 1 },
458
        { title => 'total price', align => 'auto', align_title => 'center' },
459
        { title => '|', is_sep => 1 },
460
);
461
 
462
foreach my $id ( sort {my $p = $Inv[$a]{Manufacturer} cmp $Inv[$b]{Manufacturer}; if ($p == 0) {
463
#               return $Inv[$a]{Manufacturer_Partno} cmp $Inv[$b]{Manufacturer_Partno}}; return $p; }  keys %BomData) {
464
                return $Inv[$a]{Description} cmp $Inv[$b]{Description}}; return $p; }  keys %BomData) {
318 agaran 465
        my %tmp;
466
        map { $tmp{$_} = 1 } @{$BomData{$id}{RefDes}};
467
        @{$BomData{$id}{RefDes}} = keys %tmp;
325 agaran 468
        my $quant = scalar @{$BomData{$id}{RefDes}};
318 agaran 469
        if (!defined $Inv[$id]{Price}) {
470
                wrn_printf("%s has no price, setting to 0.0", $Inv[$id]{Manufacturer_Partno});
471
                $Inv[$id]{Price} = 0;
472
        }
325 agaran 473
        my $icost = $quant * $Inv[$id]{Price};
318 agaran 474
 
325 agaran 475
        printf PARTMAP "%-35s|%s\n", shortstring($Inv[$id]{Description},35), join (', ', sort @{$BomData{$id}{RefDes}});
318 agaran 476
        $cost += $icost;
477
#       %BomData{ById}{$id}{RefDes}
325 agaran 478
        $bomtable->add($n, $Inv[$id]{Description}, $Inv[$id]{Manufacturer_Partno}, $Inv[$id]{Manufacturer},$Inv[$id]{Ordercode},
479
                $quant, $Inv[$id]{Price}, $icost);
318 agaran 480
        $n ++;
481
}
482
 
325 agaran 483
printf $out "file generated at %s\n\n", scalar localtime(time());
484
 
485
print $out $bomtable->rule('-','+');
486
print $out $bomtable->title();
487
print $out $bomtable->rule('-','+');
488
print $out $bomtable->body();
489
print $out $bomtable->rule('-','+');
490
 
491
printf $out "\nTotal cost: %.3f\n", $cost;
492
 
318 agaran 493
close PARTMAP;
325 agaran 494