Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Saturday February 14, 2004
06:14 AM

Scripts as classes

[ #17414 ]

I have been writing a lot of scripts lately---things that I have always wanted to have but never had the time to think about. Now I have lots of time because maintaining my modules while in Iraq has been such a pain (going from a decent development process to this travesty is more than I can bear, and, having only about two months to my return, I have given up for the time being, having made way to many stupid mistakes).

Of course, I have the test bug, and because I know Andy Lester will make fun of me if I do not write tests for my code, I have been writing test suites for my scripts.

This creates a small problem: how do I test only parts of the script, like the subroutines, to make sure they are doing the right thing?

I could put all of the subroutines in a library or separate module, but then it turns into two (or more) files instead of one, and I do not want that for a script.

I could make the entire script a class, and add a run() method to the class, which is sort of the same thing that I see beginning C students do in their main().

int main ( ... )
    {
    init();
    call_this();
    now_this();
    clean_up();
    }

A couple of modules do this. You have probably used one of them:

% perl -MCPAN -e shell

I want to get that same effect with a script, but without as much typing.

I have been musing on some way for the script to figure out how it was called, and if it thinks it was called as a script, automatically invoke its run() method. I have not tried anything, but an END block may be useful since everything else that needs to happen would have already happened. If the script thinks it was loaded through use() or require(), then it acts like a module.

Now, once the script has a class and methods, I should be able to test those individually and independently like any other class. I can even test the whole script by calling it in the right way.

For instance, I have an iPhoto shell that I created in Perl. Everything is a method, and it reads commands then dispatches them. Fine. However, it is also a module, so I can include it inside other scripts and programatically use it. The problem is that I have to know a little bit about what is happening to make it run as a script, and I think that is probably going to be unacceptable to the workaday Perl practitioner. I want it to act like a script when I use it like a script, and act like a module when I use it like a module, and I want to be able to test it both ways.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • You might want to investigate the $^S variable, or the CHECK blocks (which are run at the end of the main compilation phase, so they will never be run in a script which is loaded at run time with require.)

    (I notice that this problem has already been solved more or less nicely in Python and Java.)

  • Can you not just do:


    if (!caller()) {
        # ...
    }


    There is no caller in the case of the script.
    • caller() is what I would use too. Just a bare:
      run() unless caller;
    • Yup, that's exactly how diagnostics.pm becomes splain, which is what I think brian is considering.

    • I was thinking about that too, but I just hadn't tried it. I have to sit at a desk for 12 hours tomorrow, so I should have plenty of time to figure it out. I did not know about splain, so I will have to see what diagnostics.pm does too.

      Thanks to everyone for ideas. :)
    • This sounds very similiar to the python idiom:
      if __name__ == '__main__':
          main()

      -Dom

  • A simple "use Class::Script" inserts the &run call, passing @ARGV so it can be used from a script too.
    package Class::Script;

    use Filter::Simple;

    FILTER { $_ .= "\nrun(\@ARGV) unless caller;\n1;"; };

    1;