Subversion Repositories DevTools

Rev

Rev 273 | Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
271 dpurdie 1
########################################################################
2
# Copyright (C) 2008 ERG Limited, All rights reserved
3
#
4
# Module name   : JatsCopy
5
# Module type   : Makefile system
6
# Compiler(s)   : Perl
7
# Environment(s): jats
8
#
9
#                 Utility functions to
10
#                   CopyDir                 - Copy Dir Tree
11
#                   CopyFile                - Copy one file
12
#                   CreateDir               - Create one directory
13
#                   DeleteDir               - Delete Dir Tree
14
#                   DeleteFile              - Delete a file
15
#                   SetCopyDirDefaults      - Set application wide defaults
16
#
17
#                 Performs these operations within a common logging
18
#                 and error reporting framework
19
#
20
#                 Intended to replace all similar lumps of code
21
#                 within JATS
22
#
23
#                 Intended to make life simple
24
#
25
#                 It has  alot of callbacks and config, but the body
26
#                 of the work is similar
27
#
28
# Examples:
29
#               CopyDir ( 'aaa', 'bbb' );
30
#               Simply copy the aaa dir-tree to 'bbb'
31
#
32
#               CopyDir ( 'aaa', 'bbb',
33
#                           { 'DeleteFirst' => 1,
34
#                             'Error' => \&MyError,
35
#                             'Logger' => \&MyLog,
36
#                             'Examine' => \&MyExamine,
37
#                             'Expert' => \&MyExpert,
38
#                             'Exists' => \&MyExists,
39
#                             'Ignore' => ['a2', 'build.pl'],
40
#                             'IgnoreRE' => ['2$'],
41
#                             'IgnoreDirs' => ['.svn'],
42
#                             });
43
#
44
#              Complex copy of the 'aaa' tree to 'bbb'
45
#               Do my own error processing
46
#               Do my own loggind
47
#               Examine each file/dir being processed
48
#               Do my own copy ( Expert)
49
#               Notify me if an existing file is present
50
#               Do not copy files called a2 and build.pl
51
#               Do not copy the .svn subdir
52
#               Do not copy files ending in 2
53
#
54
#......................................................................#
55
 
56
use strict;
57
use warnings;
58
 
59
package JatsCopy;
60
 
61
use JatsError;
62
use FileUtils;
63
use File::Path;
64
 
65
 
66
# automatically export what we need into namespace of caller.
67
use Exporter();
68
our (@ISA, @EXPORT);
69
@ISA         = qw(Exporter);
70
@EXPORT      = qw(
71
                    CopyDir
72
                    CopyFile
73
                    CreateDir
74
                    DeleteDir
75
                    DeleteFile
76
                    SetCopyDirDefaults
77
                );
78
 
79
#
80
#   Global Data
81
#
82
my $Global_opts;
83
 
84
#-------------------------------------------------------------------------------
85
# Function        : CopyDir
86
#
87
# Description     : Utility function to copy a directory of files
88
#                   This function is NOT reentrant
89
#                   Do not use it within callback frunctions
90
#
91
# Inputs          : $src_dir                - Src directory
92
#                   $dst_dir                - Dest dir
93
#                   $opt                    - An Array or Hash Ref of options
94
#                    Flags that affect operation
95
#                       DeleteFirst     - True: Delete target directory first
96
#                       NoSubDirs       - True: Only source directory files
97
#                       Flatten         - True: Flatten output dir
98
#                       Log             - True: Log activity
99
#                       EmptyDirs       - True: Create empty dirs
100
#                       IgnoreDots      - True: Ignore files and dirs starting with .
101
#                       NoOverwrite     - True: Do not replace file in target
102
#                       DuplicateLinks  - True: Duplicate, don't copy links
103
#                       SymlinkFiles    - True: Create symlinks if possible
104
#                       ReadOnlyFiles   - True: Make files Read Only
105
#                     User callback functions
106
#                       Expert          - Function to do the work
107
#                       Examine         - Function to examine each entry
108
#                                         If True, then proceed
109
#                       Exists          - Function called if target file exists
110
#                                         If True, then delete and replace
111
#                       Logger          - Function to Log operations
112
#                       Error           - Function to Process errors
113
#                     Mataching operations
114
#                       Ignore          - An array of files to ignore
115
#                       IgnoreRE        - An array of files to ignore (RE)
116
#                       IgnoreDirs      - An array of subdirs to ignore
117
#                       IgnoreDirsRE    - An array of subdirs to ignore (RE)
118
#                       Match           - An array of files to match
119
#                       MatchRE         - An array of file to match (RE)
120
#                     Misc
121
#                       Stats           - A ref to a hash of stats
122
#                       UserData        - Item passed through to call backs
123
#                                         for the users own purposes.
124
#
125
#                   File and dir match/ignore operations come in two flavours
126
#                   Simple : Use of simple wildcards: ?,* and [...] constructs
127
#                   RE     : Full regular expression
128
#
129
#                   Dir match/ignore work on a single dirname, not dirs and subdirs.
130
#
131
#                   Matches rules are applied before ignore rules.
132
#
133
#                   User functions are called a ref to a copy of the options hash
134
#                   with the folling data added.
135
#                       item            - Source Path
136
#                       file            - Filename
137
#                       target          - Dest Path
138
#                   In the 'Examine' callback, 'target' may be modified
139
#                   This will be used as the path to target file.
140
#
141
# Returns         :
142
#
143
sub CopyDir
144
{
145
    my $src_dir = shift;
146
    my $dst_dir = shift;
147
 
148
    #
149
    #   Setup default options
150
    #   Merge user options with default options to create a local set
151
    #
152
    my $opt = JatsCopyInternal::DefaultOpts( 'CopyDir', @_);
153
 
154
    #
155
    #   Remove any training / from the users dirs
156
    #
157
    $src_dir =~ s~/+$~~;
158
    $dst_dir =~ s~/+$~~;
159
 
160
    #
161
    #   Insert some default options
162
    #
163
    $opt->{'SrcDir'} = $src_dir;
164
    $opt->{'DstDir'} = $dst_dir;
165
 
166
    #
167
    #   Convert Match requests into MatchRE requests
168
    #
169
    if ($opt->{'OptMatch'} )
170
    {
171
        JatsCopyInternal::Pat2GlobList ($opt ,'Match'     , 'MatchRE' );
172
        JatsCopyInternal::Pat2GlobList ($opt ,'MatchDirs' , 'MatchDirsRE' );
173
        JatsCopyInternal::Pat2GlobList ($opt ,'Ignore'    , 'IgnoreRE' );
174
        JatsCopyInternal::Pat2GlobList ($opt ,'IgnoreDirs', 'IgnoreDirsRE' );
175
    }
176
 
177
    #
178
    #   Validate source dir
179
    #
180
    Error ("CopyDir: Source dir not found: $src_dir" )
181
        if ( ! -d $src_dir );
182
 
183
    #
184
    #   Delete and create target dir
185
    #
186
    rmtree( $dst_dir )
187
        if ( $opt->{'DeleteFirst'} );
188
    mkpath($dst_dir, 0, 0775);
189
 
190
    #
191
    #   Invoke Find to decend the directory tree
192
    #
193
    #   Have used global vars to pass data into the find callback
194
    #
195
    #   Only use the preprocess rotine if we are doing any form
196
    #   of matching.
197
    #
198
    #   'follow_fast' does not work correctly under windows if the path
199
    #   has a drive letter. Don't use it under Windows
200
    #
201
 
202
    JatsCopyInternal::MyFind( $opt );
203
 
204
#    DebugDumpData("opt", $opt );
205
}
206
 
207
#-------------------------------------------------------------------------------
208
# Function        : CopyFile
209
#
210
# Description     : Utility function to copy a single file
211
#                   Uses the same options and loging infrastructure
212
#                   as CopyDir
213
#
214
# Inputs          : $src_file               - Src directory
215
#                   $dst_file               - Dest file (or dir)
216
#                   $opt                    - A Hash of options
217
#                                             Refer to CopyDir
218
#
219
# Returns         : 
220
#
221
sub CopyFile
222
{
223
    my $src_file = shift;
224
    my $dst_file = shift;
225
    my $opt = JatsCopyInternal::DefaultOpts( 'CopyFile', @_);
226
 
227
    #
228
    #   Do not Validate source dir
229
    #   Do it within the copy operation and get the same error
230
    #   handling as the CopyDir processing
231
    #
232
 
233
    #
234
    #   Insert additional options to provide the same interface to
235
    #   the internal functions used to do the copy
236
    #
237
    #       item            - Source Path
238
    #       tgt             - Dest subdir below dst_dir
239
    #       file            - Filename
240
    #       target          - Dest Path
241
    #
242
    $opt->{'item'}     = $src_file;
243
    $opt->{'file'}     = StripDir ($dst_file);
244
    $opt->{'target'}   = $dst_file;
245
    $opt->{'type'}     = 'f';
246
 
247
    #
248
    #   Invoke the common file copy routine
249
    #
250
    JatsCopyInternal::CopyFile ( $src_file, $dst_file, $opt );
251
 
252
#    DebugDumpData("opt", $opt );
253
}
254
 
255
#-------------------------------------------------------------------------------
256
# Function        : CreateDir
257
#
258
# Description     : Utility function to create a directory
259
#                   Uses the same options and loging infrastructure
260
#                   as CopyDir
261
#
262
# Inputs          : $dst                    - Dest dir to create
263
#                   $opt                    - A Hash of options
264
#                                             Refer to CopyDir
265
#
266
# Returns         : 
267
#
268
sub CreateDir
269
{
270
    my $dst = shift;
271
    my $opt = JatsCopyInternal::DefaultOpts( 'CreateDir', @_ );
272
 
273
    #
274
    #   Delete and create target dir
275
    #
276
    rmtree( $dst )
277
        if ( $opt->{'DeleteFirst'} );
278
 
279
    #
280
    #   Insert additional options to provide the same interface to
281
    #   the internal functions used to do the copy
282
    #
283
    #       item            - Source Path
284
    #       tgt             - Dest subdir below dst_dir
285
    #       file            - Filename
286
    #       target          - Dest Path
287
    #
288
    $opt->{'item'}     = '';
289
    $opt->{'file'}     = '';
290
    $opt->{'target'}   = $dst;
291
    $opt->{'type'}     = 'd';
292
 
293
    #
294
    #   Invoke the common file copy routine
295
    #
296
    JatsCopyInternal::CreateDir ( $dst, $opt );
297
 
298
#    DebugDumpData("opt", $opt );
299
}
300
 
301
#-------------------------------------------------------------------------------
302
# Function        : DeleteDir
303
#
304
# Description     : Utility function to delete a directory tree
305
#                   Uses the same options and loging infrastructure
306
#                   as CopyDir
307
#
308
# Inputs          : $dst                    - Dest dir to create
309
#                   $opt                    - A Hash of options
310
#                                             Refer to CopyDir
311
#
312
# Returns         : 
313
#
314
sub DeleteDir
315
{
316
    my $dst = shift;
317
    my $opt = JatsCopyInternal::DefaultOpts( 'DeleteDir', @_ );
318
 
319
    #
320
    #   Insert additional options to provide the same interface to
321
    #   the internal functions used to do the copy
322
    #
323
    #       item            - Source Path
324
    #       tgt             - Dest subdir below dst_dir
325
    #       file            - Filename
326
    #       target          - Dest Path
327
    #
328
    $opt->{'item'}     = '';
329
    $opt->{'file'}     = '';
330
    $opt->{'target'}   = $dst;
331
    $opt->{'type'}     = 'd';
332
 
333
    #
334
    #   Invoke the common file copy routine
335
    #
336
    $opt->{'Logger'} ( "Delete Dir" ,$opt );
337
    rmtree( $dst );
338
}
339
 
340
#-------------------------------------------------------------------------------
341
# Function        : DeleteFile
342
#
343
# Description     : Utility function to delete a file
344
#                   Uses the same options and loging infrastructure
345
#                   as CopyDir
346
#
347
#                   Uses the same functions as DeleteDir simply because
348
#                   rmtree does such a great job
349
#
350
# Inputs          : $dst                    - Dest dir to create
351
#                   $opt                    - A Hash of options
352
#                                             Refer to CopyDir
353
#
354
# Returns         : 
355
#
356
sub DeleteFile
357
{
358
    my $dst = shift;
359
    my $opt = JatsCopyInternal::DefaultOpts( 'DeleteFile', @_);
360
 
361
    #
362
    #   Insert additional options to provide the same interface to
363
    #   the internal functions used to do the copy
364
    #
365
    #       item            - Source Path
366
    #       tgt             - Dest subdir below dst_dir
367
    #       file            - Filename
368
    #       target          - Dest Path
369
    #
370
    $opt->{'item'}     = '';
371
    $opt->{'file'}     = '';
372
    $opt->{'target'}   = $dst;
373
    $opt->{'type'}     = 'f';
374
 
375
    #
376
    #   Invoke the common file copy routine
377
    #
378
    $opt->{'Logger'} ( "Delete File" ,$opt );
379
    rmtree( $dst );
380
}
381
 
382
#-------------------------------------------------------------------------------
383
# Function        : SetCopyDirDefaults
384
#
385
# Description     : Set default options to be used by all the functions
386
#                   Simplifies the process fo setting options on all
387
#                   operations
388
#
389
# Inputs          : $uopt                        - A Hash of options
390
#                                                - An array of options
391
#
392
# Returns         : Nothing
393
#
394
sub SetCopyDirDefaults
395
{
396
    my $name = 'SetCopyDirDefaults';
397
    return if ( $#_ < 0);
398
 
399
    #
400
    #   User can pass in a reference to a hash or a hash as
401
    #   a list of argumnts
402
    #
403
    my $uopt = JatsCopyInternal::ArgsToRef ($name, @_ );
404
 
405
    #
406
    #   Insert user options into the default hash
407
    #
408
    JatsCopyInternal::ValidateArg ($name, $uopt, $Global_opts );
409
 
410
    #
411
    #   BEGIN Block to initialise default global options
412
    #   Note: This will be called first
413
    #   Note: Multiple begin blocks are allowed
414
    #
415
    sub BEGIN
416
    {
417
        my %stats;
418
        #
419
        #   Insert some default options
420
        #   Later insert user options
421
        #
422
        $Global_opts->{'Error'}  = \&JatsCopyInternal::Error;
423
        $Global_opts->{'Expert'} = \&JatsCopyInternal::Body;
424
        $Global_opts->{'Logger'} = \&JatsCopyInternal::Log;
425
        $Global_opts->{'Stats'}  = \%stats;
426
    }
427
}
428
 
429
################################################################################
430
#
431
#   Hide the body of the work within another package
432
#   Done to make it obvious which parts are user accessible
433
#
434
package JatsCopyInternal;
435
 
436
#use JatsError;
437
use File::Basename;
438
use File::Path;
439
use File::Copy;
440
use Cwd 'abs_path';
441
 
442
#-------------------------------------------------------------------------------
443
# Function        : MyFind
444
#
445
# Description     : Recurse a directory tree and locate files of interest
446
#
447
#                   Tried to use File::Find, but this has several major
448
#                   limitations:
449
#                       'preprocess' does not work with 'follow' symlinks
450
#                       Without 'preprocess' there is no way to terminate
451
#                       a directory sub-tree recursion leading to complicated
452
#                       code to do directory pruning.
453
#
454
#                   This function will perform file and directory name matching
455
#                   on the fly. All items with match are passed to the user
456
#                   examination functions and eventually to the processing
457
#                   function to perform the actual copy
458
#
459
#                   Current implementation will:
460
#                       Follow dir Symlinks
461
#
462
#                       Process dir element anytime before the dir contents
463
#                       Not 'just' before.
464
#
465
#
466
# Inputs          : $opt                        - Hash of search options
467
#
468
# Returns         : Nothing
469
#
470
sub MyFind
471
{
472
    my ( $opt ) = @_;
473
    local ( *DIR );
474
 
475
    #
476
    #   Create a list of subdirs to scan
477
    #       Elements do not contain the SrcDir
478
    #       Elements have a '/' suffix - simplify joining
479
    #
480
    my @dirs = '';
481
 
482
    #
483
    #   Process all directories in the list
484
    #   Pop them off so we do a depth first search
485
    #
486
    while ( @dirs )
487
    {
488
        my $root = pop( @dirs );
489
 
490
        my $dir = $opt->{'SrcDir'} . '/' . $root;
491
	    unless (opendir DIR, $dir )
492
        {
493
	        ::Warning ("File Find. Can't opendir($dir): $!\n");
494
	        next;
495
        }
496
	    my @filenames = readdir DIR;
497
	    closedir(DIR);
498
 
499
        foreach my $file ( @filenames )
500
        {
501
            #
502
            #   Ignore filesystem house keeping directories
503
            #
504
            next if ( $file eq '.' || $file eq '..' );
505
 
506
            #
507
            #   Common processing
508
            #       Ignore all files and directories that start with a .
509
            #       Unix 'hidden' files may be simply ignored
510
            #
511
            next if ( $opt->{'IgnoreDots'} && substr( $file, 0, 1) eq '.' );
512
 
513
            #
514
            #   Determine the type of element
515
            #       1)Link
516
            #           - Link to a File
517
            #           - Link to a directory
518
            #       2)File
519
            #       3)Directory
520
            #
521
            my $filename = $dir . $file;
522
            my $relname = $root . $file;
523
 
524
            #
525
            #   Stat the file
526
            #   Use speed trick. (-f _) will use into from last stat/lstat
527
            #
528
            stat ( $filename );
529
            if ( -f _ )
530
            {
531
                $opt->{'Stats'}{'examinedFiles'}++;
532
                next unless doMatch ( $file, $opt, 'MatchRE', 'IgnoreRE' );
533
                $opt->{'type'} = 'f';
534
            }
535
            elsif ( -d _ )
536
            {
537
                #
538
                #   Only process the top-level directory
539
                #
540
                next if ( $opt->{'NoSubDirs'} );
541
 
542
                #
543
                #   Match against wanted items
544
                #
545
                $opt->{'Stats'}{'examinedDirs'}++;
546
                next unless doMatch ( $file, $opt, 'MatchDirsRE', 'IgnoreDirsRE' );
547
 
548
                #
549
                #   Add to the list of future dirs to process
550
                #   Place on end to ensure depth first
551
                #   Algorithm requires dirname has a trailing /
552
                #
553
                push @dirs, $relname . '/';
554
 
555
                #
556
                #   Create flat output dir - no more processing
557
                #
558
                next if ( $opt->{'Flatten'} );
559
                $opt->{'type'} = 'd';
560
 
561
            }
562
            else
563
            {
564
                ::Warning ("Find File: Unknown type skipped: $filename");
565
                next;
566
            }
567
 
568
            #
569
            #   Have a valid element to process
570
            #   Setup parameters for later users
571
            #
572
            my $target = ( $opt->{'Flatten'} ) ? $file : $relname;
573
 
574
            $opt->{'file'}   = $file;                           # Element name
575
            $opt->{'item'}   = $filename;                       # Full path
576
            $opt->{'target'} = $opt->{'DstDir'} . '/' .$target; # Target(Below dest)
577
 
578
            #
579
            #   If the user has opted to examine each file then ...
580
            #   If user returns TRUE then continue with operation
581
            #
582
            #   Note: It is allowed to play with the copy args
583
            #         but be careful. Only 'target' should be messed with
584
            #
585
            if ( $opt->{'Examine'} )
586
            {
587
                next unless ( $opt->{'Examine'} ( $opt ) )
588
            }
589
 
590
            #
591
            #   Always invoke the 'Expert' function
592
            #   A dummy one will be provided unless the user gave one
593
            #
594
            $opt->{'Expert'} ( $opt );
595
        }
596
    }
597
}
598
 
599
#-------------------------------------------------------------------------------
600
# Function        : Body
601
#
602
# Description     : Default CopyDir copy operation function
603
#                   This function will be used if the user does not provide
604
#                   one of their own
605
#
606
# Inputs          : $opt            - Ref to hash of options and args
607
#
608
# Returns         :
609
#
610
sub Body
611
{
612
    my ($opt) = @_;
613
    my $item = $opt->{'item'};
614
    my $target = $opt->{'target'};
615
 
616
    #
617
    #   If a directory, create the directory
618
    #
619
    if ( $opt->{'type'} eq 'd' )
620
    {
621
        $opt->{'Stats'}{'dirs'}++;
622
 
623
        #
624
        #   Directories are handled differently
625
        #       - Directories are created with nice permissions
626
        #       - Empty directories are created here
627
        #
628
        if ( $opt->{'EmptyDirs'} )
629
        {
630
            CreateDir ($target, $opt);
631
        }
632
    }
633
    else
634
    {
635
        CopyFile ( $item, $target, $opt );
636
    }
637
}
638
 
639
#-------------------------------------------------------------------------------
640
# Function        : CreateDir
641
#
642
# Description     : Create a directory
643
#                   With loging
644
#
645
# Inputs          : $dir                        - Dir to Create
646
#                   $opt                        - Process Data
647
#
648
# Returns         : 
649
#
650
sub CreateDir
651
{
652
    my ($dir, $opt) = @_;
653
    if ( ! -d $dir )
654
    {
655
        $opt->{'Logger'} ( "Creating Dir", $opt, $dir );
656
        mkpath($dir, 0, 0775);
657
    }
658
}
659
 
660
#-------------------------------------------------------------------------------
661
# Function        : CopyFile
662
#
663
# Description     : Copy a file with common logging and other basic options
664
#
665
# Inputs          : $item                      - Source Path
666
#                   $target                    - Dest Path (dir+name)
667
#                   $opt                       - Ref to options hash
668
#
669
#
670
#                   Only a few of the options are implemented
671
#                   Becareful if using this function directly
672
#
673
# Returns         : Nothing
674
#
675
sub CopyFile
676
{
677
    my ($item, $target, $opt) = @_;
678
 
679
    #
680
    #   If the target already exists then we may need to take some
681
    #   action. The default action is to delete and replace
682
    #
683
    if ( -e $target )
684
    {
685
        if ( $opt->{'Exists'} )
686
        {
687
            return unless
688
                $opt->{'Exists'} ( $opt );
689
        }
690
        elsif ( $opt->{'NoOverwrite'} )
691
        {
692
            return;
693
        }
694
        rmtree( $target );
695
    }
696
 
697
    #
698
    #   Ensure that the target directory exists
699
    #   Don't assume prior creation - the user may have messed with the path
700
    #
701
    my $tdir = $target;
702
    $tdir =~ s~/[^/]+$~~;
703
    CreateDir ( $tdir, $opt);
704
 
705
    {
706
        #
707
        #   Try a symlink first
708
        #
709
        if ( $opt->{'SymlinkFiles'}  )
710
        {
711
            $opt->{'Logger'} ( "Linking File" ,$opt );
712
            if (symlink (abs_path( $item ), $target)  )
713
            {
714
                $opt->{'Stats'}{'links'}++;
715
                last;
716
            }
717
            #
718
            #   Symlink has failed
719
            #   Flag: Don't attempt to symlink anymore
720
            #
721
            $opt->{'SymlinkFiles'}  = 0;
722
        }
723
 
724
        #
725
        #   Copy file to destination
726
        #   If the file is a link, then duplicate the link contents
727
        #   Use: Unix libraries are created as two files:
728
        #        lib.xxxx.so -> libxxxx.so.vv.vv.vv
729
        #
730
        if ( -l $item && $opt->{'DuplicateLinks'} )
731
        {
732
            $opt->{'Logger'} ( "Copying Link" ,$opt );
733
            my $link = readlink $item;
734
            symlink ($link, $target );
735
            unless ( $link && -l $target )
736
            {
737
                $opt->{'Error'} ( "Failed to copy link [$item] to [$target]: $!", $! , $opt );
738
            }
739
            $opt->{'Stats'}{'links'}++;
740
            last;
741
        }
742
 
743
        if (File::Copy::copy($item, $target))
744
        {
745
            $opt->{'Logger'} ( "Copying File" ,$opt );
746
            my $perm = 0775;
747
            $perm = (stat $target)[2] & 07777 & 0555
748
                if ( $opt->{'ReadOnlyFiles'} );
749
            CORE::chmod $perm, $target;
750
            $opt->{'Stats'}{'files'}++;
751
            last;
752
        }
753
 
754
        #
755
        #   All attempts to copy have failed
756
        #
757
        $opt->{'Error'} ( "Failed to copy file [$item] to [$target]: $!", $! ,$opt );
758
    }
759
}
760
 
761
 
762
#-------------------------------------------------------------------------------
763
# Function        : Log
764
#
765
# Description     : Default Copy Log callback function
766
#
767
# Inputs          : $type
768
#                   $opt hash
769
#                   $ltarget                        - Target to log
770
#
771
# Returns         : 
772
#
773
sub Log
774
{
775
    my ($type, $opt, $ltarget) = @_;
776
    return unless ( $opt->{'Log'} );
777
 
778
    #
779
    #   User target or logging target as overide
780
    #
781
    $ltarget = $opt->{'target'} unless ( $ltarget );
782
 
783
    if ( $opt->{'Log'} < 2 )
784
    {
785
        JatsError::Information (sprintf( "%-15s [%s]", $type, $ltarget));
786
    }
787
    else
788
    {
789
        JatsError::Information (sprintf( "%-15s  [%s]->[%s], %s, %s", $type,
790
                                    $opt->{'item'},
791
                                    $ltarget,
792
                                    $opt->{'file'},
793
                                    $opt->{'type'},
794
                                    ));
795
    }
796
}
797
 
798
#-------------------------------------------------------------------------------
799
# Function        : Error
800
#
801
# Description     : Default Copy Error callback function
802
#
803
# Inputs          : $message
804
#                   $ecode
805
#                   $opt hash
806
#
807
# Returns         : Does not return
808
#
809
sub Error
810
{
811
    my ($message, $ecode, $opt) = @_;
812
    JatsError::Error ($message);
813
}
814
 
815
#-------------------------------------------------------------------------------
816
# Function        : Pat2GlobList
817
#
818
# Description     : Convert a list of simple filenames into list of
819
#                   RE. Simple filenames may contain simple globs
820
#
821
# Inputs          : $opt                - Option hash
822
#                   $src                - Name of Source Data
823
#                   $dst                - Name of Dest Data
824
#
825
# Returns         : Updates dst data
826
#
827
sub Pat2GlobList
828
{
829
    my ($opt, $src, $dst) = @_;
830
    foreach ( @{$opt->{$src}} )
831
    {
832
        push @{$opt->{$dst}}, glob2pat($_);
833
    }
834
}
835
 
836
#-------------------------------------------------------------------------------
837
# Function        : glob2pat
838
#
839
# Description     : Convert four shell wildcard characters into their equivalent
840
#                   regular expression; all other characters are quoted to
841
#                   render them literals.
842
#
843
# Inputs          : Shell style wildcard pattern
844
#
845
# Returns         : Perl RE
846
#
847
sub glob2pat
848
{
849
    my $globstr = shift;
850
    $globstr =~ s~^/~~;
851
    my %patmap = (
852
        '*' => '.*',
853
        '?' => '.',
854
        '[' => '[',
855
        ']' => ']',
856
    );
857
    $globstr =~ s{(.)} { $patmap{$1} || "\Q$1" }ge;
858
    return '^' . $globstr . '$';
859
}
860
 
861
#-------------------------------------------------------------------------------
862
# Function        : doMatch
863
#
864
# Description     : Match a file against a match specification
865
#                   Match, before ignore
866
#
867
# Inputs          : $file                   - File to match
868
#                   $opt                    - Options hash
869
#                   $mname                  - RE to match
870
#                   $iname                  - RE's to ignore
871
#
872
# Returns         : true    - File can matched
873
#
874
sub doMatch
875
{
876
    my ($file, $opt, $mname, $iname) = @_;
877
 
878
    if ( $opt->{$mname} )
879
    {
880
        if ( my @mlist = @{$opt->{$mname}} )
881
        {
882
            #
883
            #   Must match if we have a match list,
884
            #   then process ignore list
885
            #
886
            my $match = 0;
887
            foreach (@mlist)
888
            {
889
                if ( $file =~ m~$_~ )
890
                {
891
                    $match = 1;
892
                    last;
893
                }
894
            }
895
            return 0
896
                unless ( $match );
897
        }
898
    }
899
 
900
    if ( $opt->{$iname} )
901
    {
902
        foreach ( @{$opt->{$iname}})
903
        {
904
            return 0
905
                if ( $file =~ m~$_~ );
906
        }
907
    }
908
    return 1;
909
}
910
 
911
 
912
#-------------------------------------------------------------------------------
913
# Function        : DefaultOpts
914
#
915
# Description     : Insert default opts into the option structure
916
#
917
# Inputs          : $name               - Utility name
918
#                   @opts               - User options (hash ref or an array)
919
#
920
# Returns         : Ref to a new set of options
921
#                   With defauls inserted
922
#
923
sub DefaultOpts
924
{
925
    my $name = shift;
926
    my $uopt = ArgsToRef ($name, @_ );
927
    my $opt;
928
 
929
    #
930
    #   Init with global options
931
    #
932
    foreach ( keys %{$Global_opts} )
933
    {
934
        $opt->{$_} = $Global_opts->{$_};
935
    }
936
 
937
    #
938
    #   Transfer the users options into our own working hash
939
    #   Allows the user to create an option-set that won't get messed with
940
    #   Validity test of user args
941
    #
942
    ValidateArg ($name, $uopt, $opt);
943
 
944
    #
945
    #   Determine if the underlying system supports symlinks
946
    #   May be killed later if we discover that the filesystem
947
    #   does not support symlinks
948
    #
949
    if ( $opt->{'SymlinkFiles'} )
950
    {
951
        my $symlinks = eval { symlink("",""); 1 } || 0;
952
        $opt->{'SymlinkFiles'} = 0
953
            unless ( $symlinks  );
954
    }
955
 
956
    #
957
    #   Return a new options structure
958
    #   One that won't pollute the users set of options
959
    #
960
    return $opt;
961
}
962
 
963
 
964
#-------------------------------------------------------------------------------
965
# Function        : ArgsToRef
966
#
967
# Description     : Convert an argument list into a hash reference
968
#                   with error checking
969
#
970
# Inputs          : $name                   - User function name
971
#                   *                       - User arguments
972
#                                             May be a ref to a hash
973
#                                             Array of args
974
#
975
# Returns         : Ref to a hash
976
#
977
sub ArgsToRef
978
{
979
    my $name = shift;
980
    my $uopt;
981
 
982
    #
983
    #   User can pass in:
984
    #       Nothing at all
985
    #       A reference to a hash
986
    #       A hash as a list of argumnts
987
    #
988
    if ( $#_ < 0 ) {
989
 
990
    } elsif ( UNIVERSAL::isa($_[0],'HASH') ) {
991
         $uopt = $_[0];
992
 
993
    } else {
994
        #
995
        #   A list of arguments
996
        #   Treat it as a hash. Must have an even number of arguments
997
        #
998
        Error ("$name: Odd number of args to SvnRmView")
999
            unless ((@_ % 2) == 0);
1000
        $uopt = {@_};
1001
    }
1002
 
1003
    return $uopt;
1004
}
1005
 
1006
################################################################################
1007
#   
1008
#
1009
#   Valid User Arguments
1010
#   Hash value is used to determine if the CopyDir operation must perform
1011
#   extensive matching operations.
1012
#
1013
use constant    Scalar   => 1;
1014
use constant    Match    => 2;
1015
use constant    CodeRef  => 4;
1016
use constant    ArrayRef => 8;
1017
use constant    HashRef  => 16;
1018
 
1019
my %ValidArgs = (
1020
    'DeleteFirst'     => Scalar,
1021
    'DuplicateLinks'  => Scalar,
1022
    'NoSubDirs'       => Scalar | Match,
1023
    'Flatten'         => Scalar,
1024
    'Logger'          => CodeRef,
1025
    'EmptyDirs'       => Scalar,
1026
    'IgnoreDots'      => Scalar | Match,
1027
    'Expert'          => CodeRef,
1028
    'Examine'         => CodeRef,
1029
    'Exists'          => CodeRef,
1030
    'Log'             => Scalar,
1031
    'Error'           => CodeRef,
1032
    'Stats'           => HashRef,
1033
    'Match'           => ArrayRef | Match,
1034
    'MatchRE'         => ArrayRef | Match,
1035
    'MatchDirs'       => ArrayRef | Match,
1036
    'MatchDirsRE'     => ArrayRef | Match,
1037
    'Ignore'          => ArrayRef | Match,
1038
    'IgnoreRE'        => ArrayRef | Match,
1039
    'IgnoreDirs'      => ArrayRef | Match,
1040
    'IgnoreDirsRE'    => ArrayRef | Match,
1041
    'NoOverwrite'     => Scalar,
1042
    'UserData'        => 0,
1043
    'SymlinkFiles'    => Scalar,
1044
    'ReadOnlyFiles'   => Scalar,
1045
);
1046
 
1047
 
1048
#-------------------------------------------------------------------------------
1049
# Function        : ValidateArg
1050
#
1051
# Description     : Validate a user option arguments
1052
#                   Transfer validated options to a target hash
1053
#
1054
# Inputs          : $name                   - User function
1055
#                   $uopt                   - Source option list to process
1056
#                   $topt                   - Target option ref
1057
#
1058
# Returns         : Nothing
1059
#
1060
sub ValidateArg
1061
{
1062
    my ($name, $uopt, $topt ) = @_;
1063
 
1064
    foreach ( keys %{$uopt} )
1065
    {
1066
        #
1067
        #   Option must exist
1068
        #
1069
        Error ("$name. Invalid option: $_")
1070
            unless ( exists $ValidArgs{$_} );
1071
 
1072
        my $ref =  ref($uopt->{$_});
1073
        my $mask = $ValidArgs{$_};
1074
 
1075
        if ( $mask & Scalar )
1076
        {
1077
            Error ("$name. Argument not expecting a ref: $_")
1078
                if ( $ref );
1079
        }
1080
 
1081
        if ( $mask & CodeRef )
1082
        {
1083
            Error ("$name. Argument requires a Code Reference: $_")
1084
                if ( $ref ne 'CODE' );
1085
        }
1086
 
1087
        if ( $mask & ArrayRef )
1088
        {
1089
            Error ("$name. Argument requires an Array Reference: $_")
1090
                if ( $ref ne 'ARRAY' );
1091
        }
1092
 
1093
        if ( $mask & HashRef )
1094
        {
1095
            Error ("$name. Argument requires an Hash Reference: $_")
1096
                if ( $ref ne 'HASH' );
1097
        }
1098
 
1099
        #
1100
        #   If any of the Match options are active, then flag OptMatch
1101
        #   This will be used to speed up searching and processing
1102
        #
1103
        $topt->{'OptMatch'} = 1
1104
            if ( $mask & Match );
1105
 
1106
        #
1107
        #   Insert the user argument
1108
        #
1109
        $topt->{$_} = $uopt->{$_}
1110
    }
1111
}
1112
 
1113
1;
1114