Scripts as classes

brian_d_foy on 2004-02-14T11:14:35

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.


main programs

rafael on 2004-02-14T12:50:02

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.)

caller?

james on 2004-02-14T13:05:20

Can you not just do:


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


There is no caller in the case of the script.

Re:caller?

bart on 2004-02-14T13:36:01

caller() is what I would use too. Just a bare:
run() unless caller;

Re:caller?

VSarkiss on 2004-02-14T16:05:15

Yup, that's exactly how diagnostics.pm becomes splain, which is what I think brian is considering.

Re:caller?

brian_d_foy on 2004-02-14T22:10:39

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. :)

Re:caller?

Dom2 on 2004-02-15T10:05:32

This sounds very similiar to the python idiom:
if __name__ == '__main__':
    main()

-Dom

A source filter implementation

nmueller on 2004-02-14T18:01:09

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;