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.16';
4              
5 9     9   104968 use Catmandu::Sane;
  9         183621  
  9         68  
6 9     9   1956 use Catmandu::Util qw(:is :check);
  9         29  
  9         3975  
7 9     9   70 use Moo;
  9         37  
  9         55  
8 9     9   4902 use Cwd;
  9         21  
  9         743  
9 9     9   61 use Path::Tiny qw(path);
  9         18  
  9         484  
10 9     9   740 use Path::Iterator::Rule;
  9         10535  
  9         289  
11 9     9   56 use File::Spec;
  9         19  
  9         250  
12 9     9   51 use Catmandu::BadArg;
  9         37  
  9         227  
13 9     9   55 use Catmandu::Error;
  9         24  
  9         236  
14 9     9   72 use Data::Dumper;
  9         15  
  9         470  
15 9     9   56 use namespace::clean;
  9         16  
  9         66  
16              
17             with "Catmandu::DirectoryIndex";
18              
19             has keysize => (is => 'ro', default => 9, trigger => 1);
20              
21             sub _trigger_keysize {
22 17 100   17   3529 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 374 my ($self, $id) = @_;
28              
29 181 100       844 Catmandu::BadArg->throw("need a number") unless $id =~ /^\d+$/;
30              
31 176         463 my $n_id = int($id);
32              
33 176 50       423 Catmandu::BadArg->throw("id must be bigger or equal to zero")
34             if $n_id < 0;
35              
36 176         414 my $keysize = $self->keysize();
37              
38 176 100       501 Catmandu::BadArg->throw(
39             "id '$id' does not fit into configured keysize $keysize")
40             if length("$id") > $keysize;
41              
42 170         847 sprintf "%-${keysize}.${keysize}d", $n_id;
43             }
44              
45             sub _to_path {
46 161     161   311 my ($self, $id) = @_;
47              
48 161         1950 File::Spec->catdir($self->base_dir, unpack("(A3)*", $id));
49             }
50              
51             sub _from_path {
52 10     10   23 my ($self, $path) = @_;
53              
54 10         98 my @split_path = File::Spec->splitdir($path);
55 10         62 my $id = join("",
56             splice(@split_path, scalar(File::Spec->splitdir($self->base_dir))));
57              
58 10         34 $self->format_id($id);
59             }
60              
61             sub get {
62 96     96 0 1145 my ($self, $id) = @_;
63              
64 96         242 my $f_id = $self->format_id($id);
65 96         251 my $path = $self->_to_path($f_id);
66              
67 96 100 66     3097 is_string($path) && -d $path ? {_id => $f_id, _path => $path} : undef;
68             }
69              
70             sub add {
71 70     70 0 6937 my ($self, $id) = @_;
72              
73 70         194 my $f_id = $self->format_id($id);
74 60         164 my $path = $self->_to_path($f_id);
75              
76 60 100       1442 unless (-d $path) {
77              
78 15         47 my $err;
79 15         92 path($path)->mkpath({error => \$err});
80              
81 15 50 50     6576 Catmandu::Error->throw(
82             "unable to create directory $path: " . Dumper($err))
83             if defined($err) && scalar(@$err);
84              
85             }
86              
87 60         561 +{_id => $f_id, _path => $path};
88             }
89              
90             sub delete {
91 5     5 0 1318 my ($self, $id) = @_;
92              
93 5         17 my $f_id = $self->format_id($id);
94 5         21 my $path = $self->_to_path($f_id);
95              
96 5 50 33     131 if (is_string($path) && -d $path) {
97              
98 5         15 my $err;
99 5         24 path($path)->remove_tree({error => \$err});
100              
101 5 50 50     2084 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 504 my $self = $_[0];
113              
114 12 50       304 if (-d $self->base_dir) {
115              
116 12         35 my $err;
117 12         90 path($_[0]->base_dir)->remove_tree({keep_root => 1, error => \$err});
118              
119 12 50 50     9779 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         51 1;
127             }
128              
129             sub generator {
130 9     9 0 443 my $self = $_[0];
131              
132             return sub {
133 18     18   47 state $rule;
134 18         34 state $iter;
135 18         57 state $base_dir = $self->base_dir();
136              
137 18 100       60 unless ($iter) {
138 9         86 $rule = Path::Iterator::Rule->new();
139 9         195 $rule->min_depth($self->keysize() / 3);
140 9         504 $rule->max_depth($self->keysize() / 3);
141 9         297 $rule->directory();
142 9         234 $iter = $rule->iter($base_dir, {depthfirst => 1});
143             }
144              
145 18         1241 my $path = $iter->();
146              
147 18 100       8474 return unless defined $path;
148              
149             #Path::Iterator::Rule hardcodes forward slashes
150 10 50       43 $path =~ s/\//\\/go if $^O eq "MSWin32";
151              
152 10         35 my $id = $self->_from_path($path);
153              
154 9         45 +{_id => $id, _path => $path};
155 9         74 };
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