pericmd 047: Special arguments (1): dry_run

In Rinci, function can express in its metadata that it supports various features or options. These feature-/option-related information will later be passed back to the function during function call in the form of special arguments. These arguments are prefixed with “-” (dash) with predefined names and values, and will only be passed if the function already expresses the support, and if the function accepts named arguments (as hash or hashref).

There are several such special arguments, one that I will cover today is -dry_run.

A function can express that it supports dry-run (simulation) mode, via the dry_run feature inside the features property in the Rinci function metadata:

$SPEC{delete_files} = {
    v => 1.1,
    args => {
        ...
    },
    features => {
        dry_run => 1,
    },
}

The special argument -dry_run need not be declared in the args property. It will automatically be passed when program is run in dry-run mode.

In Perinci::CdmLine, a common command-line option --dry-run will automatically be added if function supports dry_run feature. This means, if user passes --dry-run (or, alternatively, setting DRY_RUN environment variable to true), Perinci::CmdLine will call the function with -dry_run => 1.

If function is passed -dry_run => 1 in the arguments, it should perform the operation but without actually doing it. Lots of programs have this feature, like rsync, make, or svn merge (note: git merge also supports dry-run operation but with options named --no-commit --no-ff instead of --dry-run. They are useful for testing/trial, especially when the associated operation is rather dangerous (like deleting stuffs or sending mass email).

We could, of course, manually define a dry_run argument ourselves. But the advantage of specifying the dry_run feature instead is, aside from standardization and automatic addition of –dry-run and DRY_RUN parsing, is that in transactions, the dry-run functions can have special treatment. We will cover transaction in the future.

Here’s the full example:

#!/usr/bin/env perl

use 5.010;
use strict;
use warnings;
use Log::Any '$log';

use Perinci::CmdLine::Any;

our %SPEC;

$SPEC{delete_files} = {
    v => 1.1,
    args => {
        'file' => {
            schema => ['array*', of=>'str*', min_len=>1],
            req => 1,
            pos => 0,
            greedy => 1,
        },
    },
    features => {dry_run=>1},
};
sub delete_files {
    my %args = @_;
    my $verbose = $args{verbose};

    my $num_success = 0;
    my $num_fail = 0;
    for my $file (@{$args{file}}) {
        $log->infof("Deleting %s ...", $file);
        next if $args{-dry_run};
        if (unlink $file) {
            $num_success++;
        } else {
            $num_fail++;
            $log->warnf("Can't delete %s: %s", $file, $!);
        }
    }

    if ($num_fail == 0) {
        [200, "OK"];
    } elsif ($num_success == 0) {
        [500, "All failed"];
    } else {
        [200, "Some failed"];
    }
}

Perinci::CmdLine::Any->new(url=>'/main/delete_files', log=>1)->run;
% mkdir test
% cd test
% touch file1 file2 file3; mkdir dir1 dir2
% ls
dir1/  dir2/  file1  file2  file3

% ../delete-files --dry-run f*
[pericmd] Dry-run mode is activated
delete-files: Deleting file1 ...
delete-files: Deleting file2 ...
delete-files: Deleting file3 ...
% ls
dir1/  dir2/  file1  file2  file3

% ../delete-files --verbose f*
delete-files: Deleting dir1 ...
delete-files: Can't delete dir1: Is a directory
delete-files: Deleting dir2 ...
delete-files: Can't delete dir2: Is a directory
delete-files: Deleting file1 ...
delete-files: Deleting file2 ...
delete-files: Deleting file3 ...
% ls
dir1/  dir2/

Leave a comment