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   162038 use 5.006;
  2         17  
6 2     2   10 use strict;
  2         5  
  2         47  
7 2     2   9 use warnings FATAL => 'all';
  2         3  
  2         65  
8              
9 2     2   1017 use App::VOJournal::VOTL;
  2         5  
  2         63  
10 2     2   14 use File::Find;
  2         3  
  2         134  
11 2     2   12 use File::Path qw(make_path);
  2         12  
  2         95  
12 2     2   1777 use Getopt::Long qw(GetOptionsFromArray);
  2         27307  
  2         12  
13              
14             =head1 NAME
15              
16             App::VOJournal - call Vimoutline on a journal file.
17              
18             =head1 VERSION
19              
20             Version v0.4.8
21              
22             =cut
23              
24 2     2   335 use version; our $VERSION = qv('v0.4.8');
  2         4  
  2         15  
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   1443 my ($basedir,$next_file,$f) = @_;
169 1         3 my $last_file = '';
170 1         1 my $got_it = 0;
171             my $wanted = sub {
172 7     7   42 my $this_file = $File::Find::name;
173              
174 7 100       117 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     82 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         2 $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         19 $f->{wanted}->($this_file,$last_file,$next_file,$got_it);
192             }
193 1         6 };
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   212 my @files = ();
204              
205 5 100       76 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         14 @files = grep { /^\d{8}\.otl$/ } @_;
  4         16  
216             }
217 5         44 return sort {$b cmp $a} @files;
  3         157  
218 1         5 };
219 1         110 find({wanted => $wanted,
220             preprocess => $preprocess,
221             untaint => 1, # needed when running in taint mode
222             no_chdir => 1, # we don't need to chdir
223             },$basedir);
224 1         16 return $last_file;
225             } # _find_last_file()
226              
227             sub _use_last_file {
228 0     0     my ($path,$last_file,$opt,$header) = @_;
229 0           my $votl = App::VOJournal::VOTL->new();
230 0           $votl->read_file_unchecked_boxes($last_file);
231 0 0         if ($opt->{header}) {
232 0           $votl->insert_line(0,$header);
233             }
234 0           $votl->write_file($path);
235             } # _use_old_file
236              
237             =head1 COMMANDLINE OPTIONS
238              
239             =head2 --basedir $dir
240              
241             Use C<< $dir >> instead of C<< $ENV{HOME}/journal >> as base directory for
242             the journal files.
243              
244             =head2 --date [YYYY[MM]]DD
245              
246             Use a different date than today.
247              
248             One or two digits change the day in the current month.
249              
250             Three or four digits change the day and month in the current year.
251              
252             Eight digits change day, month and year.
253              
254             The program will not test for a valid date. That means, if you specify
255             '--date 0230' on the command line, the file for February 30th this year
256             would be opened.
257              
258             =head2 --editor $path_to_editor
259              
260             Use this option to specify an editor other than C to edit
261             the journal file.
262              
263             =head2 --[no]header
264              
265             Normally every journal file starts with some header lines, indicating the
266             day the journal is written.
267              
268             If the option C<--noheader> is given on the command line, this header lines
269             will be omitted.
270              
271             =head2 --[no]resume
272              
273             Look for open checkboxes in the last journal file and carry them forward
274             to this days journal file before opening it.
275              
276             This only works if there is no journal file for this day.
277              
278             The default is C<< --resume >>
279              
280             =head2 --version
281              
282             Print the version number and exit.
283              
284             =cut
285              
286             # _initialize(@ARGV)
287             #
288             # Parse the command line and initialize the program
289             #
290             sub _initialize {
291 0     0     my @argv = @_;
292 0           my $opt = {
293             'basedir' => qq($ENV{HOME}/journal),
294             'editor' => 'vim',
295             'header' => 1,
296             'resume' => 1,
297             };
298 0           my @optdesc = (
299             'basedir=s',
300             'date=i',
301             'editor=s',
302             'header!',
303             'resume!',
304             'version',
305             );
306 0           GetOptionsFromArray(\@argv,$opt,@optdesc);
307 0           return $opt;
308             } # _initialize()
309              
310             =head1 AUTHOR
311              
312             Mathias Weidner, C<< >>
313              
314             =head1 BUGS
315              
316             Please report any bugs or feature requests to C, or through
317             the web interface at L. I will be notified, and then you'll
318             automatically be notified of progress on your bug as I make changes.
319              
320             =head1 SUPPORT
321              
322             You can find documentation for this module with the perldoc command.
323              
324             perldoc App::VOJournal
325              
326              
327             You can also look for information at:
328              
329             =over 4
330              
331             =item * RT: CPAN's request tracker (report bugs here)
332              
333             L
334              
335             =item * AnnoCPAN: Annotated CPAN documentation
336              
337             L
338              
339             =item * CPAN Ratings
340              
341             L
342              
343             =item * Search CPAN
344              
345             L
346              
347             =back
348              
349              
350             =head1 ACKNOWLEDGEMENTS
351              
352              
353             =head1 LICENSE AND COPYRIGHT
354              
355             Copyright 2015 Mathias Weidner.
356              
357             This program is free software; you can redistribute it and/or modify it
358             under the terms of the the Artistic License (2.0). You may obtain a
359             copy of the full license at:
360              
361             L
362              
363             Any use, modification, and distribution of the Standard or Modified
364             Versions is governed by this Artistic License. By using, modifying or
365             distributing the Package, you accept this license. Do not use, modify,
366             or distribute the Package, if you do not accept this license.
367              
368             If your Modified Version has been derived from a Modified Version made
369             by someone other than you, you are nevertheless required to ensure that
370             your Modified Version complies with the requirements of this license.
371              
372             This license does not grant you the right to use any trademark, service
373             mark, tradename, or logo of the Copyright Holder.
374              
375             This license includes the non-exclusive, worldwide, free-of-charge
376             patent license to make, have made, use, offer to sell, sell, import and
377             otherwise transfer the Package with respect to any patent claims
378             licensable by the Copyright Holder that are necessarily infringed by the
379             Package. If you institute patent litigation (including a cross-claim or
380             counterclaim) against any party alleging that the Package constitutes
381             direct or contributory patent infringement, then this Artistic License
382             to you shall terminate on the date that such litigation is filed.
383              
384             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
385             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
386             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
387             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
388             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
389             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
390             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
391             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
392              
393              
394             =cut
395              
396             1; # End of App::VOJournal