NAME Sub::Spec::Runner - Run subroutines VERSION version 0.23 SYNOPSIS In YourModule.pm: package YourModule; use 5.010; our %SPEC; $SPEC{a} = { deps => {run_sub=>'b'}, ... }; sub a { my %args = @_; say "a"; [200, "OK"] } $SPEC{b} = { deps => {run_sub=>'c'}, ... }; sub b { my %args = @_; say "b"; [200, "OK"] } $SPEC{c} = { deps => {all=>[{run_sub=>'d'}, {run_sub=>'e'}]}, ... }; sub c { my %args = @_; say "c"; [200, "OK"] } $SPEC{d} = { deps => {run_sub=>'e'}, ... }; sub d { my %args = @_; say "d"; [200, "OK"] } $SPEC{e} = { ... }; sub e { my %args = @_; say "e"; [200, "OK"] } In main module: use Sub::Spec::Runner; my $runner = Sub::Spec::Runner->new(load_modules=>0); $runner->add('YourModule::a'); $runner->run; Will output: e d c b a DESCRIPTION NOTICE: This module and the Sub::Spec standard is deprecated as of Jan 2012. Rinci is the new specification to replace Sub::Spec, it is about 95% compatible with Sub::Spec, but corrects a few issues and is more generic. "Perinci::*" is the Perl implementation for Rinci and many of its modules can handle existing Sub::Spec sub specs. This class "runs" a set of subroutines. "Running" a subroutine basically means loading the module and calling the subroutine, plus a few other stuffs. See run() for more details. This module uses Log::Any for logging. This module uses Moo for object system. This module parses the 'run_sub' deps clause in Sub::Spec spec. It allows specifying dependency of a subroutine to another subroutine. It accept fully qualified subroutine name as value, or a 2-element array containing the subroutine name and its arguments. The depended subroutine will be add()-ed to the runner. Example: run_sub => 'main::func2' run_sub => ['My::Other::func', {arg1=>1, arg2=>2}] ATTRIBUTES common_args => HASHREF Arguments to pass to each subroutine. Note that each argument will only be passed if the 'args' clause in subroutine spec specifies that the sub accepts that argument, or if subroutine doesn't have an 'args' clause. Example: package Foo; our %SPEC; $SPEC{sub0} = {}; sub sub0 { ... } $SPEC{sub1} = {args=>{}}; sub sub1 { ... } $SPEC{sub2} = {args=>{foo=>"str"}}; sub sub2 { ... } $SPEC{sub3} = {args=>{bar=>"str"}}; sub sub2 { ... } $SPEC{sub4} = {args=>{foo=>"str", bar=>"str"}}; sub sub4 { ... } package main; use Sub::Spec::Runner; my $runner = Sub::Spec::Runner->new(common_args => {foo=>1, foo=>2}); $runner->add("Foo::sub$_") for (1 2 3 4); $runner->run; Then only sub0 and sub4 will receive the 'foo' and 'bar' args. sub1 won't receive any arguments, sub2 will only receive 'foo', sub3 will only receive 'bar'. load_modules => BOOL (default 1) Whether to load (require()) modules when required (in add()). You can turn this off if you are (or want to make) sure that all the subroutines to be run are already loaded. stop_on_sub_errors => BOOL (default 1) When run()-ning subroutines, whether a non-success return value from a subroutine stops the whole run. If turned off, run() will continue to the next subroutine. Note that you can override what constitutes a success return value by overriding success_res(). allow_add_same_sub => BOOL (default 0) If this attribute is set to 0 (the default), then if you add() the same subroutine more than once, even with different argument, then the subsequent add will be ignored. For example: $runner->add('sub1', {arg1=>1}); # 'sub1' added $runner->add('sub1', {arg1=>2}); # not added If this attribute is set to 1, then you can add() the same subroutine more than once but with different arguments. $runner->add('sub1', {arg1=>1}); # 'sub1' added with args: {arg1=>1} $runner->add('sub1', {arg1=>2}); # 'sub1' added again with args: {arg1=>2} order_before_run => BOOL (default 1) Before run() runs the subroutines, it will call order_by_dependencies() to reorder the added subroutines according to dependency tree (the 'sub_run' dependency clause). You can turn off this behavior by setting this attribute to false. If this attribute is a true but negative value, then the order will be reversed. undo => BOOL (default undef) If set to 0 or 1, then these things will happen: 1) Prior to running, all added subroutines will be checked and must have 'undo' or 'reverse' feature, or are 'pure' (see the 'features' clause in Sub::Spec for more details on specifying features). 2) '-undo_action' and '-undo_data' special argument will be given with value 0/1 to each sub supporting undo (or '-reverse' 0/1 for subroutines supporting reverse). No special argument will be given for pure subroutines. In summary: setting to 0 means run normally, but instruct subroutines to store undo information to enable undo in the future. Setting to 1 means undo. Setting to undef (the default) means disregard undo stuffs. undo_data_dir => STRING (default ~/.undo) Location to put undo data. See save_item_undo_data() for more details. dry_run => BOOL (default 0) If set to 0 or 1, then these things will happen: 1) Prior to running, all added subroutines will be checked and must have 'dry_run' feature, or are 'pure' (see 'features' clause in Sub::Spec for more details on specifying features). 2) '-dry_run' special argument will be given with value 0/1 to each sub supporting dry run. No special argument will be given for pure subroutines. last_res => RESULT This attribute will be set by run() to be the result of each item after running each item. METHODS $runner->get_spec($subname) => SPEC Get spec for sub named $subname. Will be called by add(). Can be overriden to provide your own specs other than from %SPECS package variables. $runner->add($subname[, $args]) Add subroutine to the queue of subroutines to be run. Example: $runner->add('Package::subname'); $runner->add('Package::anothersub', {arg=>1}); Will first load the module (unless load_modules attribute to false). Then attempt to get the sub spec by reading the %SPEC package var (this behavior can be changed by overriding get_spec()). Will die if cannot get spec. After that, it will check dependencies (the 'deps' spec clause) and die if some dependencies are unmet. All subroutine names mentioned in 'run_sub' dependency clause will also be add()-ed automatically, recursively. If the same subroutine is added twice, then it will only be queued once, unless allow_add_same_sub attribute is true, in which case duplicate subroutine name can be added as long as the arguments are different. $runner->empty() Empty all items in the queue. This is the opposite of what add() does. $runner->order_by_dependencies() Reorder set of subroutines by dependencies. Normally need not be called manually since it will be caled by run() prior to running subroutines, but if you turn off 'order_before_run' attribute, you'll need to call this method explicitly if you want ordering. Will return a true value on success, or false if dependencies cannot be resolved (e.g. there is circular dependency). $runner->todo_items() => ARRAYREF Return the items in queue not yet run, in order. Previously run subroutines can belong to this list again if repeat()-ed. $runner->done_items() => ARRAYREF Return the items in queue already run. Never-run subroutines can belong to this list too if skip()-ed. $runner->run(%opts) => [STATUSCODE, ERRMSG, RESULT] Run (call) the queue of subroutines previously added by add(). First it will check 'undo' attribute. If defined, then all added subroutines are required to have undo/reverse/pure feature or otherwise run() will immediately return with error 412. Then it will check 'dry_run' attribute. If true, then all added subroutines are required to have dry_run/pure feature or otherwise run() will immediately return with error 412. After that it will call order_by_dependencies() to reorder the subroutines according to dependency order. This can be turned off via setting 'order_before_run' attribute to false. After that, it will call pre_run(), which you can override. pre_run() must return true, or run() will immediately return with 412 error. Then it will call each item in the queue successively. Each subroutine will be called with arguments specified in 'common_args' attribute and args specified in add(), with one extra special argument, '-runner' which is the runner object. Before running a subroutine: pre_sub() will be called. It must return true, or run() will immediately return with 500 error. If 'undo' attribute is set to true, get_item_undo_data() will be called to get undo data (see get_item_undo_data() for more details on customizing storage of undo data). The subroutine being run can see the status/result of other subroutines by calling $runner->done($subname), $runner->result($subname). It can share data by using $runner->stash(). It can also repeat/skip some subroutines by calling $runner->skip(), skip_all(), repeat(), repeat_all(), branch_done(). It can jump to other subroutines using $runner->jump(). See the respective method documentation for more details. After running a subroutine: Runner will store the return value of each subroutine. Exception from subroutine will be trapped by eval() and upon exception return value of subroutine is assumed to be 500. If subroutine runs successfully: if 'undo' attribute is defined and false (meaning normal run but save undo information), save_item_undo_data() will be called to save undo information (see save_item_undo_data() for more details on customizing storage of undo data). If 'undo' attribute is true, remove_item_undo_data() will be called to remove undo information. After that, post_sub() will be called. It must return true, or run() will immediately return with 500 error. If 'stop_on_sub_errors' attribute is set to true (the default), then if the subroutine returns a non-success result, run() will immediately exit with that result. The meaning of subroutine's success can be changed by overriding success_res() (by default, all 2xx and 3xx are considered success). After all subroutines have been run (or skipped), run() will call post_run() which must return true. Otherwise run() will immediately exit with 500 status. After that, run() will return the summary in RESULT (number of subroutines run, skipped, successful, etc). It will return status 200 if there are at least one subroutine returning success, or 500 otherwise. Options for run(), %opts: * use_last_res => BOOL Default is false. If set to true, then instead of returning statistics/summary result, run() will return the last item's result. $runner->format_subname($subname) => STR Can be used to format info log message: "Running XXX ..." when about to run a subroutine inside run(). Default is "Running Package::bar ..." (just the subname) $runner->success_res($res) => BOOL By default, all responses with 2xx and 3xx are assumed as a success. You can override this. $runner->pre_run() => BOOL See run() for more details. Can be overridden by subclass. $runner->pre_sub($subname, $args) => BOOL See run() for more details. Can be overridden by subclass. $runner->get_item_undo_data($subname, $args) => $undo_data Get item's undo data. If 'undo' attribute is set to true, this method will be called by run() before running each item. $runner->save_item_undo_data($subname, $args, $undo_data) Save item's undo data. If 'undo' attribute is defined, this method will be called by run() after each item is called, to save the resulting undo data. The default implementation saves undo data in YAML files under directory specified by 'undo_data_dir', one file per subname, the file being named .yaml ("::" replaced with by "." because it is not valid in some filesystems). The data being stored is an array of hashes ("records"). Each record contains: {args => ..., undo_datas => [step, ...]}. Because the Sub::Spec specification allows different undo data for different arguments, the YAML file store undo data for each different argument in each record. To store undo data in other location, you can set the 'undo_data_dir' or alternatively override the *_undo_data() methods and provide your own implementation. $runner->get_sub_undo_data($subname) => $records Get all undo data records for a certain subname. See save_item_undo_data() for more details. $runner->remove_item_undo_data($subname, $args) Remove an item's undo data. If 'undo' attribute is set to true, will be called by run() after a successful undo. $runner->remove_sub_undo_data($subname, $args) Remove a sub's undo data. $runner->post_sub($subname, $args) => BOOL See run() for more details. Can be overridden by subclass. $runner->post_run() => BOOL See run() for more details. Can be overridden by subclass. $runner->result($subname[, $args]) => RESULT Return the result of run subroutine named SUBNAME. If subroutine is not run yet, will return undef. If there are multiple items matching $subname, only the first item's result will be returned. $runner->is_done($subname[, $args]) => BOOL Check whether an item is already run/done. $runner->done(SUBNAME[, ARGS[, VALUE]]) => OLDVAL If VALUE is set, set a subroutine to be done/not done. Otherwise will return the current done status of SUBNAME. SUBNAME can also be a regex, which means all subroutines matching the regex. The last SUBNAME's current done status will be returned. $runner->skip(SUBNAME[, ARGS]) Alias for done(SUBNAME, ARGS, 1). $runner->skip_all() Skip all subroutines. $runner->repeat(SUBNAME[, ARGS]) Alias for done(SUBNAME, ARGS, 0). $runner->repeat_all() Repeat all subroutines. $runner->done_branch(SUBNAME, ARGS, VALUE) Just like done(), except that will set SUBNAME *and all its dependants*. Example: if a depends on b and b depends on c, then doing branch_done('c', undef, 1) will also set a & b as done. $runner->skip_branch(SUBNAME[, ARGS]) Alias for done_branch(SUBNAME, ARGS, 1). $runner->repeat_branch(SUBNAME[, ARGS]) Alias for done_branch(SUBNAME, ARGS, 0). $runner->jump($subname, $args) Jump to another item. Can be called in pre_sub() or inside subroutine or post_sub(). $runner->stash(NAME[, VALUE]) => OLDVAL Get/set stash data. This is a generic place to share data between subroutines being run. FAQ What is the point of this module? Sub::Spec allows us to add various useful metadata to subroutines, like dependencies and specific features. Sub::Spec::Runner utilizes this information to make calling subroutines a bit more like running programs/installing software packages, e.g.: * checking requirements prior to calling a subroutine; For example, you can specify that backup_db() requires the program "/usr/bin/mysqldump". * reordering list of subroutines to run according to interdependencies; You can specify that a() and b() must be run before c(), d() must be run before c(), and so on. The runner will automatically resolve dependencies by loading required modules and reorder subroutine execution. * running in dry-run mode; See 'dry_run' attribute for more details. * running in undo mode; See 'undo' attribute for mor details. * summary/statistics; Including the number of subroutines run, number of successes/failures, etc. What are some of the applications for this module? Sub::Spec::CmdLine uses this module in a straightforward way. Sub::Spec::CmdLine allows you to run subroutines from the command-line. Our Spanel project uses this module to run "setuplets", which are hosting server setup routines broken down to smaller bits. Each bit can be run individually (in dry-run and/or undo mode) but dependencies will always be respected (e.g. setuplets A requires running B, so running only A will always run B first). SEE ALSO Sub::Spec AUTHOR Steven Haryanto COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Steven Haryanto. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.