Wednesday, September 23, 2009

Statistic Calculator: Using the system

[Original spanish source]
In the last article I told you we'll add a manual to our calculator, let's see some ways to interact with the Perl documentation system to achieve this.
The command to look at calculator's documentation will be "man", and as I assume most people should have used perldoc, I shall start by using this command to display a manual.
Perl has long has the ability to execute system commands in several ways, one of which is the "`" operator, if you write something like:
1  my @out = `ls -l`
the array @out will end with each of the output lines of the command, you can also execute commands and use its output as input to a program using open:
1 open $fd, "-|", "ls -ls" or die "Error: $!"
2 while ( readline $fd ) {
3     # $_ contiene una lĂ­nea de la salida del comando
4 }
which is surprising, though not highly recommended in these days, although I use it over and over at work, where I solve lots of things just doing little programs in Perl.
But today I am interested in the "system" function that I will use as the first way to add a manual for our calculator. Since I don' t want to complicate the issue showing how to write a manual, I will use the Statistics::Descriptive's manual, the solution is to add a line to last article's program:
21  when ("man")  { system("perldoc Statistics::Descriptive") }
and that's it, such is Perl, there is nothing easier, some may say it is dirty, but it was definitively easy. When we use "system" perl sends the command directly to the shell, so is better to use it in this way:
21  when ("man")  { system("/usr/bin/perldoc", "Statistics::Descriptive") }
when system receives a list, perl executes the first element and pass the remaining ones as arguments, avoiding some security glitches that could occur, but the command isn't searched through the PATH, so you must pass the full path to the command executable.
It is not a big surprise that the perldoc command is also written in Perl, so we probably can reuse the code for this program in our calculator. Looking into the program you will realize that perldoc is a very simple program, in fact, the two important lines are:
1 use Pod::Perldoc;
2 Pod::Perldoc->run();
So all the perldoc's functionality is packed inside a Perl object!, this is a major pattern of Perl culture, allowing any application to be easily reused by another, and that is exactly what we want do, unfortunately someone forgot to document Pod::Perldoc so I got to look at the source code for hints about how may I integrate it into the calculator, I came up with the following:
21  when ("man") { Pod::Perldoc->new(args => ["Statistics::Descriptive"])->process }
and of course we must declare the use of the class, which I did at the beginning of the program:
 6 use Pod::Perldoc;
The real job was to learn how Pod::Perldoc works and it took me less than 2 minutes to get there using the excelent perl debugger.
Finally I took some time to refactor the code a little bit, improve the "help" command, and left the full program like this:
 1 #!/usr/bin/perl
 2 
 3 use Modern::Perl;
 4 use Scalar::Util qw( looks_like_number );
 5 use Statistics::Descriptive;
 6 use Pod::Perldoc;
 7 
 8 use constant SYNTAX_ERROR => "Error: tipee 'help' para ayuda";
 9 
10 my %FUNCS = map { $_ => 1 } qw( sum mean count variance standard_deviation
11     min mindex max maxdex sample_range median harmonic_mean geometric_mean
12     mode trimmed_mean clear );
13 
14 my @COMMANDS = qw( exit quit help man );
15 
16 sub help {
17     say "Comandos: " . join( ", ", @COMMANDS );
18     say "Funciones: " . join( ", ", keys %FUNCS );
19 }
20 
21 sub man {
22     Pod::Perldoc->new(args => \@_)->process
23 }
24 
25 my $s = Statistics::Descriptive::Full->new();
26 while (1) {
27     print "Listo> ";
28     my $command = readline(STDIN) // last;
29     $command =~ s/^\s+//; $command =~ s/\s+$//;
30     given ($command) {
31         when ( looks_like_number($_) ) { $s->add_data($command) }
32         when (%FUNCS)                  { say "$command = " . $s->$command }
33         when ("man")                   { man "Statistics::Descriptive" }
34         when ( [ "exit", "quit" ] )    { last }
35         when ("help")                  { help }
36         default                        { say SYNTAX_ERROR }
37     }
38 }
In the next article I will add more features to make calculator more user friendly.

No comments:

Post a Comment