NAME

Sub::Spec::Clause::features - Specify subroutine features

VERSION

version 0.10

SYNOPSIS

In your spec:

features => {
    FEATURE => VALUE,
    ...
}

DESCRIPTION

The 'features' clause allows subroutines to express their features. Current known features are:

reverse => 1

Specifies that subroutine supports reverse operation. To reverse, caller can add special argument '-reverse'. For example:

$SPEC{triple} = {args=>{num=>'num*'}, features=>{reverse=>1}};
sub triple {
    my %args = @_;
    my $num  = $args{num};
    [200, "OK", $args{-reverse} ? $num/3 : $num*3];
}

triple(num=>12);              # => 36
triple(num=>12, -reverse=>1); # =>  4

undo => 1

Specifies that subroutine supports undo operation. Undo is like 'reverse', but before doing undo you can save undo information first and then in the undo phase you use the previously saved undo information.

To undo, caller will add special argument '-undo' => 1, as well as '-state' => $state. $state is an state object which must support these operations: get([\%opts, ]$key), set([\%opts, ]$key => $val[, $key2 => $val, ...]), and delete([\%opts, ]$key[, $key2, ...]).

Example:

use Cwd qw(abs_path);
use File::Slurp;

$SPEC{lc_file} = {args=>{path=>'str*'}, features=>{undo=>1}};
sub triple {
    my %args  = @_;
    my $path  = $args{path};
    my $undo  = $args{-undo};
    my $state = $args{-state};

    $path = abs_path($path)
        or return [500, "Can't get file absolute path"];

    if ($undo) {
        # we did not lc_file() the file previously
        my $st = $state->load($path)
            or return [412, "No undo information"]
        write_file $path, $st->{content};
        utime undef, $st->{mtime}, $path;
    } else {
        my @st = stat($path)
            or return [500, "Can't stat file"];
        my $content = read_file($path);
        $state->save($path=>{mtime=>$st[9], content=>$content});
        write_file $path, lc($content);
    }
    [200, "OK"];
}

So if you perform a series of operations, where each operation is a call to a subroutine which supports undo, all you need as an undo stack is just a list of subroutine names and arguments. To perform undo, you just call each subroutine in reverse order, and supplying -undo => 1 argument to each call:

To do stuffs:

f1(\%args1, -undo=>0, -state=$state);
f2(\%args2, -undo=>0, -state=$state);
f3(\%args3, -undo=>0, -state=$state);
...

To undo:

...
f3(\%args3, -undo=>1, -state=>$state);
f2(\%args2, -undo=>1, -state=>$state);
f1(\%args1, -undo=>1, -state=>$state);

dry_run => 1

Specifies that subroutine supports dry-run (simulation) mode. Example:

use Log::Any '$log';

$SPEC{rmre} = {
    summary  => 'Delete files in curdir matching a regex',
    args     => {re=>'str*'},
    features => {dry_run=>1}
};
sub rmre {
    my %args    = @_;
    my $re      = qr/$args{re}/;
    my $dry_run = $args{-dry_run};

    opendir my($dir), ".";
    while (my $f = readdir($dir)) {
        next unless $f =~ $re;
        $log->info("Deleting $f ...");
        next if $dry_run;
        unlink $f;
    }
    [200, "OK"];
}

pure => 1

Specifies that subroutine is "pure" and has no "side effects" (these are terms from functional programming / computer science). Having a side effect means changing something, somewhere (e.g. setting the value of a global variable, modifies its arguments, writing some data to disk, changing system date/time, etc.) Specifying a function as pure means, among others:

  • the function needs not be involved in undo operation;

  • you can safely include it during dry run;

TODO

Some features which might benefit from standardization: transaction/atomicity, i18n (is translatable, supported languages, locales).

SEE ALSO

Sub::Spec

AUTHOR

Steven Haryanto <stevenharyanto@gmail.com>

COPYRIGHT AND LICENSE

This software is copyright (c) 2011 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.