File Coverage

blib/lib/CTK/Skel.pm
Criterion Covered Total %
statement 42 171 24.5
branch 0 72 0.0
condition 0 42 0.0
subroutine 14 26 53.8
pod 5 5 100.0
total 61 316 19.3


line stmt bran cond sub pod time code
1             package CTK::Skel; # $Id: Skel.pm 250 2019-05-09 12:09:57Z minus $
2 1     1   7 use strict;
  1         1  
  1         36  
3 1     1   4 use utf8;
  1         3  
  1         5  
4              
5             =encoding utf8
6              
7             =head1 NAME
8              
9             CTK::Skel - Helper for building project's skeletons
10              
11             =head1 VIRSION
12              
13             Version 1.00
14              
15             =head1 SYNOPSIS
16              
17             use CTK::Skel;
18              
19             my $skel = new CTK::Skel (
20             -dir => "/destination/directory/for/project",
21             );
22              
23             my $skel = new CTK::Skel (
24             -name => "ProjectName",
25             -root => "/path/to/project/dir",
26             -skels => {
27             foo => 'My::Foo::Module',
28             # ...
29             },
30             -vars => {
31             VAR1 => "abc",
32             VAR2 => "def",
33             # ...
34             },
35             -debug => 1,
36             );
37              
38             my $status = $skel->build( "foo", "/path/to/project/dir", {
39             VAR3 => 'my value',
40             # ...
41             });
42              
43             =head1 DESCRIPTION
44              
45             Helper for building project's skeletons
46              
47             =head2 new
48              
49             my $skel = new CTK::Skel (
50             -name => "ProjectName",
51             -root => "/path/to/project/dir",
52             -skels => {
53             foo => 'My::Foo::Module',
54             # ...
55             },
56             -vars => {
57             VAR1 => "abc",
58             VAR2 => "def",
59             # ...
60             },
61             -debug => 1,
62             );
63              
64             Returns skeletons helper's object
65              
66             =head2 build
67              
68             my $status = $skel->build( "foo", "/path/to/project/dir", {
69             VAR1 => 'foo',
70             VAR2 => 'bar',
71             # ...
72             });
73              
74             Building "foo" files and directories to "/path/to/project/dir" directory
75              
76             my $status = $skel->build( "foo", {
77             VAR1 => 'foo',
78             VAR2 => 'bar',
79             # ...
80             });
81              
82             Building "foo" files and directories to default directory (see L)
83              
84             =head2 dirs, pool
85              
86             Base methods. For internal use only
87              
88             =head2 skels
89              
90             my @available_skels = $skel->skels();
91              
92             Returns list of registered skeletons
93              
94             =head1 HISTORY
95              
96             See C file
97              
98             =head1 SEE ALSO
99              
100             L
101              
102             =head1 AUTHOR
103              
104             Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE
105              
106             =head1 COPYRIGHT
107              
108             Copyright (C) 1998-2019 D&D Corporation. All Rights Reserved
109              
110             =head1 LICENSE
111              
112             This program is free software; you can redistribute it and/or
113             modify it under the same terms as Perl itself.
114              
115             See C file and L
116              
117             =cut
118              
119 1     1   46 use vars qw/$VERSION/;
  1         2  
  1         44  
120             $VERSION = '1.00';
121              
122 1     1   5 use Carp;
  1         1  
  1         61  
123 1     1   8 use File::Spec;
  1         1  
  1         26  
124 1     1   736 use File::Temp qw();
  1         9038  
  1         30  
125 1     1   508 use Class::C3::Adopt::NEXT; #use MRO::Compat;
  1         6358  
  1         6  
126 1     1   37 use MIME::Base64 qw/decode_base64/;
  1         2  
  1         53  
127 1     1   649 use Term::ANSIColor qw/colored/;
  1         8391  
  1         793  
128 1     1   8 use CTK::Util qw/ :BASE /;
  1         2  
  1         502  
129 1     1   462 use CTK::ConfGenUtil;
  1         3  
  1         70  
130 1     1   437 use CTK::TFVals qw/ :ALL /;
  1         2  
  1         216  
131              
132 1     1   8 use Cwd qw/getcwd/;
  1         2  
  1         130  
133              
134             use constant {
135 1         2091 PROJECT => "Foo",
136             EXEMODE => 0755,
137             DIRMODE => 0777,
138             ROOTDIR => getcwd(),
139             BOUNDARY => qr/\-{5}BEGIN\s+FILE\-{5}(.*?)\-{5}END\s+FILE\-{5}/is,
140             STDRPLC => {
141             PODSIG => '=',
142             DOLLAR => '$',
143             GMT => sprintf("%s GMT", scalar(gmtime)),
144             YEAR => (gmtime)[5]+1900,
145             },
146 1     1   8 };
  1         1  
147              
148             our @ISA;
149              
150             sub new {
151 0     0 1   my $class = shift;
152 0 0         my ($project_name, $project_dir, $modules, $newrplc, $debug) = read_attributes([
153             ['PROJECTNAME','PROJECT','NAME'],
154             ['PROJECTDIR','DIR','PROJECTROOT','ROOT'],
155             ['SKELS','LIST','MODULES'],
156             ['REPLACE','RPLC','VARS'],
157             ['DEBUG'],
158             ],@_) if defined $_[0];
159 0   0       $newrplc ||= {};
160 0           my %rplc = %{(STDRPLC)};
  0            
161 0   0       $rplc{"PROJECT"} = $project_name || PROJECT;
162 0 0         if (ref($newrplc) eq 'HASH') {
163 0           foreach my $k (keys %$newrplc) {
164 0           $rplc{$k} = $newrplc->{$k};
165             }
166             }
167              
168             my $self = bless {
169 0 0 0       project => $rplc{"PROJECT"},
170             boundary=> BOUNDARY,
171             rplc => { %rplc },
172             root => $project_dir || ROOTDIR,
173             subdirs => {},
174             pools => {},
175             skels => [], # Names of loaded modules
176             debug => $debug ? 1 : 0,
177             }, $class;
178              
179             # Register skels
180 0 0 0       if ($modules && ref($modules) eq 'HASH') {
181 0           my @skels;
182 0           foreach my $skel (keys %$modules) {
183 0 0         if ($self->_load($modules->{$skel})) {
184 0           push @skels, $skel;
185             } else {
186 0           carp(sprintf("Can't initialize %s skeleton", $skel));
187 0           last;
188             }
189             }
190 0           $self->{skels} = [@skels];
191             }
192              
193 0           return $self;
194             }
195             sub skels {
196 0     0 1   my $self = shift;
197 0           my $skels = $self->{skels};
198 0           return @$skels;
199             }
200             sub build {
201 0     0 1   my $self = shift;
202 0           my $name = shift;
203 0 0         my $dir = shift if $_[1];
204 0   0       my $rplc = shift || {};
205 0   0       my $root = $dir || $self->{root};
206              
207             # Get skels list
208 0           my @skels = $self->skels;
209 0 0 0       unless ($name && grep {$_ eq $name} @skels) {
  0            
210 0           carp("Incorrect scope name. Allowed: ".join(", ",@skels));
211 0           return 0;
212             }
213 0 0         $rplc = {} unless ref($rplc) eq 'HASH';
214              
215             # Directories normalize
216 0 0         $self->dirs() if $self->can('dirs');
217              
218             # Pools normalize
219 0 0         $self->pool() if $self->can('pool');
220              
221             # To next build() in modules
222 0           my $ret = $self->maybe::next::method();
223 0 0         return 0 unless $ret;
224              
225             #
226             # Building
227             #
228 0           my $_rplc = $self->{rplc};
229 0           for (keys %$_rplc) { $rplc->{$_} = $_rplc->{$_} }
  0            
230              
231             # Post-processing: directories
232 0   0       my $subdirs = $self->{subdirs} || {};
233 0           my $vd = $subdirs->{$name};
234 0           foreach my $d (@$vd) {
235 0           my @ds = split(/\//,_ff($d->{path}, $rplc));
236 0 0         my $path = $root ? File::Spec->catdir($root, @ds) : File::Spec->catdir(@ds);
237 0 0         my $mode = defined $d->{mode} ? $d->{mode} : DIRMODE;
238 0 0         if (preparedir($path, $mode)) {
239 0           $self->_debug(_yep("%s", $path));
240             } else {
241 0           $self->_debug(_nope("Can't create directory \"%s\" [%o]", $path, $mode));
242             }
243             }
244              
245             # Post-processing: files
246 0   0       my $pools = $self->{pools} || {};
247 0           my $vp = $pools->{$name};
248 0           foreach my $p (@$vp) {
249 0 0 0       next if $p->{type} && !isostype($p->{type}); # Type check
250 0 0 0       my $b64 = ($p->{encode} && $p->{encode} eq 'base64') ? 1 : 0;
251 0   0       my $fname = $p->{name} || 'noname';
252 0 0         unless ($p->{file}) {
253 0           $self->_debug(_skip("Skip %s file: path not defined!", $fname));
254 0           next;
255             }
256 0           my @ds = split(/\//,_ff($p->{file}, $rplc));
257 0           my $file = File::Spec->catfile($root, @ds);
258 0 0         if (-e $file) {
259 0           $self->_debug(_skip("%s", $file));
260 0           next;
261             }
262 0           my $mode = $p->{mode};
263 0           my $st = 0;
264 0 0         if ($b64) { $st = bsave($file, decode_base64( $p->{data} )) }
  0            
265 0           else { $st = bsave($file, CTK::Util::lf_normalize(_ff($p->{data}, $rplc)), 1) }
266 0 0 0       if ($st && -e $file) {
267 0 0         chmod($mode, $file) if defined($mode);
268 0           $self->_debug(_yep("%s", $file));
269             } else {
270 0   0       $self->_debug(_nope("Can't create file \"%s\" [%o]", $file, $mode // 0));
271 0           return 0;
272             }
273             }
274              
275 0           return 1;
276             }
277             sub dirs {
278 0     0 1   my $self = shift;
279 0           $self->maybe::next::method();
280 0   0       my $dirs = $self->{subdirs} || {};
281 0           foreach my $kd (keys %$dirs) {
282 0 0         if (ref($dirs->{$kd}) eq 'HASH') {
    0          
283 0           $dirs->{$kd} = [$dirs->{$kd}];
284             } elsif (ref($dirs->{$kd}) eq 'ARRAY') {
285             # OK;
286             } else {
287 0 0         carp "Directory incorrect. Array or hash expected!" if $dirs->{$kd};
288             }
289             }
290 0           return 1;
291             }
292             sub pool {
293 0     0 1   my $self = shift;
294 0           $self->maybe::next::method();
295 0           my $boundary = $self->{boundary};
296 0   0       my $pools = $self->{pools} || {};
297 0           foreach my $kd (keys %$pools) {
298 0           my $buff = $pools->{$kd};
299 0           my @pool;
300 0           $buff =~ s/$boundary/_bcut($1,\@pool)/ge;
  0            
301 0           foreach my $r (@pool) {
302 0 0         my $name = ($r =~ /^\s*name\s*\:\s*(.+?)\s*$/mi) ? $1 : '';
303 0 0         my $file = ($r =~ /^\s*file\s*\:\s*(.+?)\s*$/mi) ? $1 : '';
304 0 0         my $mode = ($r =~ /^\s*mode\s*\:\s*(.+?)\s*$/mi) ? $1 : '';
305 0 0         my $type = ($r =~ /^\s*type\s*\:\s*(.+?)\s*$/mi) ? $1 : '';
306 0 0         my $enc = ($r =~ /^\s*encode\s*\:\s*(.+?)\s*$/mi) ? $1 : '';
307 0 0         my $data = ($r =~ /\s*\r?\n\s*\r?\n(.+)/s) ? $1 : '';
308              
309 0 0         $mode = undef unless $mode =~ /^[0-9]{1,3}$/;
310 0 0         $r = {
311             name => $name,
312             file => $file,
313             data => lf_normalize($data), # CRLF correct
314             mode => defined($mode) ? oct($mode) : undef,
315             type => $type,
316             encode => $enc,
317             };
318             }
319 0           $pools->{$kd} = [@pool];
320             }
321 0           return 1;
322             }
323              
324             # Methods
325             sub _load {
326 0     0     my $self = shift;
327 0           my $module = shift;
328 0           my $file = sprintf("%s.pm", join('/', split('::', $module)));
329 0           utf8::encode($file); # from base.pm
330 0 0         return 1 if exists $INC{$file};
331 0           eval { require $file; };
  0            
332 0 0         if ($@) {
333 0           carp(sprintf("Can't load file: %s", $@));
334 0           return 0;
335             }
336 0           push @ISA, $module;
337 0           return 1;
338             }
339             sub _debug {
340 0     0     my $self = shift;
341 0 0         return unless $self->{debug};
342 0           print @_, "\n";
343             }
344              
345             # Functions
346             sub _yep {
347 0     0     return(colored(['green on_black'], '[ OK ]'), ' ', sprintf(shift, @_));
348             }
349             sub _nope {
350 0     0     return(colored(['red on_black'], '[ FAIL ]'), ' ', sprintf(shift, @_));
351             }
352             sub _skip {
353 0     0     return(colored(['yellow on_black'], '[ SKIP ]'), ' ', sprintf(shift, @_));
354             }
355             sub _bcut {
356 0     0     my $s = shift;
357 0           my $a = shift;
358 0           push @$a, $s;
359 0           return '';
360             }
361             sub _ff {
362 0   0 0     my $d = shift || '';
363 0   0       my $h = shift || {};
364 0 0         $d =~ s/\%(\w+?)\%/(defined $h->{$1} ? $h->{$1} : '%'.$1.'%')/eg;
  0            
365 0           return $d
366             }
367              
368             1;