File Coverage

blib/lib/Path/Class/Versioned.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Path::Class::Versioned;
2 5     5   176034 use Moose;
  0            
  0            
3             use Moose::Util::TypeConstraints;
4             use MooseX::Types::Path::Class;
5             use MooseX::Params::Validate;
6              
7             use List::Util 'max';
8              
9             our $VERSION = '0.04';
10             our $AUTHORITY = 'cpan:STEVAN';
11              
12             # this is a basic type for objects
13             # that overload stringification, it
14             # is not perfect cause Perl's overload
15             # is not perfect, so if you hit an
16             # edge case, please send a bug report.
17             subtype 'Path::Class::Versioned::Stringifyable'
18             => as 'Object'
19             => where {
20             require overload;
21             overload::Method($_, '""')
22             };
23              
24             # Accept strings, or objects which
25             # we can stringify, and one undefined
26             # value (our version number placeholder)
27             subtype 'Path::Class::Versioned::NamePattern'
28             => as 'ArrayRef[Str | Undef | Path::Class::Versioned::Stringifyable]'
29             => where {
30             (grep { not(defined $_) } @{$_[0]}) == 1
31             }
32             => message {
33             "Your name pattern must be made up of "
34             . "strings, stringifyable objects and "
35             . "exactly *one* undef value"
36             };
37              
38             ## the attributes ...
39              
40             has 'name_pattern' => (is => 'ro', isa => 'Path::Class::Versioned::NamePattern', required => 1);
41             has 'version_format' => (is => 'ro', isa => 'Str', default => sub { '%d' });
42              
43             has '_compiled_name_pattern' => (
44             is => 'ro',
45             isa => 'RegexpRef',
46             lazy => 1,
47             default => sub {
48             my $self = shift;
49             my $name_pattern = join "" => (map { defined $_ ? $_ : '(\d+)' } @{ $self->name_pattern });
50             qr/$name_pattern/;
51             },
52             );
53              
54             has 'parent' => (
55             is => 'ro',
56             isa => 'Path::Class::Dir',
57             coerce => 1,
58             default => sub { Path::Class::Dir->new }
59             );
60              
61             # the methods ...
62              
63             sub next_file {
64             my $self = shift;
65             $self->parent->file($self->next_name(file => 1));
66             }
67              
68             sub next_dir {
69             my $self = shift;
70             $self->parent->subdir($self->next_name(dir => 1));
71             }
72              
73             sub next_name {
74             my ($self, $is_dir, $is_file) = validated_list(\@_,
75             dir => { isa => 'Bool', optional => 1 },
76             file => { isa => 'Bool', optional => 1, default => 1 }
77             );
78              
79             my $name_extractor = $is_dir
80             ? sub { (shift)->relative($self->parent)->stringify }
81             : sub { (shift)->basename };
82              
83             my $name_pattern = $self->_compiled_name_pattern;
84             my $max_version = max(
85             map {
86             ($name_extractor->($_) =~ /$name_pattern/)
87             } grep {
88             ($is_dir ? (-d $_) : (-f $_))
89             } $self->parent->children
90             );
91              
92             $max_version = 0 unless defined $max_version;
93              
94             my $next_version = sprintf $self->version_format, ($max_version + 1);
95              
96             join "" => (map { defined $_ ? $_ : $next_version } @{ $self->name_pattern });
97             }
98              
99             no Moose; 1;
100              
101             __END__
102              
103             =pod
104              
105             =head1 NAME
106              
107             Path::Class::Versioned - A simple module for managing versioned file names
108              
109             =head1 SYNOPSIS
110              
111             use Path::Class::Versioned;
112              
113             # typical usage for files ...
114              
115             my $v = Path::Class::Versioned->new(
116             name_pattern => [ 'MyBackups-v', undef, '.zip' ],
117             parent => [ $FindBin::Bin, 'backups' ] # coerced into Path::Class::Dir
118             );
119              
120             # create the next filename in the
121             # sequence as specified by the
122             # name pattern above.
123             my $next_file_name = $v->next_name; # defaults to files ...
124              
125             # create an instance of Path::Class::File
126             # that represents that next file name
127             my $file = $v->next_file;
128              
129             # typical usage for directories ...
130              
131             my $v = Path::Class::Versioned->new(
132             name_pattern => [ 'MyBackupDirectory-v', undef ],
133             parent => Path::Class::Dir->new() # will use current dir
134             );
135              
136             # just like the file example, but
137             # tell it to match against directories
138             # instead of files
139             my $next_dir_name = $v->next_name(dir => 1);
140              
141             # create an instance of Path::Class::Dir
142             # that represents that next directory name
143             my $subdir = $v->next_dir;
144              
145             =head1 DESCRIPTION
146              
147             C'mon, you know you have done this too, so why bother writing it over
148             and over again, just use this module.
149              
150             This module aims to provide a simple, yet sophisticated way of creating
151             and managing versioned files by name. It is a poor substitute for using
152             a real VCS (version control system) or some more sophisticated versioning
153             module that utilizes diffs, etc. However, there are some times when you
154             just don't need that level of control, and just need to back stuff up
155             in a simple way, so here it is.
156              
157             =head1 ATTRIBUTES
158              
159             These attributes should be set through the constructor, all are required
160             except for the C<version_format> which will default to just printing the
161             number.
162              
163             =over 4
164              
165             =item B<name_pattern>
166              
167             This is expected to be an ArrayRef made up of strings, stringify-able objects
168             and I<exactly> B<one> C<undef> value. The C<undef> value will serve as the
169             placeholder for the version number. Here are some example formats and the
170             names they create.
171              
172             For a simple sequentially named file set, with no extra version formatting
173             you might do something like this:
174              
175             [ 'Foo-v', undef, '.txt' ]
176             # Foo-v1.txt, Foo-v2.txt, etc ...
177              
178             For a simple date-stamped directory set with a I<version_format> of C<%02d>
179             you might do something like this:
180              
181             [ 'Baz-', $datetime, '-v', undef ]
182             # Baz-2008-05-12-v01/, Baz-2008-05-12-v02/
183              
184             It is assumed that your C<$datetime> instance already has the formatter set
185             to produce the specified string. Something like this has the benefit of making
186             it very simple to create dated files/directories, but not have to worry about
187             overwriting something in the same day.
188              
189             =item B<version_format>
190              
191             This is a format string which will be passed to C<sprintf> in order to
192             format the version number. It defaults to just returning the number itself.
193              
194             =item B<parent>
195              
196             This is a L<Path::Class::Dir> object representing the parent directory, it
197             is the contents of this directory which will be inspected to determine the
198             next name to be created.
199              
200             Alternately you can specify an ArrayRef of strings, or a string itself and
201             those will be coerced into a L<Path::Class::Dir> object. We use the type
202             created by the L<MooseX::Types::Path::Class> module, please refer that for
203             more details.
204              
205             =back
206              
207             =head1 METHODS
208              
209             =over 4
210              
211             =item B<next_name (dir => Bool, file => Bool)>
212              
213             Returns the next file name (if the C<file> boolean argument is true) or
214             the next directory name (if the C<dir> boolean argument is true). It defaults
215             to the a file name.
216              
217             =item B<next_file>
218              
219             Returns a L<Path::Class::File> object for the value of C<next_name(file => 1)>.
220              
221             =item B<next_dir>
222              
223             Returns a L<Path::Class::Dir> object for the value of C<next_name(dir => 1)>.
224              
225             =back
226              
227             =head1 BUGS
228              
229             All complex software has bugs lurking in it, and this module is no
230             exception. If you find a bug please either email me, or add the bug
231             to cpan-RT.
232              
233             =head1 ACKNOWLEDGEMENTS
234              
235             NO ONE IS INNOCENT! Here are the names of those who are especially guilty.
236              
237             =over 4
238              
239             =item Thanks to perigrin for holding back the snide comments when I suggested this module.
240              
241             =item Thanks to rjbs for the module name (although he may deny any involvment).
242              
243             =back
244              
245             =head1 AUTHOR
246              
247             Stevan Little E<lt>stevan.little@iinteractive.comE<gt>
248              
249             =head1 COPYRIGHT AND LICENSE
250              
251             Copyright 2008-2010 Infinity Interactive, Inc.
252              
253             L<http://www.iinteractive.com>
254              
255             This library is free software; you can redistribute it and/or modify
256             it under the same terms as Perl itself.
257              
258             =cut
259