File Coverage

blib/lib/App/VOJournal.pm
Criterion Covered Total %
statement 47 105 44.7
branch 12 42 28.5
condition 5 9 55.5
subroutine 11 17 64.7
pod 1 1 100.0
total 76 174 43.6


line stmt bran cond sub pod time code
1             package App::VOJournal;
2              
3             # vim: set sw=4 ts=4 tw=76 et ai si:
4              
5 2     2   176162 use 5.006;
  2         16  
6 2     2   10 use strict;
  2         4  
  2         47  
7 2     2   10 use warnings FATAL => 'all';
  2         4  
  2         77  
8              
9 2     2   923 use App::VOJournal::VOTL;
  2         6  
  2         62  
10 2     2   14 use File::Find;
  2         4  
  2         114  
11 2     2   12 use File::Path qw(make_path);
  2         10  
  2         97  
12 2     2   1543 use Getopt::Long qw(GetOptionsFromArray);
  2         27024  
  2         11  
13              
14             =head1 NAME
15              
16             App::VOJournal - call Vimoutline on a journal file.
17              
18             =head1 VERSION
19              
20             Version v0.4.7
21              
22             =cut
23              
24 2     2   338 use version; our $VERSION = qv('v0.4.7');
  2         4  
  2         10  
25              
26             =head1 SYNOPSIS
27              
28             Open a file in vimoutliner to write a journal
29              
30             use App::VOJournal;
31              
32             App::VOJournal->run;
33              
34             or, on the command line
35              
36             perl -MApp::VOJournal -e App::VOJournal->run
37              
38             =head1 SUBROUTINES/METHODS
39              
40             =head2 run
41              
42             This is the only function you need to use this Module. It parses the command
43             line and determines the date for which you want to write a journal entry.
44             Then it makes sure all necessary directories are created and starts
45             vimoutliner with the journal file for that date.
46              
47             use App::VOJournal;
48              
49             App::VOJournal->run;
50              
51             B App::VOJournal uses L
52             to search for the last journal file
53             below the directory given with option C<--basedir>
54             (default: C<$ENV{HOME}/journal>) and does not follow symbolic links.
55             Please make sure that this path points to a real directory and no symbolic link.
56              
57             =cut
58              
59             sub run {
60 0     0 1 0 my $opt = _initialize(@ARGV);
61              
62 0 0       0 if ($opt->{version}) {
63 0         0 print "App::VOJournal version $VERSION\n";
64 0         0 return 0;
65             }
66              
67 0         0 my $basedir = $opt->{basedir};
68 0         0 my $editor = $opt->{editor};
69              
70 0         0 my ($day,$month,$year) = _determine_date($opt);
71              
72 0         0 my $dir = sprintf("%s/%4.4d/%2.2d",
73             $basedir, $year, $month);
74 0         0 my $path = sprintf("%s/%4.4d/%2.2d/%4.4d%2.2d%2.2d.otl",
75             $basedir, $year, $month, $year, $month, $day);
76 0         0 my $header = sprintf('; %4.4d-%2.2d-%2.2d',$year,$month,$day);
77              
78 0         0 my $last_file = _find_last_file($basedir,$path);
79              
80 0         0 make_path($dir);
81              
82 0 0       0 if ($last_file) {
83 0 0       0 if ($last_file cmp $path) {
84 0 0       0 if ($opt->{resume}) {
85 0         0 _use_last_file($path,$last_file,$opt,$header);
86             }
87             else {
88 0         0 _create_new_file($path,$opt,$header);
89             }
90             }
91             }
92             else {
93 0         0 _create_new_file($path,$opt,$header);
94             }
95 0 0       0 if ($opt->{resume}) {
96 0 0 0     0 if ($last_file
97             && $last_file cmp $path) {
98             }
99             }
100 0         0 system($editor, $path);
101 0         0 return $?;
102             } # run()
103              
104             # _create_new_file($path,$opt,$header)
105             #
106             # Creates a new vimoutliner file at $path optionally containing a header
107             # line.
108             sub _create_new_file {
109 0     0   0 my ($path,$opt,$header) = @_;
110 0         0 my $votl = App::VOJournal::VOTL->new();
111 0 0       0 if ($opt->{header}) {
112 0         0 $votl->insert_line(0,$header);
113             }
114 0         0 $votl->write_file($path);
115             } # _create_new_file()
116              
117             # _determine_date($opt)
118             #
119             # Determines the date respecting the given options.
120             #
121             sub _determine_date {
122 0     0   0 my ($opt) = @_;
123 0         0 my ($day,$month,$year) = (localtime)[3,4,5];
124 0         0 $year += 1900;
125 0         0 $month += 1;
126              
127 0 0       0 if (my $od = $opt->{date}) {
128 0 0       0 if ($od =~ /^\d{1,2}$/) {
    0          
    0          
129 0         0 $day = $od;
130             }
131             elsif ($od =~ /^(\d{1,2})(\d{2})$/) {
132 0         0 $month = $1;
133 0         0 $day = $2;
134             }
135             elsif ($od =~ /^(\d{4})(\d{2})(\d{2})$/) {
136 0         0 $year = $1;
137 0         0 $month = $2;
138 0         0 $day = $3;
139             }
140             }
141              
142 0         0 return ($day,$month,$year);
143             } # _determine_date()
144              
145             sub _find_files_with_pattern {
146 0     0   0 my ($dirname,$pattern) = @_;
147 0         0 my @files = ();
148              
149 0 0       0 if (opendir(my $DIR,$dirname)) {
150 0         0 while (defined(my $file = readdir($DIR))) {
151 0 0       0 push(@files,$file) if ($file =~ /$pattern/);
152             }
153 0         0 closedir($DIR);
154             }
155 0         0 return @files;
156             } # _find_files_with_pattern
157              
158             # _find_last_file($basedir, $next_file, $f)
159             #
160             # Find the last journal file that can be used as a template for the next
161             # file.
162             #
163             # $basedir - the start directory
164             # $next_file - the name of the file we want to use next
165             # $f - optional, a hash with function references to aid testing/debugging
166             #
167             sub _find_last_file {
168 1     1   1390 my ($basedir,$next_file,$f) = @_;
169 1         3 my $last_file = '';
170 1         2 my $got_it = 0;
171             my $wanted = sub {
172 7     7   59 my $this_file = $File::Find::name;
173              
174 7 100       131 return if ($got_it);
175             #
176             # We get the files in reverse order, therefore the first matching
177             # file is already the one we are looking for.
178             #
179             # If we got the file we signal this with $got_it.
180             #
181 5 100 66     86 if ($this_file =~ qr|^$basedir/\d{4}/\d{2}/\d{8}[.]otl$|
      100        
182             && 0 < ($this_file cmp $last_file)
183             && 0 >= ($this_file cmp $next_file)) {
184 1         3 $last_file = $this_file;
185 1         3 $got_it = 1;
186             }
187             #
188             # The following is only to aid in testing or debugging.
189             #
190 5 50       20 if ($f->{wanted}) {
191 5         16 $f->{wanted}->($this_file,$last_file,$next_file,$got_it);
192             }
193 1         5 };
194             #
195             # Concentrate on the files whose path matches the pattern of journal
196             # files and ignore the rest with this preprocess function for
197             # File::Find::find().
198             #
199             # Sort the filenames reverse and ignore all following directory listings
200             # if we have already found the last file;
201             #
202             my $preprocess = sub {
203 5     5   258 my @files = ();
204              
205 5 100       82 if ($got_it) {
    100          
    100          
    50          
206             # leave it empty
207             }
208             elsif ($File::Find::dir =~ /^$basedir$/) {
209 1         3 @files = grep { /^\d{4}$/ } @_;
  4         14  
210             }
211             elsif ($File::Find::dir =~ m|^$basedir/\d{4}$|) {
212 1         4 @files = grep { /^\d{2}$/ } @_;
  4         13  
213             }
214             elsif ($File::Find::dir =~ m|^$basedir/\d{4}/\d{2}$|) {
215 1         5 @files = grep { /^\d{8}\.otl$/ } @_;
  4         15  
216             }
217 5         76 return sort {$b cmp $a} @files;
  3         152  
218 1         4 };
219 1         122 find({wanted => $wanted,
220             preprocess => $preprocess,
221             untaint => 1, # needed when running in taint mode
222             },$basedir);
223 1         16 return $last_file;
224             } # _find_last_file()
225              
226             sub _use_last_file {
227 0     0     my ($path,$last_file,$opt,$header) = @_;
228 0           my $votl = App::VOJournal::VOTL->new();
229 0           $votl->read_file_unchecked_boxes($last_file);
230 0 0         if ($opt->{header}) {
231 0           $votl->insert_line(0,$header);
232             }
233 0           $votl->write_file($path);
234             } # _use_old_file
235              
236             =head1 COMMANDLINE OPTIONS
237              
238             =head2 --basedir $dir
239              
240             Use C<< $dir >> instead of C<< $ENV{HOME}/journal >> as base directory for
241             the journal files.
242              
243             =head2 --date [YYYY[MM]]DD
244              
245             Use a different date than today.
246              
247             One or two digits change the day in the current month.
248              
249             Three or four digits change the day and month in the current year.
250              
251             Eight digits change day, month and year.
252              
253             The program will not test for a valid date. That means, if you specify
254             '--date 0230' on the command line, the file for February 30th this year
255             would be opened.
256              
257             =head2 --editor $path_to_editor
258              
259             Use this option to specify an editor other than C to edit
260             the journal file.
261              
262             =head2 --[no]header
263              
264             Normally every journal file starts with some header lines, indicating the
265             day the journal is written.
266              
267             If the option C<--noheader> is given on the command line, this header lines
268             will be omitted.
269              
270             =head2 --[no]resume
271              
272             Look for open checkboxes in the last journal file and carry them forward
273             to this days journal file before opening it.
274              
275             This only works if there is no journal file for this day.
276              
277             The default is C<< --resume >>
278              
279             =head2 --version
280              
281             Print the version number and exit.
282              
283             =cut
284              
285             # _initialize(@ARGV)
286             #
287             # Parse the command line and initialize the program
288             #
289             sub _initialize {
290 0     0     my @argv = @_;
291 0           my $opt = {
292             'basedir' => qq($ENV{HOME}/journal),
293             'editor' => 'vim',
294             'header' => 1,
295             'resume' => 1,
296             };
297 0           my @optdesc = (
298             'basedir=s',
299             'date=i',
300             'editor=s',
301             'header!',
302             'resume!',
303             'version',
304             );
305 0           GetOptionsFromArray(\@argv,$opt,@optdesc);
306 0           return $opt;
307             } # _initialize()
308              
309             =head1 AUTHOR
310              
311             Mathias Weidner, C<< >>
312              
313             =head1 BUGS
314              
315             Please report any bugs or feature requests to C, or through
316             the web interface at L. I will be notified, and then you'll
317             automatically be notified of progress on your bug as I make changes.
318              
319             =head1 SUPPORT
320              
321             You can find documentation for this module with the perldoc command.
322              
323             perldoc App::VOJournal
324              
325              
326             You can also look for information at:
327              
328             =over 4
329              
330             =item * RT: CPAN's request tracker (report bugs here)
331              
332             L
333              
334             =item * AnnoCPAN: Annotated CPAN documentation
335              
336             L
337              
338             =item * CPAN Ratings
339              
340             L
341              
342             =item * Search CPAN
343              
344             L
345              
346             =back
347              
348              
349             =head1 ACKNOWLEDGEMENTS
350              
351              
352             =head1 LICENSE AND COPYRIGHT
353              
354             Copyright 2015 Mathias Weidner.
355              
356             This program is free software; you can redistribute it and/or modify it
357             under the terms of the the Artistic License (2.0). You may obtain a
358             copy of the full license at:
359              
360             L
361              
362             Any use, modification, and distribution of the Standard or Modified
363             Versions is governed by this Artistic License. By using, modifying or
364             distributing the Package, you accept this license. Do not use, modify,
365             or distribute the Package, if you do not accept this license.
366              
367             If your Modified Version has been derived from a Modified Version made
368             by someone other than you, you are nevertheless required to ensure that
369             your Modified Version complies with the requirements of this license.
370              
371             This license does not grant you the right to use any trademark, service
372             mark, tradename, or logo of the Copyright Holder.
373              
374             This license includes the non-exclusive, worldwide, free-of-charge
375             patent license to make, have made, use, offer to sell, sell, import and
376             otherwise transfer the Package with respect to any patent claims
377             licensable by the Copyright Holder that are necessarily infringed by the
378             Package. If you institute patent litigation (including a cross-claim or
379             counterclaim) against any party alleging that the Package constitutes
380             direct or contributory patent infringement, then this Artistic License
381             to you shall terminate on the date that such litigation is filed.
382              
383             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
384             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
385             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
386             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
387             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
388             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
389             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
390             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
391              
392              
393             =cut
394              
395             1; # End of App::VOJournal