package Taskwarrior::Kusarigama::Wrapper;
our $AUTHORITY = 'cpan:YANICK';
# ABSTRACT: interface to the taskwarrior's 'task' command
$Taskwarrior::Kusarigama::Wrapper::VERSION = '0.12.0';
# TODO use Test::Pod::Snippet for that example ^^^
use 5.20.0;
use IPC::Open3 qw();
use Symbol;
use Taskwarrior::Kusarigama::Wrapper::Exception;
use Taskwarrior::Kusarigama::Task;
use List::Util qw/ pairmap /;
use Moo;
use experimental 'signatures', 'postderef';
has task => (
is => 'ro',
default => sub { 'task' },
);
has $_ => (
is => 'rw',
clearer => 1,
) for qw/ ERR OUT /;
our $DEBUG;
sub RUN($self,$cmd,@args) {
$self->clear_OUT;
$self->clear_ERR;
my( $parts , $stdin ) = $self->_parse_args( $cmd , @args );
my @cmd = ( $self->task , @$parts );
my( @out , @err );
{
my ($wtr, $rdr, $err);
local *TEMP;
if ($^O eq 'MSWin32' && defined $stdin) {
my $file = File::Temp->new;
$file->autoflush(1);
$file->print($stdin);
$file->seek(0,0);
open TEMP, '<&=', $file;
$wtr = '<&TEMP';
undef $stdin;
}
$err = Symbol::gensym;
print STDERR join(' ',@cmd),"\n" if $DEBUG;
my $pid = IPC::Open3::open3($wtr, $rdr, $err, @cmd);
print $wtr $stdin if defined $stdin;
close $wtr;
chomp(@out = <$rdr>);
chomp(@err = <$err>);
waitpid $pid, 0;
};
print "status: $?\n" if $DEBUG;
if ($?) {
die Taskwarrior::Kusarigama::Wrapper::Exception->new(
output => \@out,
error => \@err,
status => $? >> 8,
);
}
chomp(@err);
$self->ERR(\@err);
chomp(@out);
$self->OUT(\@err);
return @out;
}
sub _map_to_arg ( $self, $entry ) {
if( not ref $entry ) { # simple string
# extract the attributes so that they are not dealt
# with as part of the definition
my %opts;
while ( $entry =~ s/\b(?<key>[^\s:]+):(?<value>\S+)// ) {
$opts{ $+{key} } = $+{value};
}
return $entry, $self->_map_to_arg(\%opts);
}
return $entry unless ref $entry eq 'HASH';
return pairmap { join( ( $a =~ /^rc/ ? '=' : ':' ), $a, $b ) } %$entry;
}
sub _parse_args($self,$cmd,@args) {
my @command = ( $cmd );
# arrayrefs are for pre-command arguments, like
# task 123 list => ( 'list', [ 123 ] )
if( @args and ref $args[0] eq 'ARRAY' ) {
unshift @command, map { $self->_map_to_arg($_) } ( shift @args )->@*;
}
my @stdin;
push @stdin, ${pop @args} if @args and ref $args[-1] eq 'SCALAR';
return ( [ @command, map { $self->_map_to_arg($_) } @args ], @stdin );
}
sub save {
my( $self, $task ) = @_;
require JSON;
my $id = $task->{uuid} || '+LATEST';
my $json = JSON::to_json([ $task ]);
$self->RUN('import', \$json );
my ( $new ) = $self->export($id);
return $new;
}
sub export {
my( $self, @args ) = @_;
require JSON;
return map {
Taskwarrior::Kusarigama::Task->new( $self => $_ )
} JSON::from_json( join '', $self->RUN( export => @args ) )->@*;
}
sub AUTOLOAD {
my $self = shift;
(my $meth = our $AUTOLOAD) =~ s/.+:://;
return if $meth eq 'DESTROY';
$meth =~ s/(?<=.)_/-/;
return $self->RUN($meth, @_);
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Taskwarrior::Kusarigama::Wrapper - interface to the taskwarrior's 'task' command
=head1 VERSION
version 0.12.0
=head1 SYNOPSIS
use TaskWarrior::Kusarigama::Wrapper;
my $tw = TaskWarrior::Kusarigama::Wrapper->new;
say for $tw->next( [ '+focus' ] );
=head1 DESCRIPTION
Inspired by L<Git::Wrapper> (i.e., I lifted and stole
the code, and tweaked to work with 'task'). At its core
beats a dark AUTOLOAD heart, which convert any method
call into an invocation of C<task> with whatever
parameters are passed.
If the first parameter to be passed to a command is an array ref,
it's understood to be a filter that will be inserted before the command.
Also, any parameter will be a hahsref, will be also be understood as a
key-value pair, and given the right separator (C<=> for C<rc.*> arguments, C<:> for regular ones).
For example:
$tw->mod( [ '+focus', '+PENDING', { 'due.before' => 'today' } ], { priority => 'H' } );
# runs task +focus +PENDING due.before:today mod priority:H
=head1 METHODS
=head2 export
As a convenience, C<export> returns the list of tasks exported (as
L<Taskwarrior::Kusarigama::Task> objects) instead than as raw text.
=head1 AUTHOR
Yanick Champoux <yanick@cpan.org>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2019, 2018, 2017 by Yanick Champoux.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut