Annotation of todotxt/Text-Todo/bin/todo.pl, Revision 1.8
1.1 andrew 1: #!/usr/bin/perl
1.8 ! andrew 2: # $RedRiver: todo.pl,v 1.7 2010/01/10 23:58:11 andrew Exp $
1.1 andrew 3: ########################################################################
4: # todo.pl *** a perl version of todo.sh. Uses Text::Todo.
5: #
6: # 2010.01.07 #*#*# andrew fresh <andrew@cpan.org>
7: ########################################################################
8: # Copyright 2010 Andrew Fresh, all rights reserved
9: #
10: # This program is free software; you can redistribute it and/or modify
11: # it under the same terms as Perl itself.
12: ########################################################################
13: use strict;
14: use warnings;
15:
16: use Data::Dumper;
1.2 andrew 17:
18: use Getopt::Std;
1.1 andrew 19: use Text::Todo;
20:
21: use version; our $VERSION = qv('0.0.1');
22:
1.2 andrew 23: # option defaults
24: my $config_file = $ENV{HOME} . '/todo.cfg';
1.3 andrew 25: CONFIG: foreach my $f ( $config_file, $ENV{HOME} . '/.todo.cfg', ) {
26: if ( -e $f ) {
27: $config_file = $f;
28: last CONFIG;
29: }
30: }
1.2 andrew 31:
32: my %actions = (
1.3 andrew 33: add => \&add,
34: addto => \&addto,
35: append => \&append,
36: archive => \&archive,
37: command => \&command,
38: del => \&del,
39: depri => \&depri,
40: do => \&mark_done,
41: help => \&help,
1.2 andrew 42: list => \&list,
1.3 andrew 43: listall => \&listall,
44: listcon => \&listcon,
45: listfile => \&listfile,
46: listpri => \&listpri,
47: listproj => \&listproj,
48: move => \&move,
49: prepend => \&prepend,
50: pri => \&pri,
51: replace => \&replace,
52: report => \&report,
1.2 andrew 53: );
54:
55: my %aliases = (
56: a => 'add',
57: app => 'append',
58: rm => 'del',
59: dp => 'depri',
60: ls => 'list',
61: lsa => 'listall',
62: lsc => 'listcon',
63: lf => 'listfile',
1.3 andrew 64: lsp => 'listpri',
1.2 andrew 65: lsprj => 'listproj',
66: mv => 'move',
67: prep => 'prepend',
68: p => 'pri',
69: );
70:
71: my %opts;
72: getopts( '@+d:fhpPntvV', \%opts );
73:
74: my $action = shift @ARGV;
75: if ( $action && $action eq 'command' ) {
76:
77: # We don't support action scripts so . . .
78: $action = shift @ARGV;
79: }
80: if ( $action && exists $aliases{$action} ) {
81: $action = $aliases{$action};
82: }
83:
84: if ( $opts{h} || !$action ) {
85: usage( $opts{h} );
86: }
87:
1.6 andrew 88: my @unsupported = grep { defined $opts{$_} } qw( @ + f h p P t v V );
1.2 andrew 89: if (@unsupported) {
1.3 andrew 90: warn 'Unsupported options: ' . ( join q{, }, @unsupported ) . "\n";
1.2 andrew 91: }
92:
93: if ( $opts{d} ) {
94: $config_file = $opts{d};
95: }
96:
97: if ( exists $actions{$action} ) {
98: my $config = read_config($config_file);
99: my $action = $actions{$action}->( $config, @ARGV );
100: }
101: else {
102: usage();
103: }
104:
1.3 andrew 105: sub add {
1.7 andrew 106: my ( $config, @entry ) = @_;
107: if ( !@entry ) {
1.3 andrew 108: die "usage: todo.pl add 'item'\n";
109: }
110:
1.7 andrew 111: my $entry = join q{ }, @entry;
112:
1.3 andrew 113: my $todo = Text::Todo->new($config);
114: if ( $todo->add($entry) ) {
115: my @list = $todo->list;
116: my $lines = scalar @list;
117:
118: print "TODO: '$entry' added on line $lines\n";
119:
120: return $lines;
121: }
122: die "Unable to add [$entry]\n";
123: }
124:
125: sub addto {
1.7 andrew 126: my ( $config, $file, @entry ) = @_;
127: if ( !( $file && @entry ) ) {
1.3 andrew 128: die "usage: todo.pl addto DEST 'TODO ITEM'\n";
129: }
130:
1.7 andrew 131: my $entry = join q{ }, @entry;
132:
1.3 andrew 133: my $todo = Text::Todo->new($config);
134:
135: $file = $todo->file($file);
136: if ( $todo->addto( $file, $entry ) ) {
137: my @list = $todo->listfile($file);
138: my $lines = scalar @list;
139:
140: print "TODO: '$entry' added to $file on line $lines\n";
141:
142: return $lines;
143: }
144: die "Unable to add [$entry]\n";
145: }
146:
1.4 andrew 147: sub append {
1.7 andrew 148: my ( $config, $line, @text) = @_;
149: if ( !( $line && @text && $line =~ /^\d+$/xms ) ) {
1.4 andrew 150: die 'usage: todo.pl append ITEM# "TEXT TO APPEND"' . "\n";
151: }
1.7 andrew 152:
153: my $text = join q{ }, @text;
1.4 andrew 154:
155: my $todo = Text::Todo->new($config);
156: my $entry = $todo->list->[ $line - 1 ];
157:
158: if ( $entry->append($text) && $todo->save ) {
159: return printf "%02d: %s\n", $line, $entry->text;
160: }
161: die "Unable to append\n";
162: }
163:
1.5 andrew 164: sub archive {
165: my ( $config ) = @_;
166: my $todo = Text::Todo->new($config);
167:
168: my $file = $todo->file;
169:
170: my $archived = $todo->archive;
171: if (defined $archived) {
172: return print "TODO: $file archived.\n";
173: }
174: die "Unable to archive $file\n";
175: }
176:
1.3 andrew 177: sub command { return &unsupported }
1.6 andrew 178:
179: sub del {
180: my ( $config, $line ) = @_;
181: if ( !( $line && $line =~ /^\d+$/xms ) ) {
182: die 'usage: todo.pl del ITEM#' . "\n";
183: }
184: my $todo = Text::Todo->new($config);
185:
186: my $entry = $todo->list->[$line - 1];
187: print "Delete '" . $entry->text . "'? (y/n)\n";
188: warn "XXX No delete confirmation currently!\n";
189:
190: if ($opts{n}) {
191: if ($todo->del($entry) && $todo->save) {
192: return print 'TODO: \'', $entry->text, "' deleted.\n";
193: }
194: }
195: else {
196: my $text = $entry->text;
197: if ($entry->replace(q{}) && $todo->save) {
198: return print 'TODO: \'', $text, "' deleted.\n";
199: }
200: }
201:
202: die "Unable to delete entry\n";
203: }
204:
1.3 andrew 205: sub depri { return &unsupported }
206: sub mark_done { return &unsupported }
207: sub help { return &unsupported }
208:
1.2 andrew 209: sub list {
1.3 andrew 210: my ( $config, $term ) = @_;
211: my $todo = Text::Todo->new($config);
212:
213: my @list = _number_list( $todo->list );
214: my $shown = _show_sorted_list( $term, @list );
215:
216: return _show_list_footer( $shown, scalar @list, $config->{todo_file} );
217: }
218:
219: sub listall {
220: my ( $config, $term ) = @_;
221: my $todo = Text::Todo->new($config);
222:
223: my @list = _number_list(
224: $todo->listfile('todo_file'),
225: $todo->listfile('done_file'),
226: );
227: my $shown = _show_sorted_list( $term, @list );
228:
229: return _show_list_footer( $shown, scalar @list, $config->{'todo_dir'} );
230: }
231:
232: sub listcon {
1.2 andrew 233: my ($config) = @_;
234: my $todo = Text::Todo->new($config);
1.3 andrew 235: return print map {"\@$_\n"} $todo->listcon;
236: }
1.2 andrew 237:
1.3 andrew 238: sub listfile {
239: my ( $config, $file, $term ) = @_;
240: if ( !$file ) {
241: die "usage: todo.pl listfile SRC [TERM]\n";
1.2 andrew 242: }
1.3 andrew 243: my $todo = Text::Todo->new($config);
244:
245: my @list = _number_list( $todo->listfile($file) );
246: my $shown = _show_sorted_list( $term, @list );
247:
248: return _show_list_footer( $shown, scalar @list, $file );
249: }
250:
251: sub listpri {
252: my ( $config, $pri ) = @_;
253:
254: my $todo = Text::Todo->new($config);
255:
256: my @list = _number_list( $todo->listfile('todo_file') );
257: my @pri_list;
258: if ($pri) {
259: $pri = uc $pri;
260: if ( $pri !~ /^[A-Z]$/xms ) {
261: die "usage: todo.pl listpri PRIORITY\n",
262: "note: PRIORITY must a single letter from A to Z.\n";
263: }
264: @pri_list = grep {
265: defined $_->{entry}->priority
266: && $_->{entry}->priority eq $pri
267: } @list;
268: }
269: else {
270: @pri_list = grep { $_->{entry}->priority } @list;
271: }
272:
273: my $shown = _show_sorted_list( undef, @pri_list );
274:
275: return _show_list_footer( $shown, scalar @list, $config->{todo_file} );
276: }
277:
278: sub listproj {
279: my ($config) = @_;
280: my $todo = Text::Todo->new($config);
281: return print map {"\+$_\n"} $todo->listproj;
282: }
283:
284: sub move { return &unsupported }
1.8 ! andrew 285:
! 286: sub prepend {
! 287: my ( $config, $line, @text) = @_;
! 288: if ( !( $line && @text && $line =~ /^\d+$/xms ) ) {
! 289: die 'usage: todo.pl prepend ITEM# "TEXT TO APPEND"' . "\n";
! 290: }
! 291:
! 292: my $text = join q{ }, @text;
! 293:
! 294: my $todo = Text::Todo->new($config);
! 295: my $entry = $todo->list->[ $line - 1 ];
! 296:
! 297: if ( $entry->prepend($text) && $todo->save ) {
! 298: return printf "%02d: %s\n", $line, $entry->text;
! 299: }
! 300: die "Unable to append\n";
! 301: }
! 302:
1.3 andrew 303: sub pri { return &unsupported }
304: sub replace { return &unsupported }
305: sub report { return &unsupported }
306:
307: sub _number_list {
308: my (@list) = @_;
309:
310: my $line = 1;
311: return map { { line => $line++, entry => $_, } } @list;
312: }
313:
314: sub _show_sorted_list {
315: my ( $term, @list ) = @_;
316: $term = defined $term ? quotemeta($term) : '';
317:
318: my $shown = 0;
1.4 andrew 319: my @sorted
320: = map { sprintf "%02d %s", $_->{line}, $_->{entry}->text }
321: sort { lc $a->{entry}->text cmp lc $b->{entry}->text } @list;
322:
323: foreach my $line ( grep {/$term/xms} @sorted ) {
324: print $line, "\n";
1.3 andrew 325: $shown++;
326: }
327:
328: return $shown;
329: }
330:
331: sub _show_list_footer {
332: my ( $shown, $total, $file ) = @_;
333:
334: $shown ||= 0;
335: $total ||= 0;
336:
337: print "-- \n";
338: print "TODO: $shown of $total tasks shown from $file\n";
339:
340: return 1;
1.2 andrew 341: }
342:
343: sub unsupported { die "Unsupported action\n" }
344:
345: sub usage {
346: my ($long) = @_;
347:
348: print <<'EOL';
349: * command list taken from todo.sh for compatibility
350: Usage: todo.pl [-fhpantvV] [-d todo_config] action
351: EOL
352:
353: if ($long) {
354: print <<'EOL';
1.3 andrew 355:
1.2 andrew 356: Actions:
357: add|a "THING I NEED TO DO +project @context"
358: addto DEST "TEXT TO ADD"
359: append|app NUMBER "TEXT TO APPEND"
360: archive
361: command [ACTIONS]
362: del|rm NUMBER [TERM]
363: dp|depri NUMBER
364: do NUMBER
365: help
366: list|ls [TERM...]
367: listall|lsa [TERM...]
368: listcon|lsc
369: listfile|lf SRC [TERM...]
370: listpri|lsp [PRIORITY]
371: listproj|lsprj
372: move|mv NUMBER DEST [SRC]
373: prepend|prep NUMBER "TEXT TO PREPEND"
374: pri|p NUMBER PRIORITY
375: replace NUMBER "UPDATED TODO"
376: report
377: EOL
378: }
379: else {
380: print <<'EOL';
381: Try 'todo.pl -h' for more information.
382: EOL
383: }
384:
385: exit;
386: }
387:
388: sub read_config {
389: my ($file) = @_;
390:
391: my %config;
1.3 andrew 392: open my $fh, '< ', $file or die "Unable to open [$file]: $!";
1.2 andrew 393: LINE: while (<$fh>) {
394: s/\r?\n$//xms;
395: s/\s*\#.*$//xms;
396: next LINE unless $_;
397:
398: if (s/^\s*export\s+//xms) {
399: my ( $key, $value ) = /^([^=]+)\s*=\s*"?(.*?)"?\s*$/xms;
400: if ($key) {
401: foreach my $k ( keys %config ) {
402: $value =~ s/\$\Q$k\E/$config{$k}/gxms;
403: $value =~ s/\${\Q$k\E}/$config{$k}/gxms;
404: }
405: foreach my $k ( keys %ENV ) {
406: $value =~ s/\$\Q$k\E/$ENV{$k}/gxms;
407: $value =~ s/\${\Q$k\E}/$ENV{$k}/gxms;
408: }
409: $value =~ s/\$\w+//gxms;
410: $value =~ s/\${\w+}//gxms;
411:
412: $config{$key} = $value;
413: }
414: }
415: }
416: close $fh;
1.1 andrew 417:
1.2 andrew 418: my %lc_config;
419: foreach my $k ( keys %config ) {
420: $lc_config{ lc($k) } = $config{$k};
421: }
1.1 andrew 422:
1.2 andrew 423: return \%lc_config;
1.1 andrew 424: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>