File Coverage

blib/lib/NCustom.pm
Criterion Covered Total %
statement 968 1127 85.8
branch 315 552 57.0
condition 81 161 50.3
subroutine 63 65 96.9
pod 12 29 41.3
total 1439 1934 74.4


line stmt bran cond sub pod time code
1             package NCustom;
2              
3 1     2   2 use 5.008;
  1         299  
  2         2647  
4 2     2   6 use strict qw(vars);
  2         72  
  2         8  
5 2     2   4 use warnings;
  2         62  
  2         9  
6              
7 2     2   3 use vars qw(%Config $Transaction $req);
  2         583  
  2         9  
8 2     2   4 use Carp;
  2         109  
  2         14  
9 2     2   4 use File::Basename;
  2         123  
  2         10  
10 2     2   3 use File::Compare;
  2         132  
  2         900  
11 2     2   1074 use File::Copy;
  2         86  
  2         11  
12 2     2   4 use File::Find;
  2         89  
  2         10  
13 2     2   4 use File::Path;
  2         78  
  2         10  
14 2     2   4 use File::Spec;
  2         74  
  2         22  
15 2     2   3 use File::Temp qw(tempfile tempdir);
  2         43  
  2         2302  
16 2     2   42962 use FindBin qw($Bin); #this finds the dir of the src of $0
  2         181  
  2         1693  
17 2     2   2366 use Text::ParseWords;
  2         254  
  2         1726  
18 2     2   2964 use Socket;
  2         133  
  2         2138  
19 2     2   9482 use Symbol qw(delete_package);
  2         1635  
  2         22  
20              
21             require Exporter;
22 2     2   5 use AutoLoader qw(AUTOLOAD);
  2         127  
  2         1802  
23              
24             our @ISA = qw(Exporter);
25             our %EXPORT_TAGS = (
26             'all' => [ qw( &transaction &save_files &initialise &overwrite_file &append_file &prepend_file &edit_file &undo_files &required_packages $req &apt_fix &ncustom &blat_myconfig &config_edit) ]
27             );
28             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
29             our @EXPORT = qw( );
30              
31             our $VERSION = '0.07';
32              
33              
34             # ///////////////////////////////////////////////////////////////////
35 1         30 #<< PP: POD Prefix <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
36             # \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
37              
38             =head1 NAME
39              
40             NCustom - Perl extension for customising system configurations.
41              
42             =head1 SYNOPSIS
43              
44             NCUSTOM_SCRIPT
45             use NCustom;
46             # do stuff in your script using NCustom routines
47              
48             KICKSTART POST SECTION
49             # install package management tool that is used in NCUSTOM_SCRIPT
50             rpm -i http://install/install/rpm/apt-0.5.5cnc6-fr1.i386.rpm ;
51             echo 'rpm http://install/ install/rh90_apt os extras' > /etc/apt/sources.list;
52             apt-get update ;
53              
54             # install and use NCustom
55             apt-get -q -y install perl-NCustom ;
56             ncustom -i ;
57             ncustom -c src_fqdn=install.example.com ;
58             ncustom -n NCUSTOM_SCRIPT
59             ncustom -n smb_ldap_pdc-0.4-rh90.ncus ;
60              
61             =head1 ABSTRACT
62              
63             NCustom provides some file editting routines and some package management hooks to assit in system configuration.
64              
65             File editting:
66             The file editing routines include features such as transactions, and undo by transaction. The original files are archived within a directory tree structure.
67              
68             Package management:
69             You may specify packages (and minumum/maximum/exact versions) that you require to be installed, and a routine to be called if they are not installed. Your routine may use simple "rpm" commands (or whatever you want), or you may use the provided routine that uses "apt". In-built support for other package management tools is on the todo list.
70              
71             System configuration:
72             A commandline interface provides for initialisation, configuration, and invocation (including invocation across the network). This enables NCustom to be used from the post section of Kickstart script. It may also be used stand alone on an already built system.
73              
74             If system configuration tweaking is minor, then scripts (even in the post section of a kickstart configuration) may be more useful. If the system configuration tweaking is related to only one rpm, then re-rolling the rpm with a new post section may be more useful. If there are several packages that need inter-related configuration (eg building a Samba, PDC, LDAP server), then NCustom may improve the speed of development of automated system configuration.
75              
76             =head1 DESCRIPTION
77              
78             File editting:
79             Files are saved into a directory structure within the users home directory. This location may be configured. A file will be saved within a directory structure named after the current transaction name, and also under the "all" directory. Because of this "all" changes, or only changes relating to a "transaciton" may be reversed.
80              
81             Package management:
82             When a package requirement is not met, a routine that you may provide shall be called.
83              
84             System configuration:
85              
86              
87             =head2 EXPORT
88              
89             None by default.
90              
91             =head2 API
92              
93             =over
94              
95             =cut
96              
97              
98             # ///////////////////////////////////////////////////////////////////
99             #<< FF: Functions <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
100             # \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
101              
102             #====================================================================
103             # Inline testing setup and general tests
104              
105             =begin testing
106              
107 1     1   5 use Carp;
  1         157  
  1         22474  
108 1     1   2 use File::Compare ;
  1         106  
  1         875  
109 1     1   1099 use File::Copy ;
  1         46  
  1         743  
110 1     1   5103 use File::Path ;
  1         69  
  1         6  
111 1     1   3 use File::Spec ;
  1         52  
  1         4  
112 1     1   3 use vars qw($output $input);
  1         21  
  1         4  
113              
114             # test setup
115 0         0 $output = File::Spec->rel2abs("./t/embedded-NCustom.o");
116 0         0 $input = File::Spec->rel2abs("./t/embedded-NCustom.i");
117 1 50       42 ok( -d $input)
118             || diag("TEST: requires the data input directory be present");
119 1 50       15 -d $input || die; # as if we have that wrong we could clobber allsorts
120 1         29 rmtree $output;
121 1         371 mkpath $output;
122 1         4818 $ENV{HOME} = $output ; # lets be non-intrusive
123              
124 1 50   1   190 use_ok( "NCustom", qw(:all) )
  1         2  
  1         9983  
  1         1505  
  1         3  
125             || diag("TEST: is a package");
126              
127             sub test_reset {
128             #Test::Inline doesnt execute test blocks in order
129             #it does all basic tests first (seemingly in declaration order),
130             #then examples tests (seemingly in declaration order).
131             #hmmm.. test_rest can erase "why test failed" data
132 2     16   3 rmtree $output;
133 2         6893 mkpath $output;
134 16         199199 &NCustom::constructor();
135 16         4447 transaction("tx1");
136 16         206 system("cp -r $input/subject/* $output");
137              
138             }
139             sub output {
140 16 50   19   62 $_STDOUT_ && diag($_STDOUT_);
141 16 100       487280 $_STDERR_ && diag($_STDERR_);
142             }
143 1         6 output();
144              
145             =end testing
146 1         7  
147 1         395 =cut
148              
149             #====================================================================
150             # load_config
151 1         2  
  1         2  
152 1         1 =begin testing
153              
154 1         2 NCustom::load_config();
155 1 50       3 is($NCustom::Config{'test_data1'}, "global_value")
156             || diag("TEST: sets variables from global conf file 1/3");
157 1 50       4 is($NCustom::Config{'test_data2'}, "global_value")
158             || diag("TEST: sets variables from global conf file 2/3");
159 1 50       14 is($NCustom::Config{'save_dir'}, "$output/.ncustom/save",)
160             || diag("TEST: sets variables from global conf file 3/3");
161              
162 1         264 mkpath "$output/.ncustom/NCustom" ;
163 1         266 copy("$input/MyConfig.pm", "$output/.ncustom/NCustom");
164 1         420 NCustom::load_config();
165 1 50       10 is($NCustom::Config{'test_data1'}, "global_value")
166             || diag(<<' EOF');
167             TEST:
168             TEST: - will use a local conf file if present
169             TEST: - will still inheirit settings from global conf file
170             EOF
171 1 50       536 is($NCustom::Config{'test_data2'}, "local_value")
172             || diag(<<' EOF');
173             TEST:
174             TEST: - local conf file settings will override global conf file settings
175             EOF
176              
177 1         5 my $subref = $NCustom::Config{'get_url'};
178 1         293 my $target_url = "dummy_url";
179 1         278 my $target_dir = "dummy_dir";
180 1         2 &$subref($target_url, $target_dir);
181 1         40 open(STUBSLOG, "< $output/stubs.log");
182 1         5 my @lines = ;
183 1         225 close(STUBSLOG);
184 1 50       21 ok( grep( /get_url ${target_url} ${target_dir}/, @lines) > 0 )
185             || diag("TEST: override works for get_url handler");
186              
187 1         30 TODO: {
188 1         11 local $TODO = "Unload and reload modules, eg Symbol::delete_package.";
189              
190             #testing can reset / toggle
191             #Q: is this useful outside of testing context ?
192             #A: no
193              
194 1         292 rmtree "$output/.ncustom/NCustom" ;
195 1         3 NCustom::load_config();
196 1 50       570 is($NCustom::Config{'test_data1'}, "global_value")
197             || diag("TEST: is re-runnable and resets configuration 1/2");
198 1 50       5 is($NCustom::Config{'test_data2'}, "global_value")
199             || diag("TEST: is re-runnable and resets configuration 2/2");
200             }
201 1         5 output();
202              
203             =end testing
204 1         276  
205 1         678 =cut
206              
207             #====================================================================
208             sub load_config{
209 1         2  
  1         3  
210 1     21 0 3 delete_package("NCustom::Config");# if exists $NCustom::{'Config::'};
  19         159  
211 19         114 delete_package("NCustom::MyConfig");# if exists $NCustom::{'MyConfig::'};
212            
213 21         391 require NCustom::Config ;
214 21         1025 unshift @INC, "$ENV{HOME}/.ncustom";
215 21         3642 eval {require NCustom::MyConfig;} ;
  21         275  
216 21         58 shift @INC;
217 21         2149 return 1;
218             }
219              
220             #====================================================================
221             # transaction
222              
223             =item C
224            
225             trasaction("tx1");
226              
227             Set the current trasaction. If not set it defaults to basename($0). Using the default is normally good enough.
228              
229             =cut
230              
231             #====================================================================
232             sub transaction{
233 21     45 1 135 my ($tx, @rest) = @_;
234 21   33     128 $Transaction = ($tx || basename($0));
235             }
236              
237             #====================================================================
238             # apply_config
239              
240             =begin testing
241              
242 1         2 test_reset();
243              
244 1         2 &NCustom::apply_config();
245              
246 1 50       4 ok( -d "$output/.ncustom/save/all")
247             || diag("TEST: uses a save directory");
248 1 50       52 ok( -d "$output/.ncustom/tmp")
249             || diag("TEST: uses a tmp directory");
250 1 50       52 -d "$output/.ncustom/save/all" || die; # as we could be way off course
251 1         741 output();
252              
253             =end testing
254 1         344  
255 1         13 =cut
256              
257             #====================================================================
258             sub apply_config{
259 1 100   19 0 2 if(! -d $Config{'save_dir'}){
  1         6  
  45         173  
260 1         7 mkpath "$Config{'save_dir'}/all";
  45         372  
261 19         644 mkpath "$Config{'save_dir'}/all.new";
262             }
263 18 100       8587 -d $Config{'tmp_dir'} || mkpath $Config{'tmp_dir'};
264              
265             #TODO# error checking
266             #Q: whether should test src_fqdn (dns lookup/http get/ping) ?
267             #A: no, as might'nt ever matter that it is (potentially) incorrect/offline
268              
269 18         3646 transaction(basename($0)); #so easy to override in testing
270 19         3272 return 1;
271             }
272              
273             #====================================================================
274             # crud_gaurantee
275              
276             =begin testing
277              
278 1         2 test_reset();
279 1         3 my $msg;
280 1 50       7 ok( &NCustom::crud_gaurantee("$output/dir1/file1","read",\$msg) )
281             || diag("TEST: says if you can read a file.");
282 1 50       36 ok( &NCustom::crud_gaurantee("$output/dir1/file1","update",\$msg) )
283             || diag("TEST: says if you can update a file.");
284 1 50       58 ok(!&NCustom::crud_gaurantee("$output/dir1/file9","update",\$msg) )
285             || diag("TEST: says if you cant update a file.");
286             # should do more cant't testing
287             # and test that get $msg
288             #
289 1 50       896 ok( &NCustom::crud_gaurantee("$output/dir1/file1","delete",\$msg) )
290             || diag("TEST: says if you can delete a file.");
291 1 50       331 ok( &NCustom::crud_gaurantee("$output/dir1/file9","create",\$msg) )
292             || diag("TEST: says if you can create a file.");
293 1 50       262 ok( &NCustom::crud_gaurantee("$output/dir1/subdir1/file9","create",\$msg) )
294             || diag("TEST: says if you can create a file and its dirs.");
295             #
296 1 50       327 ok( &NCustom::crud_gaurantee("$output/dir1/file1","r",\$msg) )
297             || diag("TEST: says if you can r a file (short notation).");
298 1 50       357 ok(!&NCustom::crud_gaurantee("$output/dir1/file9","r",\$msg) )
299             || diag("TEST: says if you cant r a file (short notation).");
300 1 50       349 ok( &NCustom::crud_gaurantee("$output/dir1/file1","ru",\$msg) )
301             || diag("TEST: says if you can ru a file (short notation).");
302             # should do other crud combos
303             #
304 1         339 output();
305              
306             =end testing
307 1         279  
308 1         317 =cut
309              
310             #====================================================================
311             sub crud_gaurantee{
312 1     142 0 3 my ($file, $check, $msgref) = @_ ;
  1         7  
  19         1021  
313 1         8 my $rc = 1;
  19         241  
314            
315             #TODO# fix so dont fall through to success on invalid checks
316              
317             #CRUD: Create guarantee
318             # interpreting as create/clobber
319 142 100 66     1679 if($check =~ /create/i || ($check =~ /^[crud]+$/i && $check =~ /c/i )){
      66        
320 142 100       466 if(! -e dirname($file)){
321 142         2210 $rc = mkpath(dirname($file));
322 109 50       18733 unless($rc){$$msgref = "Cant create dir for file: $file."; return 0; }
  52         439057  
  52         527  
323             }
324 0 50       0 if(! -w dirname($file)){
325 0         0 $$msgref = "Cant create in dir for file: $file."; return 0;
  109         19307  
326             }
327 0 50 66     0 if(-e $file && ! -f $file){
328 0         0 $$msgref = "Shant clobber existing non-plain-file: $file."; return 0;
  109         4546  
329             }
330 0 50 66     0 if(-f $file && ! -w $file){
331 0         0 $$msgref = "Cant clobber existing plain-file: $file."; return 0;
  109         5029  
332             }
333             }
334              
335             #CRUD: Read guarantee
336 0 100 66     0 if($check =~ /read/i || ($check =~ /^[crud]+$/i && $check =~ /r/i )){
      66        
337 0 100       0 if(! -e $file){
338 142         2224 $$msgref = "Cant read non-existant file: $file."; return 0;
  30         7749  
339             }
340 1 50       8 if(! -f $file){
341 1         6 $$msgref = "Shant read from non-plain-file: $file."; return 0;
  29         807  
342             }
343 0 50       0 if(! -r $file){
344 0         0 $$msgref = "Cant read file: $file."; return 0;
  29         1010  
345             }
346             }
347              
348             #CRUD: Update guarantee
349 0 100 100     0 if($check =~ /update/i || ($check =~ /^[crud]+$/i && $check =~ /u/i )){
      66        
350 0 100       0 if(! -e $file){
351 141         1520 $$msgref = "Cant update non-existant file: $file."; return 0;
  3         103  
352             }
353 1 50       9 if(! -f $file){
354 1         5 $$msgref = "Shant update non-plain-file: $file."; return 0;
  2         51  
355             }
356 0 50       0 if(! -r $file){
357 0         0 $$msgref = "Cant read file: $file."; return 0;
  2         54  
358             }
359 0 50       0 if(! -w $file){
360 0         0 $$msgref = "Cant write file: $file."; return 0;
  2         100  
361             }
362             }
363              
364             #CRUD: Delete guarantee
365 0 100 66     0 if($check =~ /delete/i || ($check =~ /^[crud]+$/i && $check =~ /d/i )){
      66        
366             #TODO# implement when we need it
367 0 50       0 if(! -e $file){
368 140         2045 $$msgref = "Cant delete non-existant file: $file."; return 0;
  21         599  
369             }
370 0 50       0 if(! -f $file){
371 0         0 $$msgref = "Shant delete non-plain-file: $file."; return 0;
  21         1080  
372             }
373 0 50       0 if(! -w dirname($file)){
374 0         0 $$msgref = "Cant delete from dir of file: $file."; return 0;
  21         3241  
375             }
376             }
377              
378 0         0 return 1;
379             }
380              
381             #====================================================================
382             # save_files, save_file, save_file2
383              
384             =item C
385              
386             =begin example
387              
388 1         3 test_reset();
389 1 50       2 ok(-f "$output/dir2/file1")
390             || diag("TEST: must copy over the subject dir");
391 1 50       5 can_ok("NCustom", qw(save_files))
392             || diag("TEST: is a public function of NCustom");
393              
394             =end example
395              
396             =for example begin
397            
398 1         251 save_files("~/dir2/file1");
399            
400 1         1539 save_files(<<' EOF');
401             ~/dir2/file2
402             ~/dir3/*
403             EOF
404              
405             =for example end
406              
407             There is not much point to this - the customise works or not.
408             But it helps while developing the customisation.
409             Note: changes effected by using NCustom functions are saved automatically.
410              
411             =for example_testing
412 1 50       960 ok(-f "$output/.ncustom/save/all/$output/dir2/file1")
413             || diag(<<' EOF');
414             TEST:
415             TEST: - saves given file(s) to the overall archive
416             TEST: - performs filename expansion eg ~/
417             EOF
418 1 50       14 ok(-f "$output/.ncustom/save/tx1/$output/dir2/file1")
419             || diag(<<' EOF');
420             TEST:
421             TEST: - also saves given file(s) to the current tx archive
422             EOF
423 1 50       60 ok(-f "$output/.ncustom/save/all/$output/dir2/file2")
424             || diag(<<' EOF');
425             TEST: multiple arguments:
426             TEST: - accepts multiple arguments in one newline delimited string
427             TEST: - are also saved to both the overall, and current tx, archives 1/2
428             EOF
429 1 50       10778 ok(-f "$output/.ncustom/save/tx1/$output/dir2/file2")
430             || diag(<<' EOF');
431             TEST: multiple arguments:
432             TEST: - are also saved to both the overall, and current tx, archives 2/2
433             EOF
434 1 50       457 ok(-f "$output/.ncustom/save/all/$output/dir3/file1")
435             || diag("TEST: supports wildcarding 1/4");
436 1 50       352 ok(-f "$output/.ncustom/save/tx1/$output/dir3/file1")
437             || diag("TEST: supports wildcarding 2/4");
438 1 50       313 ok(-f "$output/.ncustom/save/all/$output/dir3/file2")
439             || diag("TEST: supports wildcarding 3/4");
440 1 50       291 ok(-f "$output/.ncustom/save/tx1/$output/dir3/file2")
441             || diag("TEST: supports wildcarding 4/4");
442             #
443             # extra tests
444             #
445 1         296 transaction("tx2");
446 1         301 save_files("~/dir4/file1");
447 1 50       319 ok(-f "$output/.ncustom/save/tx2/$output/dir4/file1")
448             || diag(<<' EOF');
449             TEST:
450             TEST: - saves to a corresponding tx archive when the tx changes 1/2
451             EOF
452 1 50       8 ok(! -f "$output/.ncustom/save/tx1/$output/dir4/file1")
453             || diag(<<' EOF');
454             TEST:
455             TEST: - saves to a corresponding tx archive when the tx changes 2/2
456             EOF
457 1         106 copy("$input/dir4file1.v2", "$output/dir4/file1");
458 1         1984 save_files("~/dir4/file1");
459 1         380 my @matches = glob("$output/.ncustom/save/tx2/$output/dir4/*");
460 1 50       470 is($#matches, 1)
461             || diag(<<' EOF');
462             TEST: if a file is saved to an archive, and it is already there:
463             TEST: - the file will be saved with a suffix
464             EOF
465             #this better test didnt work because of filename mangling with samba
466             #ok(-f "$output/.ncustom/save/tx2/$output/dir4/file1.AT*")
467 1         269 save_files("~/dir4/file1");
468 1         25 @matches = glob("$output/.ncustom/save/tx2/$output/dir4/*");
469 1 50       1053 is($#matches, 1)
470             || diag(<<' EOF');
471             TEST: if a file is saved to an archive, and it is already there:
472             TEST: - if there is no change it wont be saved again
473             EOF
474 1         194 output();
475              
476 1         103 =cut
477 1         867  
478             #====================================================================
479             sub save_file;
480 1         3 sub save_files {
481 1     12 1 5 my ($files, @rest) = @_;
  0         0  
482 140         721 my @lines = split(/\n/,$files);
483 1         6 my $status = 1;
  12         67  
484 1         5 my $line;
  12         75  
485 1         4  
486 12         41 foreach $line (@lines){
487 12         566 my ($file, @rest) = ($line =~ /\s*(.*)/);
488 12 50       46 save_file($file) || ($status = 0);
489             }
490 13         170 return $status;
491             }
492              
493             #====================================================================
494             sub save_file2;
495             sub expand_filenames;
496             sub save_file {
497             #save_file reduces the problem to dealing with indivudual files
498             #it then calls save_file2 to do the work
499 13     33 0 59 my ($file, @rest) = @_;
500 12         193 my ($msg, $rc, $f, $global_dest, $local_dest);
501 33         140 my $status = 1;
502              
503 33         80 foreach my $f ( expand_filenames($file)){
504 33         104 chomp $f;
505 33         194 $global_dest = "$Config{'save_dir'}/all/$f";
506 35         128 $local_dest = "$Config{'save_dir'}/$Transaction/$f";
507 35 50       660 save_file2($f, $global_dest) || ($status =0);
508 35 50       241 save_file2($f, $local_dest) || ($status =0);
509 35 100       373 if(! -e $f){
510 1         2 #So we must be dealing with a new file.
511             #For consistencey:
512 35 100       233 -d "$Config{'save_dir'}/${Transaction}"
513 1         161 || mkpath "$Config{'save_dir'}/${Transaction}";
  1         20  
514 1         465  
515             #We have a special place for noting new files:
516 35         961 $global_dest = "$Config{'save_dir'}/all.new/$f";
517 11         3853 $rc = crud_gaurantee($global_dest,"create",\$msg);
518 11 50       52 unless($rc){carp "save_file: $msg"; return 0; }
  11         185  
  11         112  
519 0         0 open(TOUCH,"> $global_dest");
520 0         0 close(TOUCH);
521              
522 11         1590 $local_dest = "$Config{'save_dir'}/${Transaction}.new/$f";
523 11         197 $rc = crud_gaurantee($local_dest,"create",\$msg);
524 11 50       72 unless($rc){carp "save_file: $msg"; return 0; }
  11         58  
  11         56  
525 0         0 open(TOUCH,"> $local_dest");
526 0         0 close(TOUCH);
527             }
528             }
529 11         1193 return $status;
530             }
531              
532             #====================================================================
533             sub save_file2 {
534 11     70 0 193 my ($file, $dest, @rest) = @_;
535 33         499 my $rc = 1;
536 70         211 my $msg;
537 70         613 my ($sec, $min, $hr) = (localtime)[0..2];
538 70         87 my $suffix = ".AT_$hr:$min:$sec" ;#also used in undo_file
539             #TODO# $sec not fine grained enough, will get caught out one day
540              
541 70 100       3782 if(! -e $file){
542             #we dont save something that doesnt exist
543             #but still relatively normal as we may be dealing with a new creation
544 70         898 return 1;
545             }
546 70 50 66     4446 if((-e $dest) && (compare($dest, $file) == 0)){
547             #already saved and files are the same
548             #TODO# add more checking here, -f, perms...
549             #carp "save_file: not saving, as not changed since last save: $file";
550 22         184 return 1;
551             }
552 48 100 66     2811 if((-e $dest) && (compare($dest, $file) != 0)){
553             #already saved but files are different
554             #better get a new name
555 0         0 $dest = "${dest}$suffix";
556             }
557              
558 48         2646 $rc = crud_gaurantee($dest,"create",\$msg);
559 4 50       851 unless($rc){carp "save_file2: $msg"; return 0; }
  48         446  
  48         278  
560              
561 0         0 $rc = copy($file, $dest);
562 0 50       0 unless($rc){carp "save_file2: copy failed: $file, $dest."; return 0; }
  48         776  
  48         71628  
563              
564 0         0 return 1;
565             }
566             #====================================================================
567             sub expand_filenames {
568 0     64 0 0 my ($file_list, @rest) = @_ ;
569 48         317 my @result;
570              
571             #tag#
572 64         219 my @lines = split(/\n/,$file_list);
573 64         117 foreach my $line (@lines){
574 64         307 $line =~ s/^\s+//; #trim leading whitespace
575 64 50       187 next if $line =~ /^#/; #TODO# comments need much work
576             #TODO# find the perl fn for the following kludge
577             #cant use builtin glob as in some instances we're dealing with a newfile
578             #(builtin glob matches existing)
579 66         492 my @filename_expansion = split(/\s+/, `echo $line`);
580 66         290 foreach my $filename (@filename_expansion){
581 66         626156 push @result, $filename;
582             }
583             }
584 66         2379 return @result;
585             }
586              
587             #====================================================================
588             # initialise
589              
590             =item C
591              
592             =begin example
593              
594 1         3 test_reset();
595 1 50       2 can_ok("NCustom", qw(initialise))
596             || diag("TEST: is a public function of NCustom");
597              
598             =end example
599              
600             =for example begin
601            
602 1         3 initialise();
603              
604             =for example end
605              
606             Initialise the archive of saved files. As this deletes files this is not done automatically.
607              
608             =for example_testing
609 1         77 @matches = glob("$output/.ncustom/save/*");
610 1 50       1219 is($#matches, 3) # ie 4 entries (all, all.new, tx1, tx1.new)
611             || diag("TEST: removes all save files");
612 1 50       201 ok(-d "$output/.ncustom/save/all")
613             || diag("TEST: creates an empty skeleton save dir");
614 1         14 output();
615              
616 1         626 =cut
617 1         686  
618             #====================================================================
619             sub initialise {
620 1 50   1 1 2 rmtree($Config{'save_dir'}) || return 0;
  70         1559  
621 1 50       7 mkpath("$Config{'save_dir'}/all") || return 0;
  64         1816  
622 1 50       1150 mkpath("$Config{'save_dir'}/all.new") || return 0;
623 1 50       6 mkpath("$Config{'save_dir'}/$Transaction") || return 0;
  1         387  
624 1 50       3 mkpath("$Config{'save_dir'}/${Transaction}.new") || return 0;
  1         141  
625 1 50       6 rmtree($Config{'tmp_dir'}) || return 0;
  1         160  
626 1 50       126 mkpath($Config{'tmp_dir'}) || return 0;
627 1         280 return 1;
628             }
629              
630             #====================================================================
631             # commit_file
632              
633             =begin testing
634              
635 1         3 test_reset();
636              
637 1         3 my $tmp = "$NCustom::Config{'tmp_dir'}";
638             #use tmp for files that will be altered/moved (to keep input unaltered)
639              
640 1         4 copy("$input/dir5file1", "$tmp/dir5file1");
641 1         47 &NCustom::commit_file("$tmp/dir5file1", "$output/dir5/file1");
642 1 50       63 ok(-f "$output/dir5/file1")
643             || diag("TEST: checks in a new file");
644 1 50       625 ok(! -f "$output/.ncustom/save/tx1/$output/dir5/file1")
645             || diag("TEST: doesnt archive files that didnt already exist");
646             #TODO# that is interesting, that means that restore wont delete it
647             #TODO# could handle by save archiving filename.new, the resore knows to rm
648              
649 1         213 copy("$input/dir5file1.v2", "$tmp/dir5file1.v2");
650 1         1715 &NCustom::commit_file("$tmp/dir5file1.v2", "$output/dir5/file1");
651 1 50       551 is(compare("$output/dir5/file1", "$input/dir5file1.v2"), 0)
652             || diag("TEST: checks in over an existing file");
653 1 50       1096 is(compare("$output/.ncustom/save/tx1/$output/dir5/file1","$input/dir5file1"), 0)
654             || diag("TEST: saves things before it clobbers them");
655              
656 1         21 copy("$input/dir5file1", "$tmp/dir5file1");
657 1         868 &NCustom::commit_file("$tmp/dir5file1", "$output/dir5/subdir1/file1");
658 1 50       549 ok(-f "$output/dir5/subdir1/file1")
659             || diag("TEST: checks in a new file, creating subdirs required");
660 1         375 output();
661              
662             =end testing
663 1         56  
664 1         1013 =cut
665              
666             #====================================================================
667             sub commit_file{
668 1     20 0 8 my($newfile, $file, @rest) = @_ ;
  1         11  
  1         145  
669 1         11 my $rc = 1;
  1         2  
  1         5  
670 20         331 my $msg;
671              
672 1         232 $rc = crud_gaurantee($newfile,"read delete",\$msg);
  1         19  
  20         64  
673 1 50       624 unless($rc){carp "commit_file: $msg"; return 0; }
  20         41  
  20         114  
  20         314  
674              
675 0         0 $rc = crud_gaurantee($file,"create",\$msg);
676 0 50       0 unless($rc){carp "commit_file: $msg"; return 0; }
  20         147  
  20         88  
677              
678 0         0 $rc = save_file($file);
679 0 50       0 unless($rc){ carp "commit_file: save_file: $file failed\n"; return 0; }
  20         82  
  20         68  
680              
681 0         0 $rc = copy($newfile, $file);
682 0 50       0 unless($rc){carp "commit_file: copy failed: $newfile, $file.\n"; return 0;}
  20         352  
  20         13624  
683              
684 0         0 $rc = unlink($newfile);
685 0 50       0 unless($rc){carp "commit_file: unlink failed: $newfile.\n"; return 0;}
  20         2939  
  20         85  
686              
687 0         0 return 1;
688             }
689              
690             #====================================================================
691             # overwrite_file
692              
693             =item C
694              
695             =begin example
696              
697 1         3 test_reset();
698 1 50       3 can_ok("NCustom", qw(overwrite_file))
699             || diag("TEST: is a public function of NCustom");
700              
701             =end example
702              
703             =for example begin
704            
705 1         7 overwrite_file(file => "~/dir6/file1", text => ' some content');
706            
707 1         89 overwrite_file(file => "~/dir6/file2",
708             strip => '^\s{4}',
709             text => <<' EOF');
710             This will be line 1 of the new content of the file.
711             This will be line 2 of the new content of the file.
712             This, line3, will still be indented. As will line 4.
713             I bet there will be a dollar sign and two single quotes on the next line.
714             'I told you so ! Now you owe me $20', I would then say.
715             This will be the last line.
716             EOF
717              
718             =for example end
719              
720             Overwrite file overwrites $file with $text.
721              
722             So that you can have pretty indentation when using here documents, the pattern $strip is stripped out prior to processing.
723              
724             More clearly, overwrite file is equivalent to:
725            
726             open(FILE,">$file");
727             $text =~ s/$strip//;
728             print FILE $text;
729              
730             =for example_testing
731 1 50       1299 is(compare("$output/dir6/file1", "$input/dir6file1.v2"), 0)
732             || diag(<<' EOF');
733             TEST:
734             TEST: - is logically equivalent to ">"
735             TEST: - will not strip anything by default
736             TEST: - performs filename expansion eg ~/
737             TEST: - will create new file if required
738             EOF
739 1 50       18 is(compare("$output/dir6/file2", "$input/dir6file2.v2"), 0)
740             || diag(<<' EOF');
741             TEST:
742             TEST: - will strip a given pattern from the text
743             EOF
744 1         39 overwrite_file(file => "~/dir6/subdir1/file1", text => ' some content');
745 1 50       884 is(compare("$output/dir6/subdir1/file1", "$input/dir6file1.v2"), 0)
746             || diag(<<' EOF');
747             TEST:
748             TEST: - will create subdirs as needed for new files
749             EOF
750 1         477 output();
751              
752 1         37 =cut
753 1         871  
754             #====================================================================
755             sub change_file {
756 1     15 0 92 my %args = @_ ;
  0         0  
757 1   50     6 my $change = ($args{'change'} || "");
  20         567  
758 15   50     149 my $files = ($args{'files'} || "");
759 1   50     2 my $body = ($args{'body'} || "");
  15         72  
760 1   100     8 my $strip = ($args{'strip'} || "");
  15         56  
761 1         6 my $status = 1;
  15         57  
762 15         147 my $rc = 1;
763              
764             # input checks
765 15 50       24 if($files eq ""){
766 15         26 carp "change_file: file name is blank.\n"; return 0;
  15         48  
767             }
768 0 50       0 if($change !~ /^(overwrite|append|prepend|edit)$/){
769 0         0 carp "change_file: Invalid type of change: $change."; return 0;
  15         319  
770             }
771              
772             # pre-process body (text / code)
773 0 100       0 $body =~ s/$strip//mg unless $strip eq "";
774              
775             # pre-process filenames
776             # then invoke processing on each one
777 0         0 foreach my $file (expand_filenames($files)){
778 15         542 chomp $file;
779 15         191 $rc = change_file2(change => $change, file => $file, body => $body);
780 17 50       116 unless($rc){$status = 0};
  17         147  
781             }
782              
783 17         216 return $status;
784             }
785              
786             #====================================================================
787             sub change_file2 {
788 0     17 0 0 my %args = @_ ;
789 15   50     477 my $change = ($args{'change'} || "");
790 17   50     587 my $file = ($args{'file'} || "");
791 17   50     199 my $body = ($args{'body'} || "");
792 17         214 my $rc = 1;
793 17         72 my $msg = "";
794 1         3  
795             #we use a tmp file
796 17         42 my $newfile = "$Config{'tmp_dir'}/" . basename($file);
797 1         193 $rc = crud_gaurantee($newfile, "create", \$msg);
  1         19  
  17         169  
798 1 50       528 unless($rc){carp "change_file2: $msg"; return 0; }
  17         4591  
  17         702  
  17         84  
799              
800 0 100       0 if($change =~ /overwrite/ ){
801 0         0 $rc = open(NEWFILE, ">$newfile");
802 17         136 print NEWFILE $body ;
803 6         825 close(NEWFILE);
804             }
805 6 100       60 if($change =~ /append/ ){
806 6 100       415 if( -e $file){
807 17         120 $rc = crud_gaurantee($file, "read", \$msg);
808 3 50       121 unless($rc){carp "change_file2: $msg"; return 0; }
  1         26  
  1         13  
809 0         0 copy($file, $newfile);
810             }
811 0         0 open(NEWFILE, ">>$newfile");
812 1         347 print NEWFILE $body ;
813 3         1568 close(NEWFILE);
814             }
815 3 100       32 if($change =~ /prepend/ ){
816 3         180 open(NEWFILE, ">$newfile");
817 17         818 print NEWFILE $body ;
818 3         603 close(NEWFILE);
819 3 100       38 -f $file && system("cat $file >> $newfile"); #TODO# do in perl
820             }
821 3 100       216 if($change =~ /edit/ ){
822 3         23112 $rc = crud_gaurantee($file, "read", \$msg);
823 17 50       126 unless($rc){carp "change_file2: $msg"; return 0; }
  5         45  
  5         48  
824 0         0 open(FILE, "<$file");
825 0         0 open(NEWFILE, ">$newfile");
826             # select newfile, so prints in $body behave as expected
827 5         350 my $old_fh = select(NEWFILE);
828             # the action
829 2     2   3569 no strict; no warnings ;
  2     2   13  
  2         6863  
  2         5  
  2         88  
  2         12  
830 5         594 while( ){
831 5         57 eval $body;
832 5 50       93 if($@){carp "change_file2: code \n$body \nraised the error $@"; return 0;}
  17         2253  
  17         83  
833             } continue {
834 0         0 print;
835             }
836 2     2   4 use strict; use warnings;
  2     2   216  
  2         41  
  2         4  
  2         72  
  2         12  
837 0         0 close(NEWFILE);
838 17         154 close(FILE);
839 5         391 select($old_fh);
840             }
841              
842 5         48 return commit_file($newfile, $file);
843             }
844              
845             #====================================================================
846             sub overwrite_file {
847 5     6 1 47 my %args = @_ ;
848 17   50     121 my $file = ($args{'file'} || "");
849 6   50     65 my $text = ($args{'text'} || "");
850 6   100     35 my $strip = ($args{'strip'} || "");
851            
852 6         29 return change_file(change => "overwrite", files => $file,
853             body => $text, strip => $strip);
854             }
855              
856             #====================================================================
857             # append_file
858              
859             =item C
860              
861             =begin example
862              
863 1         3 test_reset();
864 1 50       6 can_ok("NCustom", qw(append_file))
865             || diag("TEST: is a public function of NCustom");
866              
867             =end example
868              
869             =for example begin
870            
871 1         5 append_file(file => "~/dir7/file1", text => 'an extra line');
872            
873 1         83 append_file(file => "~/dir7/file2",
874             strip => '^\s{4}',
875             text => <<' EOF');
876             An extra line to add on to the file.
877             This line, will be indented.
878             The last last line with some special chars *!@$%.'"
879             EOF
880              
881             =for example end
882              
883             Append file is the same as overwrite file, except it behaves as ">>" instead of ">".
884              
885             =for example_testing
886 1 50       1371 is(compare("$output/dir7/file1", "$input/dir7file1.v2"), 0)
887             || diag(<<' EOF');
888             TEST:
889             TEST: - is logically equivalent to ">>"
890             TEST: - will not strip anything by default
891             TEST: - performs filename expansion eg ~/
892             TEST: - will create new file if required
893             EOF
894 1 50       21 is(compare("$output/dir7/file2", "$input/dir7file2.v2"), 0)
895             || diag(<<' EOF');
896             TEST:
897             TEST: - will strip a given pattern from the text
898             EOF
899 1         40 append_file(file => "~/dir7/subdir1/file1", text => 'an extra line');
900 1 50       926 is(compare("$output/dir7/subdir1/file1", "$input/dir7file1.v2"), 0)
901             || diag(<<' EOF');
902             TEST:
903             TEST: - will create subdirs as needed for new files
904             EOF
905 1         499 output();
906              
907 1         32 =cut
908 1         1637  
909             #====================================================================
910             sub append_file {
911 1     3 1 2 my %args = @_ ;
  6         57  
912 1   50     5 my $file = ($args{'file'} || "");
  6         42  
913 3   50     35 my $text = ($args{'text'} || "");
914 1   100     5 my $strip = ($args{'strip'} || "");
  3         22  
915 1         5
916 1         4 return change_file(change => "append", files => $file,
  3         25  
917             body => $text, strip => $strip);
918             }
919              
920             #====================================================================
921             # prepend_file
922              
923             =item C
924              
925             =begin example
926              
927 1         3 test_reset();
928 1 50       5 can_ok("NCustom", qw(prepend_file))
929             || diag("TEST: is a public function of NCustom");
930              
931             =end example
932              
933             =for example begin
934            
935 1         6 prepend_file(file => "~/dir8/file1", text => 'an extra line');
936            
937 1         115 prepend_file(file => "~/dir8/file2",
938             strip => '^\s{4}',
939             text => <<' EOF');
940             An extra line at the start of the file.
941             This line, will be indented.
942             Some special chars *!@$%.'"
943             The last extra line added to the start of the file.
944             EOF
945              
946             =for example end
947              
948             Prepend behaves the same as append, except the text is added to the start instead of the end.
949              
950 1         2 =for example_testing
951 1 50       2080 is(compare("$output/dir8/file1", "$input/dir8file1.v2"), 0)
952             || diag(<<' EOF');
953 1         206 TEST:
  1         18  
954 1         490 TEST: - is logically equivalent to ">>"
955             TEST: - will not strip anything by default
956             TEST: - performs filename expansion eg ~/
957             TEST: - will create new file if required
958             EOF
959 1 50       30 is(compare("$output/dir8/file2", "$input/dir8file2.v2"), 0)
960             || diag(<<' EOF');
961             TEST:
962             TEST: - will strip a given pattern from the text
963             EOF
964 1         42 prepend_file(file => "~/dir8/subdir1/file1", text => 'an extra line');
965 1 50       1057 is(compare("$output/dir8/subdir1/file1", "$input/dir8file1.v2"), 0)
966             || diag(<<' EOF');
967             TEST:
968             TEST: - will create subdirs as needed for new files
969             EOF
970 1         573 output();
971              
972 1         42 =cut
973 1         793  
974             #====================================================================
975             sub prepend_file {
976 1     3 1 4 my %args = @_ ;
  3         34  
977 1   50     10 my $file = ($args{'file'} || "");
  3         31  
978 3   50     47 my $text = ($args{'text'} || "");
979 1   100     7 my $strip = ($args{'strip'} || "");
  3         25  
980 1         3
981 1         7 return change_file(change => "prepend", files => $file,
  3         18  
982             body => $text, strip => $strip);
983             }
984              
985             #====================================================================
986             # edit_file
987              
988             =item C
989              
990             =begin example
991              
992 1         3 test_reset();
993 1 50       7 can_ok("NCustom", qw(edit_file))
994             || diag("TEST: is a public function of NCustom");
995              
996             =end example
997              
998             =for example begin
999            
1000 1         9 edit_file(file => "~/dir9/file1", code => 's/file/FILE/g;');
1001            
1002 1         79 edit_file(file => "~/dir9/file2",
1003             strip => '^\s{4}',
1004             code => <<' EOF');
1005             s/my\.example\.com/whatever\.com/g;
1006             s/^$/replace all blank lines with these three lines
1007             two of three, with 4 leading spaces
1008             and three of three/ ;
1009             s/might/WILL/g;
1010             EOF
1011            
1012 1         1369 edit_file(file => <<' EOF', strip => '^\s{6}', code => <<' EOF');
1013             ~/dir9/file3
1014             ~/dir10/*
1015             EOF
1016             s/file/FILE/g;
1017             s/least/LEASTWAYS/g;
1018             EOF
1019              
1020             =for example end
1021              
1022             Edit file is similar to:
1023            
1024             perl -i -e "$code" $file
1025              
1026             With edit file, $file must exist.
1027             As with the other routines, $code has the pattern $strip stripped out.
1028              
1029             You can also provide multiple filenames to be editted. This holds true for the other routines too.
1030              
1031 1         7 =for example_testing
1032 1 50       22 is(compare("$output/dir9/file1", "$input/dir9file1.v2"), 0)
1033             || diag(<<' EOF');
1034 1         241 TEST:
  1         16  
1035 1         459 TEST: - simple edit file
1036             EOF
1037             #
1038 1 50       18 is(compare("$output/dir9/file2", "$input/dir9file2.v2"), 0)
1039             || diag(<<' EOF');
1040             TEST:
1041             TEST: - multi substitution edit
1042             EOF
1043 1 50       36 is(compare("$output/dir9/file3", "$input/dir9file3.v2"), 0)
1044             || diag("TEST: - edits multiple files 1/3.");
1045 1 50       831 is(compare("$output/dir10/file1", "$input/dir10file1.v2"), 0)
1046             || diag("TEST: - edits multiple files 2/3.");
1047 1 50       500 is(compare("$output/dir10/file2", "$input/dir10file2.v2"), 0)
1048             || diag("TEST: - edits multiple files 3/3.");
1049             #
1050 1         471 output();
1051              
1052 1         536 =cut
1053 1         559  
1054             #====================================================================
1055             sub edit_file {
1056 1     3 1 2 my %args = @_ ;
  3         35  
1057 1   50     2 my $file = ($args{'file'} || "");
  3         29  
1058 3   50     39 my $code = ($args{'code'} || "");
1059 1   100     6 my $strip = ($args{'strip'} || "");
  3         23  
1060 1         2
1061 1         4 return change_file(change => "edit", files => $file,
  3         24  
1062             body => $code, strip => $strip);
1063             }
1064              
1065             #====================================================================
1066             # undo_files
1067              
1068             =item C
1069              
1070             =begin example
1071              
1072 1         7 test_reset();
1073 1 50       3 can_ok("NCustom", qw(undo_files))
1074             || diag("TEST: is a public function of NCustom");
1075 1         5 save_files("~/dir11/file1 ~/dir11/file2");
1076 1         84 transaction("tx2");
1077 1         1716 save_files("~/dir11/file3");
1078 1         32 transaction("tx3");
1079 1         16 save_files("~/dir11/file4");
1080 1         22 transaction("tx4");
1081 1         11 save_files("~/dir11/file5");
1082 1         35 transaction("tx5");
1083 1         12 save_files("~/dir11/file6");
1084 1         32 transaction("tx6");
1085 1         18 save_files("~/dir11/file7");
1086 1         26 transaction("tx7");
1087 1         16 save_files("~/dir11/file8");
1088 1         19 rmtree("$output/dir11");
1089 1         13 mkpath("$output/dir11");
1090              
1091             =end example
1092              
1093             =for example begin
1094            
1095 1         1826 undo_files("tx1");
1096              
1097 1         197 undo_files("~/.ncustom/save/tx2");
1098              
1099 1         14 undo_files("tx3 tx4");
1100              
1101 1         20 undo_files(<<' EOF');
1102             tx5
1103             ~/.ncustom/save/tx6
1104             EOF
1105              
1106             =for example end
1107              
1108             Undo transaction will restore the files from a given transaction archive directory. That includes removing any new files that were created. For any directories that it cannot find, it will try looking in $Config{'save_dir'}.
1109             Undo does not: restore files that were edited by non-NCustom function if they were not first saved using NCuston::save_files; delete new directories that were created (yet).
1110             Again: this is only a development aid.
1111              
1112 1         2 =for example_testing
1113 1 50 33     20 ok(-f "$output/dir11/file1" && -f "$output/dir11/file2")
1114             || diag("TEST: restores files for a given customisation");
1115 1 50       320 ok(-f "$output/dir11/file3")
  1         26  
  1         21  
1116 1         429 || diag("TEST: restores files for a given directory");
1117 1 50 33     106 ok(-f "$output/dir11/file4" && -f "$output/dir11/file5")
1118             || diag("TEST: restores for multiple customisations at once");
1119 1 50 33     969 ok(-f "$output/dir11/file6" && -f "$output/dir11/file7")
1120             || diag("TEST: handles mixed multi-line arguments");
1121 1 50       511 ok(!-f "$output/dir11/file8")
1122             || diag("TEST: doesnt restore too much");
1123 1         405 undo_files("all");
1124 1 50       362 ok(-f "$output/dir11/file8")
1125             || diag("TEST: will restore all");
1126 1         412 transaction("tx8");
1127 1         56 mkpath("$output/dir12/subdir1");
1128 1         921 overwrite_file(file => "~/dir12/file1", text => ' some content');
1129 1         413 transaction("tx9");
1130 1         11 overwrite_file(file => "~/dir12/file2", text => ' some content');
1131 1         25 transaction("tx10");
1132 1         15 overwrite_file(file => "~/dir12/file3", text => ' some content');
1133 1 50 33     30 ok( -f "$output/dir12/file1"
1134             && -f "$output/dir12/file2"
1135             && -f "$output/dir12/file3")
1136             || diag("TEST: new files are setup ready for undo test");
1137 1         12 undo_files("tx8");
1138 1 50       208 ok(! -f "$output/dir12/file1")
1139             || diag("TEST: removes newly created files");
1140 1 50 33     887 ok(-f "$output/dir12/file2" && -f "$output/dir12/file3")
1141             || diag("TEST: doesnt removes too much");
1142 1         44 undo_files("all");
1143 1 50 33     951 ok( ! -f "$output/dir12/file1"
1144             && ! -f "$output/dir12/file2"
1145             && ! -f "$output/dir12/file3")
1146             || diag("TEST: removes all new files for \"all\" transaction");
1147             #
1148 1         449 output();
1149              
1150 1         155 =cut
1151 1         1390  
1152             #====================================================================
1153             sub undo_file ;
1154 1         6 sub delete_file ;
1155 1         7 sub undo_files{
1156 3     7 1 30 my ($names, @rest) = @_;
1157 1         6 my $status = 1;
  3         33  
1158 1         5  
1159 1         3 #tag#
1160 7         37 foreach my $dir ( expand_filenames($names)){
1161 7 100       20 if(! -e $dir){
1162             # if dir (ie tx) to undo isnt an absolute dir, assume relative to save_dir
1163 7         51 $dir = "$Config{'save_dir'}/$dir";
1164             }
1165             # now we need to expand again, as it may be wildcarded
1166 9         270 foreach my $d ( expand_filenames($dir)){
1167 7 50       105 if(! -e $d){
1168 9         102 carp "undo_files: dir doesnt exist: $d.";
1169 9         822 $status = 0;
1170 0         0 next;
1171             }
1172             #TODO# maybe dont need this restriction, makes it safer though...
1173             #restrict to existing archive dirs (transactions)
1174 0 50       0 if( $d !~ m|$Config{'save_dir'}/([^/]*)| ){
1175 0         0 carp "undo_files: dir isnt an archive dir: $d.";
1176 9         546 $status = 0;
1177 0         0 next;
1178             }
1179             #print "\nRestoring files from archive dir: $d \n\t";
1180 0 50       0 find(\&undo_file, "$d") || ($status = 0);
1181              
1182             #print "\nDeleting files that didnt exist before: $d \n\t";
1183             #dir undoing may be a subtree of a transaction's archive dir
1184             #so get the corresponding subtree of archive.new dir
1185 0         0 my $d_new = $d ;
1186 9         2146 $d_new =~ s|($Config{'save_dir'}/[^/]*)|$1.new| ;
1187 9 100       40 if( -e $d_new){
1188 9 50       411 find(\&delete_file, $d_new) || ($status = 0);
1189             }
1190             }
1191             }
1192 9         318 return $status;
1193             }
1194              
1195             #====================================================================
1196             sub undo_file {
1197 3     80 0 260 my $file = $File::Find::name ;
1198 7         92 my ($dest, @rest) = ($file =~ m|$Config{'save_dir'}/[^/]*(.*)| );
1199              
1200 80 50       124 if($file =~ /\.AT_\d+:\d+:\d+$/ ){ #suffix set in save_file2
1201 80         822 return 1; # not restoring non-original saves
1202             }
1203 80 100       288 if(! -f $file ){return 1}
  0         0  
1204 80         2601 copy($file, $dest); #TODO# too silent on errors, however justified
1205 57         6279 return 1;
1206             }
1207              
1208             #====================================================================
1209             sub delete_file {
1210 1     19 0 5 my $archive_filename = $File::Find::name ;
  23         271  
1211 23         11872 my ($real_filename, @rest) =
1212             ($archive_filename =~ m|$Config{'save_dir'}/[^/]*(.*)| );
1213 1         318  
  1         16  
1214 1 100       730 if(! -f $archive_filename ){return 1}
  19         35  
  19         182  
1215 19         670 my $rc = unlink($real_filename);
1216             #silent, as may fail unlink as may already have been deleted
1217             #when undid a transaction, so cant unlink again when do undo all
1218             #unless($rc){carp "delete_file: unlink: $!";}
1219 15         1312 return 1;
1220             }
1221              
1222             #====================================================================
1223             # check_pkg
1224              
1225             =begin testing
1226              
1227 1         6 test_reset();
1228 1         8 system('rpm -e perl-NCustomDummy > /dev/null 2>&1');
1229              
1230 1         9 my $junk = "JUNK gg";
1231 1 50       12121 ok(! &NCustom::check_pkg($junk))
1232             || diag("TEST: checks input format");
1233             # supress expected error message:
1234 1         30 $_STDERR_ =~ s/check_pkg: invalid arguments. at lib\/NCustom.pm line \d+\n// ;
1235             #
1236 1         49 my $req = { match => "", version => "0.0.0", pkg => "", result => ""};
1237 1         582 my $p = "perl-NCustomDummy";
1238             #
1239 1         23 $req = { match => "MINIMUM", version => "0.0.0", pkg => $p, result=>"" };
1240 1 50       3 &NCustom::check_pkg($req); is($$req{'result'}, "MISSING")
  1         5  
1241             || diag("TEST: checks if package meets minimum version 1/4");
1242 1         10 $req = { match => "MAXIMUM", version => "0.0.0", pkg => $p, result=>"" };
1243 1 50       27 &NCustom::check_pkg($req); is($$req{'result'}, "MISSING")
  1         630  
1244             || diag("TEST: checks if package meets maximum version 1/4");
1245 1         13 $req = { match => "EXACTLY", version => "0.0.0", pkg => $p, result=>"" };
1246 1 50       40 &NCustom::check_pkg($req); is($$req{'result'}, "MISSING")
  1         525  
1247             || diag("TEST: checks if package meets exact version 1/4");
1248 1         23 $req = { match => "NOTWANT", version => "0.0.0", pkg => $p, result=>"" };
1249 1 50       39 &NCustom::check_pkg($req); is($$req{'result'}, "OK")
  1         526  
1250             || diag("TEST: checks if package present 1/4");
1251             #
1252 1         23 system("rpm -i $input/perl-NCustomDummy-1.23-1.noarch.rpm");
1253             #shouldnt assume this works
1254             #
1255 1         28 $req = { match => "MINIMUM", version => "1.09.1", pkg => $p, result=>"" };
1256 1 50       5956 &NCustom::check_pkg($req); is($$req{'result'}, "OK")
  1         89  
1257             || diag("TEST: checks if package meets minimum version 2/4");
1258 1         55 $req = { match => "MINIMUM", version => "1.23", pkg => $p, result=>"" };
1259 1 50       65 &NCustom::check_pkg($req); is($$req{'result'}, "OK")
  1         1301  
1260             || diag("TEST: checks if package meets minimum version 3/4");
1261 1         19 $req = { match => "MINIMUM", version => "1.99.9", pkg => $p, result=>"" };
1262 1 50       51 &NCustom::check_pkg($req); is($$req{'result'}, "BELOW")
  1         1762  
1263             || diag("TEST: checks if package meets minimum version 4/4");
1264             #
1265 1         12 $req = { match => "MAXIMUM", version => "1.09.1", pkg => $p, result=>"" };
1266 1 50       50 &NCustom::check_pkg($req); is($$req{'result'}, "ABOVE")
  1         1772  
1267             || diag("TEST: checks if package meets maximum version 2/4");
1268 1         16 $req = { match => "MAXIMUM", version => "1.23", pkg => $p, result=>"" };
1269 1 50       45 &NCustom::check_pkg($req); is($$req{'result'}, "OK")
  1         1848  
1270             || diag("TEST: checks if package meets maximum version 3/4");
1271 1         14 $req = { match => "MAXIMUM", version => "1.99.9", pkg => $p, result=>"" };
1272 1 50       51 &NCustom::check_pkg($req); is($$req{'result'}, "OK")
  1         1735  
1273             || diag("TEST: checks if package meets maximum version 4/4");
1274             #
1275 1         21 $req = { match => "EXACTLY", version => "1.09.1", pkg => $p, result=>"" };
1276 1 50       35 &NCustom::check_pkg($req); is($$req{'result'}, "ABOVE")
  1         1693  
1277             || diag("TEST: checks if package meets exact version 2/4");
1278 1         18 $req = { match => "EXACTLY", version => "1.23", pkg => $p, result=>"" };
1279 1 50       34 &NCustom::check_pkg($req); is($$req{'result'}, "OK")
  1         1622  
1280             || diag("TEST: checks if package meets exact version 3/4");
1281 1         20 $req = { match => "EXACTLY", version => "1.99.9", pkg => $p, result=>"" };
1282 1 50       39 &NCustom::check_pkg($req); is($$req{'result'}, "BELOW")
  1         1624  
1283             || diag("TEST: checks if package meets exact version 4/4");
1284             #
1285 1         15 $req = { match => "NOTWANT", version => "1.09.1", pkg => $p, result=>"" };
1286 1 50       43 &NCustom::check_pkg($req); is($$req{'result'}, "UNWELCOME")
  1         1760  
1287             || diag("TEST: checks if package present 2/4");
1288 1         15 $req = { match => "NOTWANT", version => "1.23", pkg => $p, result=>"" };
1289 1 50       35 &NCustom::check_pkg($req); is($$req{'result'}, "UNWELCOME")
  1         1589  
1290             || diag("TEST: checks if package present 3/4");
1291 1         20 $req = { match => "NOTWANT", version => "1.99.9", pkg => $p, result=>"" };
1292 1 50       39 &NCustom::check_pkg($req); is($$req{'result'}, "UNWELCOME")
  1         1456  
1293             || diag("TEST: checks if package present 4/4");
1294             #
1295 1         15 output();
1296              
1297             =end testing
1298 1         33  
1299 1         1622 =cut
1300              
1301             #====================================================================
1302             sub vcmp ;
1303 1         167 sub check_pkg {
  1         26  
1304 1     21 0 9 my ($req, @rest) = @_ ;
  4         348  
1305             #$req = { match => "", version => "", pkg => "", result => ""};
1306              
1307 4 100       90 if(! defined $$req{'match'}){
1308 21         81 carp "check_pkg: invalid arguments.";
1309 21         235 return 0 ;
1310             }
1311 1 50       449 if($$req{'match'} !~ /(MINIMUM)|(MAXIMUM)|(EXACTLY)|(NOTWANT)/){
1312 1         29 carp "check_pkg: invalid argument values.";
1313 20         295 return 0 ;
1314             }
1315              
1316 0         0 my $rc = system("rpm -q $$req{'pkg'}");
1317              
1318 0 50 33     0 if(($rc == 0)and($$req{'match'} =~ /NOTWANT/)){
1319 20         106652 $$req{'result'} = "UNWELCOME";
1320 20         422 return 1;
1321             }
1322 0 100 66     0 if(($rc != 0)and($$req{'match'} =~ /NOTWANT/)){
1323 0         0 $$req{'result'} = "OK";
1324 20         896 return 1;
1325             }
1326 6 50 33     78 if(($rc != 0)and($$req{'match'} !~ /NOTWANT/)){
1327 6         144 $$req{'result'} = "MISSING";
1328 14         349 return 1;
1329             }
1330              
1331 14         135 my $ver = `rpm -q $$req{'pkg'} --qf \%{VERSION}` . "";
1332 14         372 my $reqver = "$$req{'version'}" . "";
1333              
1334             # vstring will be deprecated, by then we'll find a module for it
1335 0 0 0     0 if(($$req{'match'} =~ /EXACTLY/) and(vcmp($ver, "eq", $reqver))){
1336 0         0 $$req{'result'} = "OK";
1337 0         0 return 1;
1338             }
1339 0 0 0     0 if(($$req{'match'} =~ /MINIMUM|EXACTLY/) and(vcmp($ver, "lt", $reqver))){
1340 0         0 $$req{'result'} = "BELOW";
1341 0         0 return 1;
1342             }
1343 0 0 0     0 if(($$req{'match'} =~ /MAXIMUM|EXACTLY/) and(vcmp($ver, "gt", $reqver))){
1344 0         0 $$req{'result'} = "ABOVE";
1345 0         0 return 1;
1346             }
1347             # fall-through is brave ? check this logic
1348 0         0 $$req{'result'} = "OK";
1349 0         0 return 1;
1350             }
1351              
1352             #====================================================================
1353             # required_packages
1354              
1355             =item C
1356              
1357             =begin example
1358              
1359 1         3 test_reset();
1360 1 50       2 can_ok("NCustom", qw(required_packages))
1361             || diag("TEST: is a public function of NCustom");
1362             #that was test 93
1363              
1364             =end example
1365              
1366             =for example begin
1367              
1368             sub handler{
1369 0     0   0 my ($reqref, $url, $file) = @_;
  0     1   0  
1370 0         0 print "As $$reqref{'match'} version $$reqref{'version'} of ";
  0         0  
1371 0         0 print "$$reqref{'pkg'} was $$reqref{'result'} - ";
  1         6  
1372 0         0 print "we are going to fetch $file from $url and execute it.\n";
  1         38  
1373 0         0 print "This should set things right.\n";
  1         31  
1374 0         0 return 1;
  1         16  
1375             }
1376              
1377 1         9 required_packages(<<' EOF');
1378             EXACTLY; 9.9.9; acme; handler($req, "URL", "FILE")
1379             NOTWANT; 0.0.0; perl; print "Dont be stupid\n"
1380             #MAXIMUM; 9.9.9; perl; carp("Warning: untested with this perl")
1381             #MINIMUM; 9.9.9; perl; apt_fix()
1382             NOTWANT; 0.0.0; perl; for($i = 0; $i < 10; $i++){$s="Hello"; print "${s}${i}\n"}
1383             EOF
1384              
1385             =for example end
1386              
1387             Required packages take a multi-line argument list, where each line is of the format: requirement, version, package, handler code.
1388              
1389             Required packages will invoke the handler if the package is (or isnt) installed as per the requirement and version.
1390              
1391             Valid requirements are: MINUMUM, MAXUMUM, EXACTLY, and NOTWANT.
1392              
1393             Input lines will be ignored if the first non-whitespace character is the '#' character.
1394              
1395             The handler code is eval'd, and it may make use of the hashref "req". The hash has the keys: match, version, and package; which correspond to the original arguments. The hash also contains result, which is the answer as to whether the requirements was met or not. Possible values of result (each referring to the package or it's version in relation to the requuirements) are: MISSING, ABOVE, BELOW, or UNWELCOME.
1396              
1397             A handler "apt_fix" is provided that will simply attempt to remove UNWELCOME packages, and do an install for all other scenarios - so you might get the verion you want or not, depending upon your apt repository.
1398              
1399             =for example_testing
1400 1         77 my $o = $_STDOUT_;
1401 1         1769 my $e = $_STDERR_;
1402 1 50       7 like($o, qr/As EXACTLY version .* fetch FILE from URL and execute it.\n/)
1403             || diag("TEST: calls a handler");
1404 1 50       4 like($o, qr/Dont be stupid/)
1405             || diag("TEST: executes simple statements");
1406 1 50       39 like($o, qr/Hello9/)
1407             || diag("TEST: executes compound statements");
1408             #
1409             # supress expected output
1410 1         729 $_STDOUT_ =~ s/Hello\d+\n//gm ;
1411 1         12125 $_STDOUT_ =~ s/Dont be stupid\n//gm ;
1412 1         1138 $_STDOUT_ =~ s/As EXACTLY version .* fetch FILE from URL and execute it.\n//gm ;
1413 1         7 $_STDOUT_ =~ s/This should set things right.\n//gm ;
1414             #
1415 1         16 output();
1416              
1417 1         11  
1418 1         17 =cut
1419              
1420             #====================================================================
1421 1         2 sub required_packages {
1422 1     1 1 7 my ($requirements, @rest) = @_;
  1         13  
1423 1         30 my $status = 1;
1424 1         4 my $rc = 1;
  1         14  
1425 1         6  
1426 1         2 my @lines = split(/\n/,$requirements);
  1         8  
1427 1         12 foreach my $line (@lines){
1428 1         23 $line =~ s/^\s+//; #trim leading whitespace
1429 1 100       14 next if $line =~ /^#/; #TODO# comments need much work
1430              
1431 5         117 $req = { match => "", version => "0.0.0", pkg => "", result => ""};
1432 5         35 ($$req{'match'}, $$req{'version'}, $$req{'pkg'}, my @rest)
1433             = parse_line('\s*;\s*',1, $line);
1434 3         76 my $code = join(';',@rest);
1435              
1436 3         91 $rc = check_pkg($req);
1437 3 50       2983 unless($rc){$status = 0; next}
  3         72  
  3         47  
1438              
1439 0 100       0 if($$req{'result'} ne "OK"){
1440             #we invoke handler from caller's perspective
1441             package main ;
1442 2     2   3 no strict; no warnings;
  2     2   3980  
  2         22  
  2         4  
  2         69  
  2         13  
1443 0         0 eval $code ;
1444 3 50       76 if($@){
1445 1         365 carp("required_packages: code \n$code \nraised the error $@");
1446 1         15 $NCustom::status = 0;
1447             }
1448 2     2   5 use strict; use warnings;
  2     2   158  
  2         11  
  2         4  
  2         67  
  2         10  
1449             #
1450             #back to normal
1451             package NCustom ;
1452 1         3 $rc = check_pkg($req);
  0         0  
1453 0 50 33     0 unless(($$req{'result'} eq "OK") && ($rc)){ $status = 0; }
  1         11  
1454             }
1455 1         153 }
  1         21  
1456 1         255 return $status;
  1         43  
1457             }
1458              
1459             #====================================================================
1460             # apt_fix
1461              
1462             =begin testing
1463              
1464 1         6 test_reset();
1465 1 50 33     11 SKIP: {
1466 1         8 skip "apt too intrusive", 6 unless (defined $ENV{'TEST_APT'} || defined $ENV{'TEST_ALL'});
1467             #############
1468 1         37 system('rpm -e perl-NCustomDummy > /dev/null 2>&1');
1469 1         97 my ($version, $rc);
1470 0         0 $version = `rpm -q perl-NCustomDummy --qf \%{VERSION}`;
1471 0 0       0 like($version, qr/package perl-NCustomDummy is not installed/)
1472             || diag("TEST: must remove perl-NCustomDummy package");
1473             #
1474             #
1475 0 0       0 can_ok("NCustom", qw(apt_fix))
1476             || diag("TEST: is a public function of NCustom");
1477             #
1478 0         0 $rc = required_packages(<<' EOF');
1479             EXACTLY; 9.9.9; perl-NCustomDummy; apt_fix()
1480             EOF
1481 0 0       0 ok(! $rc)
1482             || diag("TEST: must return 0 if requirements arent met");
1483 0         0 $version = `rpm -q perl-NCustomDummy --qf \%{VERSION}`;
1484 0 0       0 like($version, qr/1.23/)
1485             || diag("TEST: must will install its version rather than nothing");
1486             #
1487 0         0 $rc = required_packages(<<' EOF');
1488             NOTWANT; 9.9.9; perl-NCustomDummy; apt_fix()
1489             EOF
1490 0 0       0 is($rc, 1)
1491             || diag("TEST: must return 1 if requirements are met");
1492 0         0 $version = `rpm -q perl-NCustomDummy --qf \%{VERSION}`;
1493 0 0       0 like($version, qr/package perl-NCustomDummy is not installed/)
1494             || diag("TEST: will remove unwanted packages");
1495             ############
1496             }
1497 0         0 output();
1498              
1499             =end testing
1500 0         0  
1501 1         3971 =cut
1502              
1503             #====================================================================
1504             sub apt_fix {
1505 1 0   0 0 337 if($$req{'result'} =~ /UNWELCOME/){
  1         10  
  1         22  
1506 1         6 system("apt-get -q -y remove $$req{'pkg'}");
  1         34  
1507             }else{
1508 0         0 system("apt-get -q -y install $$req{'pkg'}");
1509             }
1510 0         0 return 1; #hmm
1511             }
1512              
1513             #====================================================================
1514             # vcmp
1515              
1516             =begin testing
1517              
1518             #FALSE
1519 1 50       2 ok(! &NCustom::vcmp(1,"gt",2) )
1520             || diag("TEST: compares version strings");
1521 1 50       5 ok(! &NCustom::vcmp(1,"eq",2) )
1522             || diag("TEST: compares version strings");
1523 1 50       23 ok(! &NCustom::vcmp(3,"gt",3) )
1524             || diag("TEST: compares version strings");
1525 1 50       1421 ok(! &NCustom::vcmp(3,"lt",3) )
1526             || diag("TEST: compares version strings");
1527 1 50       684 ok(! &NCustom::vcmp(3,"ne",3) )
1528             || diag("TEST: compares version strings");
1529             #
1530             #TRUE";
1531 1 50       748 ok(&NCustom::vcmp(1,"lt",2) )
1532             || diag("TEST: compares version strings");
1533 1 50       848 ok(&NCustom::vcmp(1,"ne",2) )
1534             || diag("TEST: compares version strings");
1535 1 50       542 ok(&NCustom::vcmp(3,"eq",3) )
1536             || diag("TEST: compares version strings");
1537 1 50       818 ok(&NCustom::vcmp("1.2.3","lt",2) )
1538             || diag("TEST: compares version strings");
1539 1 50       749 ok(&NCustom::vcmp("1.2.3","gt","1.1.99") )
1540             || diag("TEST: compares version strings");
1541 1 50       673 ok(&NCustom::vcmp("1.2.3","eq","1.2.3") )
1542             || diag("TEST: compares version strings");
1543 1 50       767 ok(&NCustom::vcmp(1,"ne",0) )
1544             || diag("TEST: compares version strings");
1545 1 50       566 ok(&NCustom::vcmp("1.2.3","lt",2) )
1546             || diag("TEST: compares version strings");
1547 1 50       725 ok(&NCustom::vcmp("1.2.3","gt","1.1.99") )
1548             || diag("TEST: compares version strings");
1549 1 50       606 ok(&NCustom::vcmp("1.2.3","eq","1.2.3") )
1550             || diag("TEST: compares version strings");
1551 1 50       911 ok(&NCustom::vcmp(1,"ne",0) )
1552             || diag("TEST: compares version strings");
1553 1 50       1002 ok(&NCustom::vcmp("1.2.3","lt","1.03") )
1554             || diag("TEST: compares version strings");
1555 1         637 output();
1556              
1557             =end testing
1558 1         670  
1559 1         1669 =cut
1560              
1561              
1562 1         2 #====================================================================
1563 1         3 sub vcmp {
1564 1     30 0 3 my ($vstring1, $cmp, $vstring2) = @_;
  0         0  
1565 0         0 my ($v1, $v2, $dummy, @rest);
1566              
1567             # check input ######################################
1568 30 50       310 if( $cmp !~ /(lt)|(gt)|(eq)|(ne)/ ){
1569 30         40 carp "vcmp: invalid comparision operator: $cmp.\n";
1570 30         124 return 0; #arbitrary
1571             }
1572 0 50       0 if( $vstring1 !~ /^(\d+\.)*\d*$/ ){
1573 0         0 carp "vcmp: invalid version string: $vstring1.\n";
1574 30         145 return 0; #arbitrary
1575             }
1576 0 50       0 if( $vstring2 !~ /^(\d+\.)*\d*$/ ){
1577 0         0 carp "vcmp: invalid version string: $vstring2.\n";
1578 30         112 return 0; #arbitrary
1579             }
1580              
1581             # reduce ###########################################
1582 0         0 $vstring1 =~ s/^\.*([^\.]+)\.*// ;
1583 0         0 $v1 = $1;
1584             #if($v1 eq ""){$v1 = 0}
1585             #if($vstring1 eq ""){$vstring1 = 0}
1586             #print "\n\t\tv1: $v1 vstring1: $vstring1 ";
1587             #
1588 30         115 $vstring2 =~ s/^\.*([^\.]+)\.*// ;
1589 30         65 $v2 = $1;
1590             #if($v2 eq ""){$v2 = 0}
1591             #if($vstring2 eq ""){$vstring2 = 0}
1592             #print "\n\t\tv2: $v2 vstring2: $vstring2 ";
1593              
1594              
1595             # result/recuse ###################################
1596 30 100       180 if( $cmp eq "eq"){
1597 30 100 66     153 if((! defined $v1) and (! defined $v2)){
    50 33        
    100          
    50          
1598 1         2 return 1;
  30         78  
1599             }elsif((! defined $v1) or (! defined $v2)){
1600 11         90 return 0;
1601 1         308 }elsif( $v1 != $v2 ){
  1         21  
1602 1         521 return 0;
  3         20  
1603             }elsif( $v1 != $v2 ){
1604             }else{
1605 0         0 return vcmp($vstring1, $cmp, $vstring2) ;
1606             }
1607             }
1608 1 100       7 if( $cmp eq "ne"){
1609 7 100 66     26 if((! defined $v1) and (! defined $v2)){
    50 33        
    100 33        
    50          
1610 19         43 return 0;
1611             }elsif((! defined $v1) or (! defined $v2)){
1612 5         55 return 1;
1613             }elsif( $v1 != $v2 ){
1614 1         5 return 1;
1615             }elsif($v1 eq "" && $v2 eq ""){
1616 0         0 return 0;
1617             }else{
1618 3         16 return vcmp($vstring1, $cmp, $vstring2) ;
1619             }
1620             }
1621 0 100       0 if( $cmp eq "lt"){
1622 1 100 66     3 if((! defined $v1) and (! defined $v2)){
    50 33        
    50 33        
    100 33        
    50          
    50          
1623 14         38 return 0;
1624             }elsif((! defined $v1) and ( defined $v2)){
1625 7         87 return 1;
1626             }elsif(( defined $v1) and (!defined $v2)){
1627 1         6 return 0;
1628             }elsif( $v1 < $v2 ){
1629 0         0 return 1;
1630             }elsif($v1 > $v2 ){
1631 0         0 return 0;
1632             }elsif($v1 eq "" && $v2 eq ""){
1633 4         20 return 0;
1634             }else{
1635 0         0 return vcmp($vstring1, $cmp, $vstring2) ;
1636             }
1637             }
1638 0 50       0 if( $cmp eq "gt"){
1639 2 100 66     10 if((! defined $v1) and (! defined $v2)){
    50 33        
    50 33        
    100 33        
    100          
    50          
1640 7         50 return 0;
1641             }elsif((! defined $v1) and ( defined $v2)){
1642 7         99 return 0;
1643             }elsif(( defined $v1) and (!defined $v2)){
1644 1         6 return 1;
1645             }elsif( $v1 > $v2 ){
1646 0         0 return 1;
1647             }elsif($v1 < $v2 ){
1648 0         0 return 0;
1649             }elsif($v1 eq "" && $v2 eq ""){
1650             #}elsif($v1 == 0 && $v2 == 0){
1651 2         12 return 0;
1652             }else{
1653 1         15 return vcmp($vstring1, $cmp, $vstring2) ;
1654             }
1655             }
1656             }
1657              
1658             #====================================================================
1659             # blat_myconfig
1660              
1661             =item C
1662              
1663             =begin example
1664              
1665 1         5 test_reset();
1666 1 50       2 can_ok("NCustom", qw(blat_myconfig))
1667             || diag("TEST: is a public function of NCustom");
1668              
1669             =end example
1670              
1671             =for example begin
1672              
1673 1         5 blat_myconfig();
1674              
1675              
1676             =for example end
1677              
1678             Blat_myconfig overwrites the personal configuration profile with the global configuration profile. The personal configuration profile is "~/.ncustom/NCustom/MyConfig.pm".
1679              
1680             =for example_testing
1681 1 50       101 is(compare("$output/.ncustom/NCustom/MyConfig.pm", "$input/Global.pm"), 0)
1682             || diag(<<' EOF');
1683             TEST:
1684             TEST: - MyConfig.pm replaced by Config.pm
1685             TEST: - This test will fail if you change Config.pm and
1686             TEST: dont update reference copies used in test comparision.
1687             EOF
1688             #
1689 1         1319 output();
1690              
1691 1         25  
1692 1         757 =cut
1693              
1694             #====================================================================
1695 1         2 sub blat_myconfig {
1696 1     2 1 4 my $rc ;
  0         0  
1697 3         20 my $myconfig_file_dir = "$ENV{'HOME'}/.ncustom/NCustom";
1698 1         1 my $myconfig_file = "$myconfig_file_dir/MyConfig.pm";
  2         11  
1699 1         2 my $global_config_file = "dummy" ;
  2         12  
1700 1         3  
1701             # ensure target directory exists
1702 2 100       14 if( ! -e $myconfig_file_dir){
1703 2         17 $rc = mkpath $myconfig_file_dir;
1704 2 50       104 unless($rc){
1705 1         471 carp "blat_myconfig: couldnt create $myconfig_file_dir: $!";
1706 1         13 return 0;
1707             }
1708             }
1709 0 50       0 if( ! -d $myconfig_file_dir){
1710 0         0 carp "blat_myconfig: not at directory: $myconfig_file_dir";
1711 2         55 return 0;
1712             }
1713              
1714             # find source file
1715 0         0 foreach my $i (@INC){
1716 0 100       0 if(-e "$i/NCustom/Config.pm"){
1717 2         22 $global_config_file = "$i/NCustom/Config.pm";
1718 4         109 last;
1719             }
1720             }
1721 2 50       6 if( $global_config_file =~ /^dummy$/){
1722 2         11 carp "blat_myconfig: cant find global Config.pm file";
1723 2         14 return 0;
1724             }
1725              
1726             # copy file, without pod doco past end
1727 0         0 $rc = open(SRCFILE, "< $global_config_file");
1728 0 50       0 unless($rc){carp "blat_myconfig: open $global_config_file : $!"; return 0; }
  2         100  
  2         33  
1729              
1730 0         0 $rc = open(NEWFILE, "> ${myconfig_file}");
1731 1 50       272 unless($rc){carp "blat_myconfig: open ${myconfig_file}: $!"; return 0; }
  0         0  
  2         188  
  2         12  
1732              
1733 0         0 while(){
1734 1 100       170 /^__END__/ && last;
  1         14  
  0         0  
1735 1         287 print NEWFILE $_ ;
  2         63  
1736             }
1737 126         466 close(SRCFILE);
1738 124         299 close(NEWFILE);
1739 2         36 return 1;
1740             }
1741              
1742             #====================================================================
1743             # config_edit
1744              
1745             =item C
1746              
1747             =begin example
1748              
1749 1         2 test_reset();
1750 1 50       2 can_ok("NCustom", qw(config_edit))
1751             || diag("TEST: is a public function of NCustom");
1752              
1753             =end example
1754              
1755             =for example begin
1756              
1757 1         7 config_edit((src_fqdn => '"install.baneharbinger.com"',
1758             test_url1 => '"install.baneharbinger.com/index.html"'));
1759              
1760              
1761              
1762             =for example end
1763              
1764             Config_edit is followed by name vaule pairs. If there is a corresponding name in the personal configuration file, then its vaule shall be updated. If there is no corresponding name then the name value shall be added to the end of the file. If there is no file it shall be created. The personal configuration file is "~/.ncustom/NCustom/MyConfig.pm".
1765              
1766             If some configuration vlaues are defined in terms of other configuration values, then the order may be important.
1767              
1768              
1769             =for example_testing
1770 1         85 my @lines ;
1771 1         2130 open(MYCFG, "< $output/.ncustom/NCustom/MyConfig.pm");
1772 1         3 @lines = ;
1773 1         115 close(MYCFG);
1774 1 50       25 ok( grep( /src_fqdn.*install.baneharbinger.com/, @lines) > 0 )
1775             || diag("TEST: can edit(add) src_fqdn");
1776 1 50       17 ok( grep( /test_url1.*install.baneharbinger.com/, @lines) > 0 )
1777             || diag("TEST: can edit(add) test_url1");
1778             #
1779 1         44 &NCustom::blat_myconfig(); #TODO# hmmm tests should be independent
1780 1         896 &NCustom::config_edit((test_data1 => "wow", test_data2 => "whoopee doo"));
1781 1         265 open(MYCFG, "< $output/.ncustom/NCustom/MyConfig.pm");
1782 1         6 @lines = ;
1783 1         54 close(MYCFG);
1784 1 50       52 ok( grep( /test_data1.*wow/, @lines) > 0 )
1785             || diag("TEST: can edit(change) test_data1");
1786 1 50       13 ok( grep( /test_data2.*whoopee doo/, @lines) > 0 )
1787             || diag("TEST: can edit(change) test_data2");
1788             #
1789 1         26 output();
1790              
1791 1         383  
1792 1         309 =cut
1793              
1794             #====================================================================
1795 1         5 sub config_edit {
1796 1     2 1 7 my (%config_edit) = @_;
  2         94  
1797              
1798 1         2 my ($rc, $name, $value) ;
  2         9  
1799 1         3 my $myconfig_file_dir = "$ENV{'HOME'}/.ncustom/NCustom";
  2         29  
1800 1         2 my $myconfig_file = "$myconfig_file_dir/MyConfig.pm";
  2         11  
1801 2         18 my $global_config_file = "dummy" ;
1802              
1803             # ensure target directory exists
1804 2 100       17 if( ! -e $myconfig_file_dir){
1805 2         15 $rc = mkpath $myconfig_file_dir;
1806 2 50       1191 unless($rc){ carp "config_edit: mkpath $myconfig_file_dir: $!"; return 0; }
  1         375  
  1         14  
1807             }
1808 0 50       0 if( ! -d $myconfig_file_dir){
1809 0         0 carp "config_edit: not at directory: $myconfig_file_dir"; return 0;
  2         56  
1810             }
1811              
1812             # create blank personal config file if there isnt one
1813 0 100       0 if( ! -e $myconfig_file){
1814 0         0 $rc = open(NEWFILE, ">$myconfig_file");
1815 2 50       52 unless($rc){carp "config_edit: open $myconfig_file: $!"; return 0; }
  1         102  
  1         13  
1816 0         0 my $content = <<' EOF' ;
1817             package NCustom ;
1818             no warnings;
1819             1;
1820             EOF
1821 0         0 print NEWFILE $content ;
1822 1         6 close(NEWFILE);
1823             }
1824              
1825             # open files for editting
1826 1         417 $rc = open(OLDFILE, "< ${myconfig_file}");
1827 1 50       48 unless($rc){carp "config_edit: open ${myconfig_file}: $!"; return 0; }
  2         92  
  2         14  
1828              
1829 0         0 $rc = open(NEWFILE, "> ${myconfig_file}.new");
1830 0 50       0 unless($rc){carp "config_edit: open ${myconfig_file}.new: $!"; return 0; }
  2         172  
  2         14  
1831              
1832             # do options that are already in the file
1833 0         0 while(){
1834 0         0 my $line = $_ ;
1835 2         33 my $line_replaced = 0;
1836 1 100       2 next if($line =~ /^\s*1;/) ; # we will add it back on later
  65         84  
1837 65         224 while(($name, $value) = each(%config_edit)) {
1838 65 100       163 if($line =~ /^\s*\$Config{.$name.}/ ){ #TODO#nasty pattern assumptions!!!
1839 1         255 #print NEWFILE "\$Config{\'$name\'} = \"$value\" ; \n";
  1         22  
1840 1         449 print NEWFILE "\$Config{\'$name\'} = $value ; \n";
  63         992  
1841 37         2739 $line_replaced = 1;
1842 2         13 delete($config_edit{$name});
1843             }
1844             }
1845 2 100       4 print NEWFILE $line unless $line_replaced ;
1846             }
1847 2         12 close(OLDFILE);
1848              
1849             # do options that were not in the file
1850 63         927 while(($name, $value) = each(%config_edit)) {
1851             #print NEWFILE "\$Config{\'$name\'} = \"$value\" ;\n";
1852 2         22 print NEWFILE "\$Config{\'$name\'} = $value ;\n";
1853             }
1854 2         9 print NEWFILE "1;\n"; # we said we would add it back later
1855 2         9 close(NEWFILE);
1856              
1857             # all done
1858 2         4 $rc = move("${myconfig_file}.new", "${myconfig_file}");
1859 2 50       91 unless($rc){carp "config_edit: move $!"; return 0; }
  2         42  
  2         13963  
1860              
1861 0         0 return 1;
1862             }
1863              
1864             #====================================================================
1865             # ncustom
1866              
1867             =item C
1868              
1869             =begin example
1870              
1871 1         4 test_reset();
1872 1 50       2 can_ok("NCustom", qw(ncustom))
1873             || diag("TEST: is a public function of NCustom");
1874 1         6 copy("$input/test1.ncus", "$output");
1875 1         84 chmod(0750,"$output/test1.ncus");
1876 1         1008 copy("$input/test2.ncus", "$output");
1877 1         999 chmod(0750,"$output/test2.ncus");
1878              
1879             =end example
1880              
1881             =for example begin
1882              
1883 1         19 ncustom(<<' EOF');
1884             ~/test1.ncus
1885             test2.ncus
1886             EOF
1887              
1888             =for example end
1889              
1890             Ncustom is passed one or more filenames, either local filenames or URLs.
1891             The filenames are assumed to be NCustom scripts, are fetched, and executed.
1892             If the filename is not an NCustom script, then transactions will not be journalled, and will not be able to be undone.
1893             An unqualified NCustom script name will be searched for in pwd and the location(s) specified in NCustom::Config.
1894             URLs will be fetched using the get_url subrouting in NCustom::Config.
1895              
1896             =for example_testing
1897 1         510 open(STUBSLOG, "< $output/stubs.log");
1898 1         22 my @lines = ;
1899 1         102 close(STUBSLOG);
1900 1 50       54 ok( grep( /NCustom test1.ncus/, @lines) > 0 )
1901             || diag("TEST: fetches and executes file 1/2");
1902 1 50       18 ok( grep( /NCustom test2.ncus/, @lines) > 0 )
1903             || diag("TEST: fetches and executes file 2/2");
1904             #
1905 1         53 output();
1906              
1907 1         768 =cut
1908 1         312  
1909             #====================================================================
1910             sub ncustom {
1911 1     1 1 5 my ($file_list, @rest) = @_ ;
  0         0  
1912 1         4 my $status = 1;
  2         18  
1913              
1914             #tag#
1915 1         6 my @lines = split(/\n/,$file_list);
1916 1         5 foreach my $line (@lines){
1917 1         10 my $executed = 0; #we must invoked something for a line
1918 1         7 chomp $line ;
1919 2         7 $line =~ s/^\s+//; #trim leading whitespace
1920 2 50       8 next if $line =~ /^#/; #TODO# comments need much work
1921 2 100       64 if(ncustom_try_dir($line, "")){$executed = 1}
  2         20  
1922 2 100       19 if($executed){next}
  1         15  
1923 2 50       26 if(ncustom_try_url($line, "")){$executed = 1}
  1         8  
1924 1 50       17 if($executed){next}
  0         0  
1925 1         12 my $src_arraryref = $NCustom::Config{'default_src'};
1926 0         0 foreach my $src (@$src_arraryref){
1927 1         7 my $dir = (glob($src))[0];
1928 1 50       7 if(-d $dir){
1929 1 50       40 if(ncustom_try_dir($line, $dir)){$executed = 1}
  1         27  
1930 1 50       7 if($executed){last};
  1         18  
1931             }else{
1932 1 0       15 if(ncustom_try_url($line, $src)){$executed = 1}
  1         13  
1933 0 0       0 if($executed){last};
  0         0  
1934             }
1935             }
1936 0 50       0 if($executed){next}
  0         0  
1937 1         11 carp "ncustom: cant find/execute \"$line\".\n";
1938 1         20 $status = 0;
1939             }
1940 0         0 return $status;
1941             }
1942              
1943             #====================================================================
1944             sub ncustom_try_dir {
1945 0     3 0 0 my ($line, $prefix, @rest) = @_ ;
1946 1         22 my (@candidates, $candidate);
1947 3         24 my $executed = 0 ;
1948 3         13 my $rc ;
1949              
1950 3         7 @candidates = glob($line);
1951 3         7 foreach $candidate (@candidates){
1952 3         175 my $file = (glob("${prefix}${candidate}"))[0];
1953 3 100 66     12 if(-f $file && -x $file){
1954 3 50       85 if( basename($file) eq $file){
1955 3         126 $file = "./$file";
1956             # so system call ok in this scenario (regardless of pwd being in path)
1957             }
1958 2         204 $rc = system($file);
1959 0 50       0 carp "ncustom_try_dir: system call=$file : error=$?\n" unless $rc == 0;
1960 2         32925 $executed = 1;
1961             #for better or worse...
1962             }
1963             }
1964 2         60 return $executed;
1965             }
1966              
1967             #====================================================================
1968             sub ncustom_try_url {
1969 2     1 0 58 my ($line, $prefix, @rest) = @_ ;
1970 3         84 my (@candidates, $candidate);
1971 1         10 my $executed = 0 ;
1972 1         7 my $rc ;
1973              
1974 1         3 my $stagedir = tempdir( DIR => $Config{'tmp_dir'});
1975             #TODO# add CLEANUP => 1
1976 1         8 my $subref = $NCustom::Config{'get_url'};
1977 1         35 &$subref("${prefix}${line}",$stagedir);
1978 1         738 opendir(DIR, $stagedir);
1979 1 50       164 @candidates = grep { -f $_ && -x $_ } # executable files only
  1         29  
1980 1         17 map { "$stagedir/$_" } # form: "path/filename"
1981             readdir(DIR); # all files
1982 2         61 foreach $candidate (@candidates){
1983 2         14 $rc = system($candidate);
1984 1 0       7 carp "ncustom_try_url: system call=${candidate}: error=$?\n" unless $rc ==0;
1985 0         0 $executed = 1;
1986             #for better or worse...
1987             }
1988 0         0 return $executed;
1989             }
1990             #====================================================================
1991              
1992             # ///////////////////////////////////////////////////////////////////
1993             #<< CC: Constructor <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1994             # \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
1995             constructor();
1996              
1997             sub constructor {
1998 0     18 0 0 load_config();
1999 1         7 apply_config();
2000             }
2001              
2002             # ///////////////////////////////////////////////////////////////////
2003             #<< DD: Destructor <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
2004             # \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
2005             # Dont clean up on exit of tests,
2006             # or we would have nothing to diagnose upon failure of tests,
2007             # instead we prevent polution by doing a cleanup prior to each test,
2008             # this is also better as destructor isnt 100% reliable, depending upon death.
2009              
2010             1;
2011             __END__