File Coverage

blib/lib/Catmandu/DirectoryIndex/Number.pm
Criterion Covered Total %
statement 89 89 100.0
branch 21 28 75.0
condition 6 12 50.0
subroutine 21 21 100.0
pod 0 6 0.0
total 137 156 87.8


line stmt bran cond sub pod time code
1             package Catmandu::DirectoryIndex::Number;
2              
3             our $VERSION = '1.14';
4              
5 9     9   105648 use Catmandu::Sane;
  9         180592  
  9         70  
6 9     9   1958 use Catmandu::Util qw(:is :check);
  9         22  
  9         3774  
7 9     9   79 use Moo;
  9         20  
  9         57  
8 9     9   4684 use Cwd;
  9         21  
  9         772  
9 9     9   64 use Path::Tiny qw(path);
  9         39  
  9         530  
10 9     9   764 use Path::Iterator::Rule;
  9         10208  
  9         257  
11 9     9   51 use File::Spec;
  9         17  
  9         191  
12 9     9   45 use Catmandu::BadArg;
  9         20  
  9         321  
13 9     9   56 use Catmandu::Error;
  9         16  
  9         213  
14 9     9   47 use Data::Dumper;
  9         27  
  9         461  
15 9     9   66 use namespace::clean;
  9         19  
  9         61  
16              
17             with "Catmandu::DirectoryIndex";
18              
19             has keysize => (is => 'ro', default => 9, trigger => 1);
20              
21             sub _trigger_keysize {
22 17 100   17   3482 Catmandu::BadArg->throw("keysize needs to be a multiple of 3")
23             unless $_[0]->keysize % 3 == 0;
24             }
25              
26             sub format_id {
27 181     181 0 350 my ($self, $id) = @_;
28              
29 181 100       521 Catmandu::BadArg->throw("need natural number") unless is_natural($id);
30              
31 176         1246 my $n_id = int($id);
32              
33 176 50       369 Catmandu::BadArg->throw("id must be bigger or equal to zero")
34             if $n_id < 0;
35              
36 176         395 my $keysize = $self->keysize();
37              
38 176 100       508 Catmandu::BadArg->throw(
39             "id '$id' does not fit into configured keysize $keysize")
40             if length("$id") > $keysize;
41              
42 170         864 sprintf "%-${keysize}.${keysize}d", $n_id;
43             }
44              
45             sub _to_path {
46 161     161   315 my ($self, $id) = @_;
47              
48 161         1943 File::Spec->catdir($self->base_dir, unpack("(A3)*", $id));
49             }
50              
51             sub _from_path {
52 10     10   63 my ($self, $path) = @_;
53              
54 10         98 my @split_path = File::Spec->splitdir($path);
55 10         66 my $id = join("",
56             splice(@split_path, scalar(File::Spec->splitdir($self->base_dir))));
57              
58 10         36 $self->format_id($id);
59             }
60              
61             sub get {
62 96     96 0 1156 my ($self, $id) = @_;
63              
64 96         234 my $f_id = $self->format_id($id);
65 96         281 my $path = $self->_to_path($f_id);
66              
67 96 100 66     3064 is_string($path) && -d $path ? {_id => $f_id, _path => $path} : undef;
68             }
69              
70             sub add {
71 70     70 0 6973 my ($self, $id) = @_;
72              
73 70         185 my $f_id = $self->format_id($id);
74 60         171 my $path = $self->_to_path($f_id);
75              
76 60 100       1276 unless (-d $path) {
77              
78 15         50 my $err;
79 15         86 path($path)->mkpath({error => \$err});
80              
81 15 50 50     9289 Catmandu::Error->throw(
82             "unable to create directory $path: " . Dumper($err))
83             if defined($err) && scalar(@$err);
84              
85             }
86              
87 60         576 +{_id => $f_id, _path => $path};
88             }
89              
90             sub delete {
91 5     5 0 1272 my ($self, $id) = @_;
92              
93 5         20 my $f_id = $self->format_id($id);
94 5         23 my $path = $self->_to_path($f_id);
95              
96 5 50 33     140 if (is_string($path) && -d $path) {
97              
98 5         17 my $err;
99 5         24 path($path)->remove_tree({error => \$err});
100              
101 5 50 50     2209 Catmandu::Error->throw(
102             "unable to remove directory $path: " . Dumper($err))
103             if defined($err) && scalar(@$err);
104              
105             }
106              
107 5         30 1;
108             }
109              
110             sub delete_all {
111              
112 12     12 0 468 my $self = $_[0];
113              
114 12 50       277 if (-d $self->base_dir) {
115              
116 12         33 my $err;
117 12         78 path($_[0]->base_dir)->remove_tree({keep_root => 1, error => \$err});
118              
119 12 50 50     9200 Catmandu::Error->throw("unable to remove entries from base directory "
120             . $self->base_dir . ": "
121             . Dumper($err))
122             if defined($err) && scalar(@$err);
123              
124             }
125              
126 12         44 1;
127             }
128              
129             sub generator {
130 9     9 0 434 my $self = $_[0];
131              
132             return sub {
133 18     18   58 state $rule;
134 18         32 state $iter;
135 18         52 state $base_dir = $self->base_dir();
136              
137 18 100       57 unless ($iter) {
138 9         101 $rule = Path::Iterator::Rule->new();
139 9         170 $rule->min_depth($self->keysize() / 3);
140 9         561 $rule->max_depth($self->keysize() / 3);
141 9         315 $rule->directory();
142 9         232 $iter = $rule->iter($base_dir, {depthfirst => 1});
143             }
144              
145 18         1217 my $path = $iter->();
146              
147 18 100       8626 return unless defined $path;
148              
149             #Path::Iterator::Rule hardcodes forward slashes
150 10 50       49 $path =~ s/\//\\/go if $^O eq "MSWin32";
151              
152 10         42 my $id = $self->_from_path($path);
153              
154 9         68 +{_id => $id, _path => $path};
155 9         59 };
156             }
157              
158             1;
159              
160             __END__
161              
162             =pod
163              
164             =head1 NAME
165              
166             Catmandu::DirectoryIndex::Number - A natural number based directory translator
167              
168             =head1 SYNOPSIS
169              
170             use Catmandu::DirectoryIndex::Number;
171              
172             my $p = Catmandu::DirectoryIndex::Number->new(
173             base_dir => "/data",
174             keysize => 9
175             );
176              
177             # get mapping for id: { _id => 1234, _path => "/data/000/001/234" }
178             # can be undef
179             my $mapping = $p->get(1234);
180              
181             # create mapping for id. Path created if necessary
182             my $mapping = $p->add(1234);
183              
184             # Catmandu::DirectoryIndex::Number is a Catmandu::Iterable
185             # Returns list of records: [{ _id => "000001234", _path => "/data/000/001/234" }]
186             my $mappings = $p->to_array();
187              
188             =head1 METHODS
189              
190             =head2 new( base_dir => $base_dir , keysize => NUM )
191              
192             Create a new Catmandu::DirectoryIndex::Number with the following configuration
193             parameters:
194              
195             =over
196              
197             =item base_dir
198              
199             See L<Catmandu::DirectoryIndex>
200              
201             =item keysize
202              
203             By default the directory structure is 3 levels deep. With the keysize option
204             a deeper nesting can be created. The keysize needs to be a multiple of 3.
205              
206             =back
207              
208             =head1 LIMITATIONS
209              
210             The keys in this directory can only be natural numbers 0,1,2 ...
211              
212             =head1 INHERITED METHODS
213              
214             This Catmandu::DirectoryIndex::Number implements:
215              
216             =over 3
217              
218             =item L<Catmandu::DirectoryIndex>
219              
220             =back
221              
222             =head1 SEE ALSO
223              
224             L<Catmandu::DirectoryIndex>
225              
226             =cut