Having written Finance::Quote::currency_lookup I was able to get back to the little script that triggered the idea in the first place
The idea: a script which I can call with partial code or currency names and an amount which will then try and tell me what the current converted value is. If there's something other than a single matching currency let me know that nothing or too many things matched.
Here's the script (also attached):
1 #!/usr/bin/env perl 2 3 use strict; 4 use warnings; 5 6 use Finance::Quote; 7 my $q = Finance::Quote->new(); 8 9 my %trans = (); 10 @trans{qw(from to)} = @ARGV; 11 12 # Copied this patter from Finance::Quote::currency to be consistent 13 $trans{from} =~ s/^\s*(\d*\.?\d*)\s*//; 14 my $amount = $1 || 1; 15 16 # Search out currencies 17 for my $key (qw( from to )) { 18 # Is it a code or a name? 19 my $currencies; 20 if ( $trans{$key} =~ /^[A-Z]{2,}$/ ) { 21 # Guess code 22 $currencies = $q->currency_lookup( code => qr/$trans{$key}/ ); 23 } 24 else { 25 # Guess name 26 $currencies = $q->currency_lookup( name => qr/$trans{$key}/i ); 27 } 28 if ( scalar keys %{$currencies} == 1 ) { 29 my $real_key = "real_${key}"; 30 ($trans{$real_key}) = keys %{$currencies}; 31 printf "%s : %s (%s)\n", $key 32 , $trans{$real_key} 33 , $currencies->{$trans{$real_key}}->{name}; 34 } 35 elsif ( scalar keys %{$currencies} > 1 ) { 36 print "Multiple currency matches for ${key}:\n"; 37 print " * $_ (" . $currencies->{$_}->{name} . ")\n" for keys %{$currencies}; 38 } 39 else { 40 print "No currency matches for ${key}: " . $trans{$key} . "!\n"; 41 } 42 } 43 44 # If real from and to exist run the conversion 45 if ( exists $trans{real_from} && exists $trans{real_to} ) { 46 print "${amount} $trans{real_from} => " 47 . $q->currency("${amount}$trans{real_from}", $trans{real_to}) 48 . " $trans{real_to}\n"; 49 }
And here's some examples of this script in action:
# Some Australian dollars to pounds # (name searches only) $ ./conv.pl 42aus brit from : AUD (Australian Dollar) to : GBP (British Pound) 42 AUD => 18.8454 GBP # And the some pounds to US dollars # (a name and a code search, because US is all-caps) $ ./conv.pl brit US from : GBP (British Pound) to : USD (U.S. Dollar) 1 GBP => 1.4225 USD # Oops - a typo on pounds $ ./conv.pl GPB euro No currency matches for from: GPB! to : EUR (Euro) # And now, to make the British cry... $ ./conv.pl GBP euro from : GBP (British Pound) to : EUR (Euro) 1 GBP => 1.1317 EUR # And finally, too many matches: $ ./conv.pl egypt ven from : EGP (Egyptian Pound) Multiple currency matches for to: * SIT (Slovenian Tolar) * VEB (Venezuelan Bolivar)
While using
Finance::Quote::currency() to do some conversions I found myself
having to go searching around the web for currency codes. At the time I
thought it would be much easier if you could just asked the module what
currency codes existed - and so I started work on a
currency_lookup()
function to do just that.
Finance::Quote uses the Yahoo Currency Converter website so the new function needed to report a compatible list of currencies.
Having developed the search method against a statically stored list of currencies (as I hadn't implemented the live extraction yet) I then discussed options with the other developers and decided that a hard coded list was a better approach given that the list wasn't going to change very often.
Here's a basic idea of usage:
1 $currencies_by_name = $q->currency_lookup( name => 'Australian' ); 2 $currencies_by_code = $q->currency_lookup( code => qr/^b/i ); 3 $currencies_by_both = $q->currency_lookup( name => qr/pound/i 4 , code => 'GB' );
The return value is a hash of hash-refs - for example for the first query above:
1 $VAR1 = { AUD => { name => "Australian Dollar" } }
I had added a module to contain the currency list (Finance::Quote::Currencies
).
Once the search code was complete I then added a test to compare the static list with a live
list and implemented Finance::Quote::Currencies::fetch_live_currencies()
. This
function uses HTML::Parser
to extract the currency list. If the currency list changes this function can be used to easily
updated the stored list.
These changes are currently available on the currency_lookup branch on github. Coming to a CPAN release in your town real soon now.
This one caught somewhat by surprise - mostly because of the unusual use of a 500 (Internal Server Error) code by LWP::UserAgent to flag a client-side configuration problem.
The answer, as with so many things perl, lay with the PerlMonks in this article: Mysterious LWP status 500 Line too long.
To summarise:
Net::HTTP has a default maximum header line length of 4096. This
can be changed by passing a MaxLineLength
option to the
constructor - and happily LWP::UserAgent has an internal hash which can
be tweaked to set the desired maximum line length:
1 my %OPTS = @LWP::Protocol::http::EXTRA_SOCK_OPTS; 2 $OPTS{MaxLineLength} = 8192; # Or however large is needed... 3 @LWP::Protocol::http::EXTRA_SOCK_OPTS = %OPTS;
There's a couple of cheat/reference sheets out there for perl already:
Here's a perlpod quick reference:
SECTIONS INDENTATION START/STOP POD =head1 =over indentlevel =pod =head2 =item bullet =cut =head3 =back FORMATTING CODES FORMATTING CODES (cont.) Iitalic text F filename B bold text S text with non-breaking spaces C code text X index entry L hyperlink Z<> null E character escape B<< >> More than one delimiter is ok COMMON MAIN SECTIONS NAME BUGS/CAVEATS SYNOPSIS DIAGNOSTICS DESCRIPTION DEPENDENCIES COPYRIGHT LICENSE SEE ALSO AUTHOR
Until I've come up with something to add, for the moment the best way I can think to describe testing with perl is to reference several excelent sources of documentation on the subject:
Those provide an excellent basis for constructing and running tests in perl.
Once the processes in those documents and modules have become familiar there are a number of excellent tools that can be used to make testing even more powerful:
Early experiences with using the Catalyst Web Framework tend to suggest that there are a few common starting points when deploying a new Catalyst application.
These steps take the 'complete' application harness (which is essentially a completely working but completely empty web application) and add a few features to get a little content and functionality into the application.
To begin with it's worth noting that most of documentatin for Catalyst is in the POD - so you can do a lot worse then point your web browser at Catalyst when you're looking for help. The next two resources I've found most useful are the mailing list and the Developer Community Site.
Another invaluable starting point is the Catalyst Tutorial.
Once an application has been created it's time to start adding functionality, so without further ado:
catalyst.pl TestApplication
Often the Template Toolkit will be all you need to manage the View output of an application.
The easiest way to start using Template Toolkit as the default view is to create a view using the Template Toolkit View helper:
./script/testapplication_create.pl view TT TT
Once this is done, the view is most easily accessed by using the DefaultEnd
Plugin which will direct all responses without content in the body to the
first View. This is done by adding DefaultEnd
to the use
Catalyst
part of lib/TestApplication.pm
use Catalyst qw/ -Debug ConfigLoader Static::Simple DefaultEnd /;
A few notes on use of the Template Toolkit view:
./script/testapplication_create.pl view TT TTSite
DefaultEnd
plugin has a view
configuration directive:
# In the YAML configuration file: testapplication.yml --- name: TestApplication view: TT # OR # In the application module: lib/TestApplication.pm __PACKAGE__->config( name => 'TestApplication', view => 'TT', );
lib/TestApplication/Controller/Root.pm
:
# # Output a friendly welcome message # sub default : Private { my ( $self, $c ) = @_; # If we reach here, the correct reponse is a 404 $c->response->status(404); # Hello World $c->response->body( $c->welcome_message ); }Of course removing the default Catalyst welcome page is also a good idea but can be done later when you get around to putting content into the site.
lib/TestApplicatoin.pm
):
__PACKAGE__->config( name => 'TestApplication', view => 'TT', 'View::TT' => { INCLUDE_PATH => [ __PACKAGE__->path_to('templates'), ], WRAPPER => 'site/wrapper.tt', } );This sets up a template include path in a
templates
directory in the root application directory. In addition a wrapper is defined. A Template Toolkit wrapper is a template which is wrapped around all content rendered by the library. When writing a wrapper the content of the page will be inserted where the [% content %]
directive is placed.
An example wrapper is as follows:
[% DEFAULT title = c.config.name %][% title %] [% title %]
[% content %]
sub index : Private { my ( $self, $c ) = @_; # Simple index $c->{stash}->{title} = "Index"; $c->{stash}->{text} = qq{In this case the ''text.tt'' template is very simple:Welcome to the application - nothing to see here yet
}; $c->{stash}->{template} = "text.tt"; }
[% text %]For further information on the automagically called private methods of controllers see Catalyst::Manual::Intro.
Class::DBI
.
In order to properly use data modelling it should not be a requirement that accessing the model is done through the catalyst application. By defining a Class::DBI
library independant of the catalyst application and then referring to that library this can be done. If no library is defined for the database yet you may be able to use the automatic database interrogation done for you by Class::Loader.
I'm using [http://www.sqlite.org/ SQLite] because it's very simple to do so - if you haven't seen SQLite before go have a look. :)
To start with - create an SQLite database (in this case in a {{db}} subdirectory):
BEGIN TRANSACTION; CREATE TABLE foo ( bar VARCHAR(132) ); INSERT INTO "foo" VALUES('sdfasd'); INSERT INTO "foo" VALUES('sdfasd'); INSERT INTO "foo" VALUES('sdfasd'); INSERT INTO "foo" VALUES('sdfasd'); INSERT INTO "foo" VALUES('sdfasd'); COMMIT;Then add the model:
$ ./script/testapplication_create.pl model AppDB CDBI dbi:SQLite:/path/to/TestApplication/db/db.sqliteOnce this is done automagically it's helpful for portability to kill that hardwired path to the database - modify the dsn in the created
AppDB.pm
file from:
dsn => 'dbi:SQLite:/path/to/db.sqlite',to:
dsn => 'dbi:SQLite:' . TestApplication->path_to('db') . '/db.sqlite',And that's it... No really... To access the Model inside the application use the
model
method:
my $foo_model = $c->model('AppDB::Foo'); my @foo_rows = $foo_model->retrieve_all();
And now for a super-short article with a fast answer to a problem - on the other hand, it's a problem I really needed a quick answer to the other day that I couldn't find. So here's the article:
This can be very useful because it's sometimes very helpful to know which of the installed versions of a module are being used - in my case I needed to build a set of libraries to deploy onto a server on which I could not easily build libraries. The problem was finding out when my script was quietly grabbing a module from the core perl installation instead of my library bundle.
A couple of interesting discussions on PerlMonks on the matter:
There were a couple of different approaches discussed, the most complex of which were ''re-following'' the @INC array to try and find which library ''would'' be used. The problem with that approach is that it's a guess about what will be used rather than a report of what was used.
It turns out there's a very very simple way...
One of the Perl predefined variables is %INC (not to be confused with the library search path @INC).
As per the perldoc:
%INC The hash %INC contains entries for each filename included via the "do", "require", or "use" operators. The key is the filename you specified (with module names con- verted to pathnames), and the value is the location of the file found. The "require" operator uses this hash to determine whether a particular file has already been included. If the file was loaded via a hook (e.g. a subroutine reference, see "require" in perlfunc for a description of these hooks), this hook is by default inserted into %INC in place of a filename. Note, however, that the hook may have set the %INC entry by itself to provide some more specific info.
So to get a run-time report of what modules are in use, and where the source files were, just print out %INC:
1 use Data::Dumper; 2 print Data::Dumper:;Dumper(\%INC);
For example:
1 $ perl -MData::Dumper -MEnglish -MCGI -e 'print Data::Dumper::Dumper(\%INC)' 2 $VAR1 = { 3 'warnings/register.pm' => '/usr/lib/perl5/5.8.6/warnings/register.pm', 4 'bytes.pm' => '/usr/lib/perl5/5.8.6/bytes.pm', 5 'Carp.pm' => '/usr/lib/perl5/5.8.6/Carp.pm', 6 'XSLoader.pm' => '/usr/lib/perl5/5.8.6/i386-linux-thread-multi/XSLoader.pm', 7 'English.pm' => '/usr/lib/perl5/5.8.6/English.pm', 8 'Exporter/Heavy.pm' => '/usr/lib/perl5/5.8.6/Exporter/Heavy.pm', 9 'vars.pm' => '/usr/lib/perl5/5.8.6/vars.pm', 10 'strict.pm' => '/usr/lib/perl5/5.8.6/strict.pm', 11 'Exporter.pm' => '/usr/lib/perl5/5.8.6/Exporter.pm', 12 'constant.pm' => '/usr/lib/perl5/5.8.6/constant.pm', 13 'warnings.pm' => '/usr/lib/perl5/5.8.6/warnings.pm', 14 'CGI/Util.pm' => '/usr/lib/perl5/5.8.6/CGI/Util.pm', 15 'overload.pm' => '/usr/lib/perl5/5.8.6/overload.pm', 16 'CGI.pm' => '/usr/lib/perl5/5.8.6/CGI.pm', 17 'Data/Dumper.pm' => '/usr/lib/perl5/5.8.6/i386-linux-thread-multi/Data/Dumper.pm' 18 };
Here's the perldoc on PerlDoc:perlvar.
Of course, there must be a gotch...
Modifying %INC is a common way to trick Perl into believing that a module has already been loaded (for instance when using something like Test::MockObject) but when that happens the hash value set is usually not a path to a file. That said - this method is not going to be all that reliable if used in an environment in which munging of %INC is going on.