File Coverage

blib/lib/Taskwarrior/Kusarigama/Wrapper.pm
Criterion Covered Total %
statement 61 105 58.1
branch 10 22 45.4
condition 5 11 45.4
subroutine 14 16 87.5
pod 1 3 33.3
total 91 157 57.9


line stmt bran cond sub pod time code
1             package Taskwarrior::Kusarigama::Wrapper;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: interface to the taskwarrior's 'task' command
4             $Taskwarrior::Kusarigama::Wrapper::VERSION = '0.11.0';
5              
6             # TODO use Test::Pod::Snippet for that example ^^^
7              
8              
9 2     2   199061 use 5.20.0;
  2         20  
10              
11 2     2   1003 use IPC::Open3 qw();
  2         7707  
  2         51  
12 2     2   14 use Symbol;
  2         5  
  2         111  
13 2     2   874 use Taskwarrior::Kusarigama::Wrapper::Exception;
  2         5  
  2         57  
14 2     2   875 use Taskwarrior::Kusarigama::Task;
  2         6  
  2         68  
15              
16 2     2   13 use List::Util qw/ pairmap /;
  2         5  
  2         110  
17              
18 2     2   1082 use Moo;
  2         22079  
  2         11  
19              
20 2     2   2916 use experimental 'signatures', 'postderef';
  2         6  
  2         16  
21              
22             has task => (
23             is => 'ro',
24             default => sub { 'task' },
25             );
26              
27             has $_ => (
28             is => 'rw',
29             clearer => 1,
30             ) for qw/ ERR OUT /;
31              
32             our $DEBUG;
33              
34 0     0 0 0 sub RUN($self,$cmd,@args) {
  0         0  
  0         0  
  0         0  
  0         0  
35              
36 0         0 $self->clear_OUT;
37 0         0 $self->clear_ERR;
38              
39 0         0 my( $parts , $stdin ) = $self->_parse_args( $cmd , @args );
40              
41 0         0 my @cmd = ( $self->task , @$parts );
42              
43 0         0 my( @out , @err );
44              
45             {
46              
47 0         0 my ($wtr, $rdr, $err);
  0         0  
48              
49 0         0 local *TEMP;
50              
51 0 0 0     0 if ($^O eq 'MSWin32' && defined $stdin) {
52 0         0 my $file = File::Temp->new;
53 0         0 $file->autoflush(1);
54 0         0 $file->print($stdin);
55 0         0 $file->seek(0,0);
56 0         0 open TEMP, '<&=', $file;
57 0         0 $wtr = '<&TEMP';
58 0         0 undef $stdin;
59             }
60              
61 0         0 $err = Symbol::gensym;
62              
63 0 0       0 print STDERR join(' ',@cmd),"\n" if $DEBUG;
64              
65              
66 0         0 my $pid = IPC::Open3::open3($wtr, $rdr, $err, @cmd);
67              
68 0 0       0 print $wtr $stdin if defined $stdin;
69 0         0 close $wtr;
70              
71 0         0 chomp(@out = <$rdr>);
72 0         0 chomp(@err = <$err>);
73              
74 0         0 waitpid $pid, 0;
75             };
76              
77 0 0       0 print "status: $?\n" if $DEBUG;
78              
79 0 0       0 if ($?) {
80 0         0 die Taskwarrior::Kusarigama::Wrapper::Exception->new(
81             output => \@out,
82             error => \@err,
83             status => $? >> 8,
84             );
85             }
86              
87 0         0 chomp(@err);
88 0         0 $self->ERR(\@err);
89              
90 0         0 chomp(@out);
91 0         0 $self->OUT(\@err);
92              
93 0         0 return @out;
94            
95             }
96              
97 10     10   14 sub _map_to_arg ( $self, $entry ) {
  10         19  
  10         12  
  10         14  
98              
99 10 100       31 if( not ref $entry ) { # simple string
100             # extract the attributes so that they are not dealt
101             # with as part of the definition
102 3         6 my %opts;
103            
104 3         11 while ( $entry =~ s/\b(?<key>[^\s:]+):(?<value>\S+)// ) {
105 2     2   2453 $opts{ $+{key} } = $+{value};
  2         739  
  2         1206  
  0         0  
106             }
107 3         13 return $entry, $self->_map_to_arg(\%opts);
108             }
109              
110 7 50       17 return $entry unless ref $entry eq 'HASH';
111              
112 7 100   4   55 return pairmap { join( ( $a =~ /^rc/ ? '=' : ':' ), $a, $b ) } %$entry;
  4         42  
113             }
114              
115 3     3   259 sub _parse_args($self,$cmd,@args) {
  3         9  
  3         7  
  3         6  
  3         6  
116 3         9 my @command = ( $cmd );
117              
118             # arrayrefs are for pre-command arguments, like
119             # task 123 list => ( 'list', [ 123 ] )
120 3 100 66     26 if( @args and ref $args[0] eq 'ARRAY' ) {
121 2         7 unshift @command, map { $self->_map_to_arg($_) } ( shift @args )->@*;
  5         16  
122             }
123              
124 3         9 my @stdin;
125 3 100 66     20 push @stdin, ${pop @args} if @args and ref $args[-1] eq 'SCALAR';
  1         4  
126              
127 3         12 return ( [ @command, map { $self->_map_to_arg($_) } @args ], @stdin );
  2         7  
128             }
129              
130             sub save {
131 1     1 0 4 my( $self, $task ) = @_;
132              
133 1         665 require JSON;
134              
135 1   50     7690 my $id = $task->{uuid} || '+LATEST';
136              
137 1         6 my $json = JSON::to_json([ $task ]);
138              
139 1         39 $self->RUN('import', \$json );
140              
141 1         5 my ( $new ) = $self->export($id);
142              
143 1         75 return $new;
144             }
145              
146             sub export {
147 0     0 1 0 my( $self, @args ) = @_;
148 0         0 require JSON;
149              
150             return map {
151 0         0 Taskwarrior::Kusarigama::Task->new( $self => $_ )
  0         0  
152             } JSON::from_json( join '', $self->RUN( export => @args ) )->@*;
153             }
154              
155             sub AUTOLOAD {
156 1     1   813 my $self = shift;
157              
158 1         8 (my $meth = our $AUTOLOAD) =~ s/.+:://;
159 1 50       150 return if $meth eq 'DESTROY';
160              
161 0           $meth =~ s/(?<=.)_/-/;
162              
163 0           return $self->RUN($meth, @_);
164             }
165              
166             1;
167              
168             __END__
169              
170             =pod
171              
172             =encoding UTF-8
173              
174             =head1 NAME
175              
176             Taskwarrior::Kusarigama::Wrapper - interface to the taskwarrior's 'task' command
177              
178             =head1 VERSION
179              
180             version 0.11.0
181              
182             =head1 SYNOPSIS
183              
184             use TaskWarrior::Kusarigama::Wrapper;
185              
186             my $tw = TaskWarrior::Kusarigama::Wrapper->new;
187              
188             say for $tw->next( [ '+focus' ] );
189              
190             =head1 DESCRIPTION
191              
192             Inspired by L<Git::Wrapper> (i.e., I lifted and stole
193             the code, and tweaked to work with 'task'). At its core
194             beats a dark AUTOLOAD heart, which convert any method
195             call into an invocation of C<task> with whatever
196             parameters are passed.
197              
198             If the first parameter to be passed to a command is an array ref,
199             it's understood to be a filter that will be inserted before the command.
200             Also, any parameter will be a hahsref, will be also be understood as a
201             key-value pair, and given the right separator (C<=> for C<rc.*> arguments, C<:> for regular ones).
202             For example:
203              
204             $tw->mod( [ '+focus', '+PENDING', { 'due.before' => 'today' } ], { priority => 'H' } );
205             # runs task +focus +PENDING due.before:today mod priority:H
206              
207             =head1 METHODS
208              
209             =head2 export
210              
211             As a convenience, C<export> returns the list of tasks exported (as
212             L<Taskwarrior::Kusarigama::Task> objects) instead than as raw text.
213              
214             =head1 AUTHOR
215              
216             Yanick Champoux <yanick@cpan.org>
217              
218             =head1 COPYRIGHT AND LICENSE
219              
220             This software is copyright (c) 2018, 2017 by Yanick Champoux.
221              
222             This is free software; you can redistribute it and/or modify it under
223             the same terms as the Perl 5 programming language system itself.
224              
225             =cut