#!/usr/bin/perl -w # Usages: # ./farnell-find [] [describe] NNN-NNNN .... # Prints info about the specified relevant part. # ./farnell-find [] order [|] [NNN-NNNN ...] # Add specified parts and quantities to the cart. # If no parts and quantities specified on command line, # will read from stdin (format is ). # If no or specified (ie thing with jsessionid) # then will prompt and read a line from tty. # ./farnell-find [] bom ... # Processes input files and produces BOM parts list. # ./farnell-find [] order-bom ... # Processes input files and adds BOM parts to cart. # # Options: # -S # Specifies sort order for BOM. Several may be specified, first # one on the command line is least significant. Can be `desc' # `avail' `price'. Default sort order is avail, then price, # then desc (equivalent to -Sdesc -Sprice -Savail). # # Input format: lines, #-comments, blank lines ignored. # Indent level is relevant, but only 0, 1, >1 relevant. # Order of information in a file is not relevant unless explicitly # stated below. # # # Starts a circuit. # Circuit names must start with an uppercase letter. # per # Starts an iteratable (ends at start of next cct). # Iteratable name must start with uppercase letter, # may contain any non-ws chars. # [] # [] # Specifies that the circuit contains that part # in the quantity specified. Quantity is scaled # by the iteratable's count if we're in an iteratable. # is any string starting with a non-ws char. # is [=] or /[=]. is # an integer. = says not to order one extra. # is an integer (meaning the number of gates to # a device) or an integer followed by / (meaning we # can't mix this use with other uses or other circuits). # = [ # Defines . is a sequence of words each separated # by one space. Each word consists of some non-ws chars. The # first word may not consist entirely of digits and hyphens; no # other words may consist entirely of digits, slashes and # equals signs. is any string starting with non-ws. # ? = # Gives a default description of parts which have # as an initial subsequence of words. has the # syntax of a . The description is used only if there is # no more specific description, and the constructed description # is with a space and the remainder of the part # name appended. # # suppress # suppress # Suppresses all processing for all parts matching the # glob. # # count # include ) use only one of # exclude ) include and exclude # # end # Ends the file. Optional. use strict; use POSIX; use IO::Handle; use IO::File; use LWP::UserAgent; use HTTP::Request; use HTTP::Request::Common; use HTTP::Response; use HTML::Entities; our(@warn); our(@fault); # neither @warn nor @fault entries have trailing \n our(%pkinddesc); # $pkinddesc{'a b c'} => 'y' after `a b c ? = y' our(%parts); # $parts{"$circuit\n$iteratable"}[]{Part} or item # $parts{"$circuit\n$iteratable"}[]{Qty} # $parts{"$circuit\n$iteratable"}[]{Use} # $parts{"$circuit\n$iteratable"}[]{FileLine} our(%partdef); # $partdef{"part name"}= $item; our(%count); # $count{"$iteratable"} is number of times iteratable is instantiated # (no syntax for specifying this atm) our(%iteminstances); # @{ $iteminstances{$item} } one entry for every line which mentions it # $iteminstances{$item}[]{Use} includes circuit # $iteminstances{$item}[]{Qty} # $iteminstances{$item}[]{Mult} number of this iteratable # $iteminstances{$item}[]{FileLine} our(%itemdesc); # $itemdesc{$item}[]= $desc; our(%iteminfo); # $iteminfo{$item}= by_item(...); our(%show); # $show{$item}{Head} ) newline terminated # $show{$item}{Info} ) strings ready to print # $show{$item}{Price} calculated value our(%suppress_item); our(@suppress_partre); our($incexc); # 'include' or 'exclude' our(%incexc); # $incexc{$circuit}= 1; for those mentioned our(@sorthow); # list of desc price avail our($useragent); our($jsessionid); our($partfrac_unique)= 'aaaa'; # increments our($partword1_re)= '\S*[^ \t0-9-]\S*'; our($partword_re)= '\S*[^ \t0-9/=]\S*'; our($part_re)= "$partword1_re(?: $partword_re)*"; our($item_re)= '\d{3}\-\d{3,7}'; our(%stockmap)= ('In Stock' => 'Y', 'Awaiting Delivery' => 'A'); our($urlbase)= 'http://uk.farnell.com'; sub get_useragent () { my ($whoami,$mailname); return if defined $useragent; die "want http_proxy\n" unless exists $ENV{'http_proxy'}; chomp($whoami= `whoami`); $? and die $?; chomp($mailname= `cat /etc/mailname`); $? and die $?; $useragent= new LWP::UserAgent(env_proxy => 1, keep_alive =>1); } sub check_response ($$) { my ($response,$url) = @_; $response->is_success or die "$url ".$response->as_string." ?"; } sub by_item ($;$) { my ($itemcode,$why) = @_; # looks up $itemcode (ddd-d...) at Farnell and returns # cat hash ref local ($_); my ($chr,$shortcode,$sc2,$url,$price); $url= "$urlbase/jsp/catalog/viewproductdetails.jsp?prodId="; $itemcode =~ m/^(\d\d\d)\-(\d{3,7})$/ or die "$itemcode ?"; $chr->{Item}= $itemcode; $chr->{Shortcode}= $1.$2; $url .= $chr->{Shortcode}; if (!-f "farn.$itemcode") { my ($response); get_useragent(); $response= $useragent->get($url); check_response($response, $url); $_= $response->content; open N, ">tmp.farn.$itemcode" or die $!; print N or die $!; close N or die $!; rename "tmp.farn.$itemcode", "farn.$itemcode" or die $!; unlink "tmp.farnerr.$itemcode"; } else { $_= `cat farn.$itemcode`; $? and die $?; } s/[^\n\t\x20-\x7e\£]/\?/g; if (!eval { $sc2= snarf('Order Code / Sku'); die "wrong results $sc2 ?\n" unless $sc2 eq $chr->{Shortcode}; 1; }) { my ($emsg); $@ =~ s/\n$//; $emsg= "item $itemcode: $@"; $emsg.= " ($why)" if defined $why; push @warn, $emsg; return undef; } $chr->{Description}= snarf('Description'); eval { my ($partno)= snarf('Manufacturer Part Number'); $chr->{Description}= "[$partno] $chr->{Description}"; }; $chr->{Avail}= snarf('Stock Availability'); $chr->{MinOrder}= snarf('Minimum Order'); $chr->{Multiple}= snarf('Order Multiple'); $price= snarf('Unit Price', "\n\