0

I am creating a set of small Perl programs which will be run like a CLI. I want all of them to share some features, eg. same input/output format (probably JSON). I also want each component to be black-box testable with mock arguments. This is what I have so far:

  • A number of modules actionA.pm, actionB.pm... actionZZZ.pm, one for each component/operation. All of them will use Moose and should have a sub run {...} which will receive a data hasref/object with the operation's input parameters, and any handle (database, filesystem, whatever) they need to use, and return a data hashref/object with the operation's output parameters.

  • A Moose role which requires 'run' and is consumed by every actionX.pm module, forcing them to implement run.

  • Additional Moose roles and/or superclasses for certain subsets of the actionX.pm modules, so they can consume them to specify wether they need their run method to be provided with a database handler, a filesystem handler, etc.

  • A common Moose superclass (don't know if this really needs to be either Moose nor a superclass, just want this to be a fairly simple one-liner on each actionX.pm), let's call it abstractAction.pm, for all of the actionX.pm classes, which converts them into some sort of Modulino (see below).

What I want to achieve with this is that, when I run perl actionX.pm arguments from command line, some code in abstractAction.pm will:

  1. Read JSON/whatever from @ARGV and convert it to Perl data structures.

  2. This part is not clear: know that it was called from/as actionX, so it knows that if actionX consumes role needDB, it will need to get a database handler to pass it to run, etc.

  3. Call actionX->run() passing the data object and any handler obtained at step 2.


I tried using both run() and __PACKAGE__->run() in abstractAction.pm, but it calls the superclass method, not the child's. Is there any way I can know the child's package name from the parent? That doesn't even sound good. Is there a better way to do what I am trying to achieve?

I know I could simply do perl abstractAction.pm actionX arguments instead of perl actionX.pm arguments and call it a day, but I'd rather not.

willyjoker
  • 787
  • 6
  • 16
  • 1
    Your question title and the body of the post don't really fit together. Please try to make it more clear what your actual question is. Also note that in Perl package names are typically written in camel case with a capital first letter. `ActionA.pm` would be correct. I would go with `Action.pm` as the base class and `Action::A`, `Action::B` and so on for the actual action classes. You might also want to take a look at https://metacpan.org/release/MooseX-App. – simbabque Feb 02 '18 at 18:26
  • @simbabque: I changed the question title, I hope now it fits better. You're right about the naming convention. This is not actual code, I just made example names up as I was writing. – willyjoker Feb 02 '18 at 18:43
  • @simbabque I already looked at MooseX::App and CLI::Framework, but it looks to me that these are good for writing one program with many commands. I need to write many separate programs with just one (implicit) command each, even if they all share some code. – willyjoker Feb 02 '18 at 18:49

1 Answers1

1

Fundamentally, you'll need to call run as a method, using the name of the child class (if it's a class method) or an object of the child class (if it's an instance method) as the invocant.

Calling run as a class method:

ActionX->run(...)

Calling run as an instance method:

ActionX->new(...)->run(...)

The other issue is that you're executing a .pm file, which leads to problems. Load modules, don't execute them. For example,

#!/usr/bin/perl
use strict;
use warnings;

my $action = shift(@ARGV)
   or die("usage\n");

$action =~ /^\w+\z/
   or die("Bad action\n");

my $pkg = $action;   # Or: my $pkg = "...::" . $action;

my $module =~ s{::}{/}g . '.pm';
require $module;

$pkg->new(@ARGV)->run();

If the script is named action, then you can do

action ActionX parameters
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Thanks for your answer @ikegami. As I stated in the last paragraph of the question, I'd rather not have the action name passed as a parameter. I'd prefer getting the action name from the module which is being executed. – willyjoker Feb 02 '18 at 20:42
  • Look again. The parameter is the name of the module. Instead of `perl actionX.pm arguments`, use `action actionX arguments`. – ikegami Feb 02 '18 at 20:44