File Coverage

blib/lib/YATT/Lite/VFS.pm
Criterion Covered Total %
statement 233 263 88.5
branch 76 110 69.0
condition 30 49 61.2
subroutine 50 60 83.3
pod 2 46 4.3
total 391 528 74.0


line stmt bran cond sub pod time code
1             package YATT::Lite::VFS;
2 12     12   8879 use strict;
  12         24  
  12         371  
3 12     12   64 use warnings qw(FATAL all NONFATAL misc);
  12         22  
  12         483  
4 12     12   57 use Exporter qw(import);
  12         22  
  12         359  
5 12     12   57 use Scalar::Util qw(weaken);
  12         20  
  12         606  
6 12     12   68 use Carp;
  12         22  
  12         728  
7 12     12   55 use constant DEBUG_VFS => $ENV{DEBUG_YATT_VFS};
  12         19  
  12         784  
8 12     12   58 use constant DEBUG_REBUILD => $ENV{DEBUG_YATT_REBUILD};
  12         26  
  12         1777  
9              
10             require File::Spec;
11             require File::Basename;
12              
13             #========================================
14             # VFS 層. vfs_file (Template) のダミー実装を含む。
15             #========================================
16             {
17             sub MY () {__PACKAGE__}
18             use YATT::Lite::Types
19 12         208 ([Item => -fields => [qw(cf_name cf_public)]
20             , [Folder => -fields => [qw(Item cf_path cf_parent cf_base
21             cf_entns)]
22             , -eval => q{use YATT::Lite::Util qw(cached_in);}
23             , [File => -fields => [qw(partlist cf_string cf_overlay
24             dependency
25             )]
26             , -alias => 'vfs_file']
27             , [Dir => -fields => [qw(cf_encoding)]
28 12     12   6566 , -alias => 'vfs_dir']]]);
  12         27  
29              
30       0 0   sub YATT::Lite::VFS::Item::after_create {}
31             sub YATT::Lite::VFS::Folder::configure_parent {
32 190     190 0 310 my MY $self = shift;
33             # 循環参照対策
34             # XXX: Item に移すべきかもしれない。そうすれば、 Widget->parent が引ける。
35 190         1180 weaken($self->{cf_parent} = shift);
36             }
37              
38 12     12   544 package YATT::Lite::VFS; BEGIN {$INC{"YATT/Lite/VFS.pm"} = 1}
39             sub VFS () {__PACKAGE__}
40 12     12   68 use parent qw(YATT::Lite::Object);
  12         26  
  12         49  
41 12         56 use YATT::Lite::MFields qw/cf_ext_private cf_ext_public cf_cache cf_no_auto_create
42             cf_facade cf_base
43             cf_entns
44             cf_always_refresh_deps
45             on_memory
46             root extdict
47             cf_mark
48             n_creates
49 12     12   869 pkg2folder/;
  12         24  
50 12     12   70 use YATT::Lite::Util qw(lexpand rootname terse_dump);
  12         19  
  12         11081  
51 0     0 0 0 sub default_ext_public {'yatt'}
52 0     0 0 0 sub default_ext_private {'ytmpl'}
53             sub new {
54 51     51 1 7010 my ($class, $spec) = splice @_, 0, 2;
55 51         258 (my VFS $vfs, my @task) = $class->SUPER::just_new(@_);
56 51   33     441 foreach my $desc ([1, ($vfs->{cf_ext_public}
      33        
57             ||= $vfs->default_ext_public)]
58             , [0, ($vfs->{cf_ext_private}
59             ||= $vfs->default_ext_private)]) {
60 102         238 my ($value, @ext) = @$desc;
61 102         482 $vfs->{extdict}{$_} = $value for @ext;
62             }
63              
64 51 50       184 if ($spec) {
65 51         240 my Folder $root = $vfs->root_create
66             (linsert($spec, 2, $vfs->cf_delegate(qw(entns))));
67             # Mark [data => ..] vfs as on_memory
68 51 100 66     384 $vfs->{on_memory} = 1 if $spec->[0] eq 'data' or not $root->{cf_path};
69             }
70              
71 51         129 $$_[0]->($vfs, $$_[1]) for @task;
72 51         176 $vfs->after_new;
73 51         333 $vfs;
74             }
75             sub after_new {
76 51     51 1 85 my MY $self = shift;
77 51 50       143 confess __PACKAGE__ . ": facade is empty!" unless $self->{cf_facade};
78 51         157 weaken($self->{cf_facade});
79             }
80             sub error {
81 29     29 0 47 my MY $self = shift;
82 29         184 $self->{cf_facade}->error(@_);
83             }
84             #========================================
85             sub find_file {
86 196     196 0 407 (my VFS $vfs, my $filename) = @_;
87             # XXX: 拡張子をどうしたい?
88 196 50       1068 my ($name) = $filename =~ m{^(\w+)}
89             or croak "Can't extract part name from filename '$filename'";
90 196         778 $vfs->{root}->lookup($vfs, $name);
91             }
92             sub list_items {
93 1     1 0 3 (my VFS $vfs) = @_;
94 1         5 $vfs->{root}->list_items($vfs);
95             }
96             sub resolve_path_from {
97 0     0 0 0 (my VFS $vfs, my Folder $folder, my $fn) = @_;
98 0 0       0 my $dirname = $folder->dirname
99             or return undef;
100 0         0 File::Spec->rel2abs($fn, $dirname)
101             }
102              
103             #========================================
104             sub find_part {
105 45     45 0 11242 my VFS $vfs = shift;
106 45         151 $vfs->{root}->lookup($vfs, @_);
107             }
108             sub find_part_from {
109 94     94 0 257 (my VFS $vfs, my $from) = splice @_, 0, 2;
110 94         305 my Item $item = $from->lookup($vfs, @_);
111 94 100 100     1016 if ($item and $item->isa($vfs->Folder)) {
112 14         118 (my Folder $folder = $item)->{Item}{''}
113             } else {
114 80         517 $item;
115             }
116             }
117              
118             # To limit call of refresh atmost 1, use this.
119             sub reset_refresh_mark {
120 34     34 0 68 (my VFS $vfs) = shift;
121 34 50       153 $vfs->{cf_mark} = @_ ? shift : {};
122             }
123              
124 0     0 0 0 sub YATT::Lite::VFS::Dir::dirobj { $_[0] }
125             sub YATT::Lite::VFS::File::dirobj {
126 2     2 0 4 (my vfs_file $file) = @_;
127 2         7 $file->{cf_parent};
128             }
129              
130             sub YATT::Lite::VFS::Dir::dirname {
131 0     0 0 0 (my vfs_dir $dir) = @_;
132 0         0 $dir->{cf_path};
133             }
134             sub YATT::Lite::VFS::File::dirname {
135 0     0 0 0 (my vfs_file $file) = @_;
136 0 0       0 if (my $parent = $file->{cf_parent}) {
    0          
137 0         0 $parent->dirname;
138             } elsif (my $path = $file->{cf_path}) {
139 0         0 File::Basename::dirname(File::Spec->rel2abs($path));
140             } else {
141 0         0 undef;
142             }
143             }
144              
145 12     12   71 use Scalar::Util qw(refaddr);
  12         19  
  12         35823  
146             sub YATT::Lite::VFS::File::fake_filename {
147 159     159 0 261 (my vfs_file $file) = @_;
148 159   66     1081 $file->{cf_path} // $file->{cf_name};
149             }
150              
151             sub YATT::Lite::VFS::File::lookup {
152 134     134 0 331 (my vfs_file $file, my VFS $vfs, my $name) = splice @_, 0, 3;
153 134 100       360 unless (@_) {
154             # ファイルの中には、深さ 1 の name しか無いはずだから。
155             # mtime, refresh
156 125 100       587 $file->refresh($vfs) unless $vfs->{cf_mark}{refaddr($file)}++;
157 125         241 my Item $item = $file->{Item}{$name};
158 125 100       424 return $item if $item;
159             }
160             # 深さが 2 以上の (name, @_) については、継承先から探す。
161 30         112 $file->lookup_base($vfs, $name, @_);
162             }
163             sub YATT::Lite::VFS::Dir::lookup {
164 292     292 0 692 (my vfs_dir $dir, my VFS $vfs, my $name) = splice @_, 0, 3;
165 292 100 100     1755 if (my Item $item = $dir->cached_in
166             ($dir->{Item} //= {}, $name, $vfs, $vfs->{cf_mark})) {
167 265 100 66     1528 if ((not ref $item or not UNIVERSAL::isa($item, Item))
      66        
168             and not $vfs->{cf_no_auto_create}) {
169 140         473 $item = $dir->{Item}{$name} = $vfs->create
170             (data => $item, parent => $dir, name => $name);
171             }
172 263 100       1608 return $item unless @_;
173 38         112 $item = $item->lookup($vfs, @_);
174 38 100       220 return $item if $item;
175             }
176 28         106 $dir->lookup_base($vfs, $name, @_);
177             }
178             sub YATT::Lite::VFS::Folder::lookup_base {
179 58     58 0 147 (my Folder $item, my VFS $vfs, my $name) = splice @_, 0, 3;
180 58         152 my @super = $item->list_base;
181 58         120 foreach my $super (@super) {
182 53 100       222 my $ans = $super->lookup($vfs, $name, @_) or next;
183 43         187 return $ans;
184             }
185 15         59 undef;
186             }
187             sub YATT::Lite::VFS::Folder::list_base {
188 370   100 370 0 488 my Folder $folder = shift; @{$folder->{cf_base} ||= []}
  370         467  
  370         1754  
189             }
190             sub YATT::Lite::VFS::File::list_base {
191 342     342 0 512 my vfs_file $file = shift;
192              
193             # $dir/$file.yatt inherits its own base decl,
194 342         1165 my @super = $file->YATT::Lite::VFS::Folder::list_base;
195              
196             # $dir ($dir's bases will be called in $dir->lookup),
197 342 100       1207 push @super, $file->{cf_parent} if $file->{cf_parent};
198              
199             # and then directory named $dir/$file.ytmpl (or "$dir/$file")
200 342 100       870 push @super, $file->{cf_overlay} if $file->{cf_overlay};
201              
202 342         997 @super;
203             }
204             sub YATT::Lite::VFS::File::list_items {
205 0     0 0 0 die "NIMPL";
206             }
207             sub YATT::Lite::VFS::Dir::list_items {
208 1     1 0 2 (my vfs_dir $in, my VFS $vfs) = @_;
209 1 50       5 return unless defined $in->{cf_path};
210 1         2 my %dup;
211             my @exts = map {
212 2 50 33     16 if (defined $_ and not $dup{$_}++) {
213 2         5 $_
214 0         0 } else { () }
215 1         3 } ($vfs->{cf_ext_public}, $vfs->{cf_ext_private});
216 1         3 my %dup2;
217             map {
218 1         165 my $name = substr($_, length($in->{cf_path})+1);
  2         8  
219 2         8 $name =~ s/\.\w+$//;
220 2 50       21 $dup2{$name}++ ? () : $name;
221             } glob("$in->{cf_path}/[a-z]*.{".join(",", @exts)."}");
222             }
223             #----------------------------------------
224             sub YATT::Lite::VFS::Dir::load {
225 55     55 0 114 (my vfs_dir $in, my VFS $vfs, my $partName) = @_;
226 55 100       199 return unless defined $in->{cf_path};
227 50         135 my $vfsname = "$in->{cf_path}/$partName";
228 50         144 my @opt = (name => $partName, parent => $in);
229 50         65 my ($kind, $path, @other) = do {
230 50 100       143 if (my $fn = $vfs->find_ext($vfsname, $vfs->{cf_ext_public})) {
    100          
    100          
231 26         90 (file => $fn, public => 1);
232             } elsif ($fn = $vfs->find_ext($vfsname, $vfs->{cf_ext_private})) {
233             # dir の場合、 new_tmplpkg では?
234 6 50       112 my $kind = -d $fn ? 'dir' : 'file';
235 6         20 ($kind => $fn);
236             } elsif (-d $vfsname) {
237 1         5 return $vfs->{cf_facade}->create_neighbor($vfsname);
238             } else {
239 17         91 return undef;
240             }
241             };
242 32         124 $vfs->create($kind, $path, @opt, @other);
243             }
244             sub find_ext {
245 74     74 0 170 (my VFS $vfs, my ($vfsname, $spec)) = @_;
246 74 50       251 foreach my $ext (!defined $spec ? () : ref $spec ? @$spec : $spec) {
    50          
247 74         166 my $fn = "$vfsname.$ext";
248 74 100       2379 return $fn if -e $fn;
249             }
250             }
251             #========================================
252             # 実験用、ダミーのパーサー
253             sub YATT::Lite::VFS::File::reset {
254 3     3 0 7 (my File $file) = @_;
255 3         18 undef $file->{partlist};
256 3         158 undef $file->{Item};
257 3         13 undef $file->{cf_string};
258 3         12 undef $file->{cf_base};
259 3         19 $file->{dependency} = +{};
260             }
261       2 0   sub YATT::Lite::VFS::Dir::refresh {}
262             sub YATT::Lite::VFS::File::refresh {
263 22     22 0 31 (my vfs_file $file, my VFS $vfs) = @_;
264 22 50 66     64 return unless $$file{cf_path} || $$file{cf_string};
265             # XXX: mtime!
266 22         28 my @part = do {
267 22         76 local $/; split /^!\s*(\w+)\s+(\S+)[^\n]*?\n/m, do {
  22         29  
268 22 100       54 if ($$file{cf_path}) {
269             open my $fh, '<', $$file{cf_path}
270 19 50       660 or die "Can't open '$$file{cf_path}': $!";
271             scalar <$fh>
272 19         611 } else {
273 3         15 $$file{cf_string};
274             }
275             };
276             };
277 22         69 $file->add_widget('', shift @part);
278 22         104 while (my ($kind, $name, $part) = splice @part, 0, 3) {
279 19 100 66     131 if (defined $kind and my $sub = $file->can("declare_$kind")) {
280 4         12 $sub->($file, $name, $vfs, $part);
281             } else {
282 15         54 $file->can("add_$kind")->($file, $name, $part);
283             }
284             }
285             }
286              
287             sub YATT::Lite::VFS::File::add_dependency {
288 15     15 0 38 (my File $file, my $wpath, my File $other) = @_;
289 15         163 Scalar::Util::weaken($file->{dependency}{$wpath} = $other);
290             }
291             sub YATT::Lite::VFS::File::list_dependency {
292 0     0 0 0 (my File $file, my $detail) = @_;
293             defined (my $deps = $file->{dependency})
294 0 0       0 or return;
295 0 0       0 if ($detail) {
296 0 0       0 wantarray ? map([$_ => $deps->{$_}], keys %$deps) : $deps;
297             } else {
298 0         0 values %$deps;
299             }
300             }
301             sub refresh_deps_for {
302 0     0 0 0 (my MY $self, my File $file) = @_;
303 0         0 print STDERR "refresh deps for: ", $file->{cf_path}, "\n" if DEBUG_REBUILD;
304 0         0 foreach my $dep ($file->list_dependency) {
305 0 0       0 unless ($self->{cf_mark}{refaddr($dep)}++) {
306 0         0 print STDERR " refreshing: ", $dep->{cf_path}, "\n" if DEBUG_REBUILD;
307 0         0 $dep->refresh($self);
308             }
309             }
310             }
311              
312             #========================================
313             sub add_to {
314 18     18 0 45 (my VFS $vfs, my ($path, $data)) = @_;
315 18 50       67 my @path = ref $path ? @$path : $path;
316 18         32 my $lastName = pop @path;
317 18         38 my Folder $folder = $vfs->{root};
318 18         53 while (@path) {
319 0         0 my $name = shift @path;
320 0   0     0 $folder = $folder->{Item}{$name} ||= $vfs->create
321             (data => {}, name => $name, parent => $folder);
322             }
323             # XXX: path を足すと、memory 動作の時に困る
324 18         89 $folder->{Item}{$lastName} = $vfs->create
325             (data => $data, name => $lastName, parent => $folder);
326             }
327             #========================================
328 14     14 0 25 sub root {(my VFS $vfs) = @_; $vfs->{root}}
  14         44  
329              
330             # special hook for root creation.
331             sub root_create {
332 51     51 0 235 (my VFS $vfs, my ($kind, $primary, %rest)) = @_;
333 51   66     215 $rest{entns} //= $vfs->{cf_entns};
334 51         222 $vfs->{root} = $vfs->create($kind, $primary, %rest);
335             }
336             sub create {
337 251     251 0 1730 (my VFS $vfs, my ($kind, $primary, %rest)) = @_;
338             # XXX: $vfs は className の時も有る。
339 251 100       1377 if (my $sub = $vfs->can("create_$kind")) {
340 201         780 $vfs->fixup_created(\@_, $sub->($vfs, $primary, %rest));
341             } else {
342 50   33     197 $vfs->{cf_cache}{$primary} ||= do {
343             # XXX: Really??
344 50   66     208 $rest{entns} //= $vfs->{cf_entns};
345 50         630 $vfs->fixup_created
346             (\@_, $vfs->can("vfs_$kind")->()->new(%rest, path => $primary));
347             };
348             }
349             }
350             sub fixup_created {
351 251     251 0 436 (my VFS $vfs, my $info, my Folder $folder) = @_;
352             printf STDERR "# VFS::create(%s) => %s(0x%x)\n"
353 251         302 , terse_dump(@{$info}[1..$#$info])
354             , ref $folder, ($folder+0) if DEBUG_VFS;
355             # create の直後、 after_create より前に、mark を打つ。そうしないと、 delegate で困る。
356 251 100       737 if (ref $vfs) {
357 249         461 $vfs->{n_creates}++;
358 249         1217 $vfs->{cf_mark}{refaddr($folder)}++;
359             }
360              
361 251 100 100     1124 if (my $path = $folder->{cf_path} and not defined $folder->{cf_name}) {
362 34         104 $path =~ s/\.\w+$//;
363 34         142 $path =~ s!.*/!!;
364 34         87 $folder->{cf_name} = $path;
365             }
366              
367 251 100       676 if (my Folder $parent = $folder->{cf_parent}) {
368 190 100       523 if (defined $parent->{cf_entns}) {
369             $folder->{cf_entns} = join '::'
370 170         601 , $parent->{cf_entns}, $folder->{cf_name};
371             # XXX: base 指定だけで済むべきだが、Factory を呼んでないので出来ないorz...
372             YATT::Lite::MFields->add_isa_to
373 170         1147 ($folder->{cf_entns}, $parent->{cf_entns});
374 170         558 $vfs->{pkg2folder}{$folder->{cf_entns}} = $folder;
375             }
376             }
377 251         932 $folder->after_create($vfs);
378 248         1717 $folder;
379             }
380             sub create_data {
381 185     185 0 407 (my VFS $vfs, my ($primary)) = splice @_, 0, 2;
382 185 100       372 if (ref $primary) {
383             # 直接 Folder slot にデータを。
384 28         233 my vfs_dir $item = $vfs->vfs_dir->new(@_);
385 28         73 $item->{Item} = $primary;
386 28         109 $item;
387             } else {
388 157         1398 $vfs->vfs_file->new(public => 1, @_, string => $primary);
389             }
390             }
391             sub YATT::Lite::VFS::Folder::vivify_base_descs {
392 59     59 0 91 (my Folder $folder, my VFS $vfs) = @_;
393 59         79 foreach my Folder $desc (@{$folder->{cf_base}}) {
  59         195  
394 16 50       51 if (ref $desc eq 'ARRAY') {
395             # XXX: Dirty workaround.
396 16 100       48 if ($desc->[0] eq 'dir') {
397             # To create YATT::Lite with .htyattconfig.xhf, Factory should be involved.
398 12         85 $desc = $vfs->{cf_facade}->create_neighbor($desc->[1]);
399             } else {
400 4         13 $desc = $vfs->create(@$desc);
401             }
402             }
403 16 50       61 $desc = $vfs->create(@$desc) if ref $desc eq 'ARRAY';
404             # parent がある == parent から指されている。なので、 weaken する必要が有る。
405 16 50       58 weaken($desc) if $desc->{cf_parent};
406             }
407             }
408             sub YATT::Lite::VFS::Dir::after_create {
409 59     59 0 94 (my vfs_dir $dir, my VFS $vfs) = @_;
410 59         214 $dir->YATT::Lite::VFS::Folder::vivify_base_descs($vfs);
411             # $dir->refresh($vfs);
412 59         99 $dir;
413             }
414             # file 系は create 時に必ず refresh. refresh は decl のみ parse.
415             sub YATT::Lite::VFS::File::after_create {
416 192     192 0 287 (my vfs_file $file, my VFS $vfs) = @_;
417 192         496 $file->refresh_overlay($vfs);
418 192         767 $file->refresh($vfs);
419             }
420             sub YATT::Lite::VFS::File::refresh_overlay {
421 192     192 0 298 (my vfs_file $file, my VFS $vfs) = @_;
422 192 50       1510 return if $file->{cf_overlay};
423 192 100       588 return unless $file->{cf_path};
424 35         127 my $rootname = rootname($file->{cf_path});
425 35         164 my @found = grep {-d $$_[-1]} ([1, $rootname]
  70         1531  
426             , [0, "$rootname.$vfs->{cf_ext_private}"]);
427 35 50       198 if (@found > 1) {
    100          
428             $vfs->error(q|Don't use %1$s and %1$s.%2$s at once|
429 0         0 , $rootname, $vfs->{cf_ext_private});
430             } elsif (not @found) {
431 34         73 return;
432             }
433 1         2 $file->{cf_overlay} = do {
434 1         3 my ($public, $path) = @{$found[0]};
  1         3  
435 1 50       3 if ($public) {
436 1         6 $vfs->{cf_facade}->create_neighbor($path);
437             } else {
438             $vfs->create
439 0         0 (dir => $path, parent => $file->{cf_parent});
440             }
441             };
442             }
443             #----------------------------------------
444             sub YATT::Lite::VFS::File::declare_base {
445 4     4 0 11 (my vfs_file $file, my ($spec), my VFS $vfs, my $part) = @_;
446 4         12 my ($kind, $path) = split /=/, $spec, 2;
447             # XXX: 物理 path だと困るよね? findINC 的な処理が欲しい
448             # XXX: 帰属ディレクトリより強くするため、先頭に。でも、不満。
449 4         6 unshift @{$file->{cf_base}}, $vfs->create($kind => $path);
  4         19  
450 4         14 weaken($file->{cf_base}[0]);
451 4         24 $file->{Item}{''} .= $part;
452             }
453             sub YATT::Lite::VFS::File::add_widget {
454 37     37 0 94 (my vfs_file $file, my ($name, $part)) = @_;
455 37         57 push @{$file->{partlist}}, $file->{Item}{$name} = $part;
  37         215  
456             }
457              
458             sub linsert {
459 51     51 0 80 my @ls = @{shift()};
  51         142  
460 51         153 splice @ls, shift, 0, @_;
461 51 50       317 wantarray ? @ls : \@ls;
462             }
463             }
464              
465 12     12   80 use YATT::Lite::Breakpoint;
  12         23  
  12         1267  
466             YATT::Lite::Breakpoint::break_load_vfs();
467              
468             1;