Subversion Repositories DevTools

Rev

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

Rev Author Line No. Line
227 dpurdie 1
#! perl
2
########################################################################
3
# Copyright ( C ) 2005 ERG Limited, All rights reserved
4
#
5
# Module name   : jats.sh
6
# Module type   : Perl Package
7
# Compiler(s)   : n/a
8
# Environment(s): jats
9
#
10
# Description   : This package contains functions to manipulate file paths
11
#                 directories and names.
12
#
13
#                    Getcwd                     - Get current directory
14
#                    Realpath                   - Get real path
15
#                    Realfile                   - Get real path ?
16
#                    RelPath                    - Convert to relative path
17
#                    AbsPath                    - Convert to Abs path
18
#                    FullPath                   - Convert to Abs path with driver letter
257 dpurdie 19
#                    TruePath                   - Case Corrected pathname
227 dpurdie 20
#                    CleanPath                  - Clean up a path
21
#                    StripDrive                 - Remove drive letter
22
#                    StripDir                   - Return file + extension
23
#                    StripExt                   - Return dir + file
24
#                    StripFile                  - Returns extension
25
#                    StripFileExt               - Returns directory
26
#                    StripDirExt                - Returns filename ( with optional ext)
27
#                    CleanDirName               - Clean up a path
28
#                    DisplayPath                - Genate a Path that can be displayed
29
#
30
#
31
#......................................................................#
32
 
255 dpurdie 33
use 5.006_001;
227 dpurdie 34
use strict;
35
use warnings;
36
 
37
################################################################################
38
#   Global variables used by functions in this package
39
#   For historical reasons many of these variabeles are global
40
#
41
 
42
package FileUtils;
43
use JatsError;
44
use Cwd;
45
 
46
our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION);
47
use Exporter;
48
 
49
$VERSION = 1.00;
50
@ISA = qw(Exporter);
51
 
52
# Symbols to autoexport (:DEFAULT tag)
53
@EXPORT = qw(   InitFileUtils
54
                Getcwd
55
                Realpath
56
                RelPath
57
                AbsPath
58
                FullPath
59
                CleanPath
60
                StripDrive
61
                StripDir
62
                StripExt
63
                StripFile
64
                StripFileExt
65
                StripDirExt
66
                CleanDirName
67
                TouchFile
68
                FileIsNewer
69
                DisplayPath
257 dpurdie 70
                TruePath
227 dpurdie 71
 
72
                $ScmPathSep
73
                $Cwd
74
                $CwdDrive
75
                $ScmHost
76
            );
77
#
78
# exported package globals go here
79
#
80
our $ScmPathSep;                # Windows/Unix path seperator
81
our $Cwd       ;                # Current directory ( no drive letter )
247 dpurdie 82
our $CwdFull   ;                # Current directory ( with drive letter )
227 dpurdie 83
our $CwdDrive  ;                # Current drive
84
our $ScmHost   ;                # Host Type. Unix, WIN
85
 
86
#
87
#   Internal variables
88
#
89
my  $isCygWin;                 # Running under CygWin
90
my  $isUnix;                   # Is Unix
91
 
92
#-------------------------------------------------------------------------------
93
# Function        : BEGIN
94
#
95
# Description     : Determine some values very early
96
#
97
#
98
BEGIN
99
{
100
    $ScmHost = "Unix";                                # UNIX, default
101
 
102
    Debug( "PerlHost:  $^O" );
103
    $ScmHost = "DOS"      if ($^O eq "win95");        # DOS Perl dependent
104
    $ScmHost = "WIN"      if ($^O eq "MSWin32");      # ActivePerl
105
    $ScmHost = "WIN"      if ($^O eq "cygwin");       # Cygwin
106
 
107
    $isUnix = 1 if ( $ScmHost eq "Unix"  );
255 dpurdie 108
    $isCygWin = 1 if ( $ENV{'SHELL'} || $ENV{'CYGWIN'} );
227 dpurdie 109
 
110
    $ScmPathSep = $isUnix ? ':' : ';';     # Unix / Windows
111
}
112
 
113
#-------------------------------------------------------------------------------
114
# Function        : InitFileUtils
115
#
116
# Description     : Initialise this package
117
#                   This function should be called once the user has determined
118
#                   settled on a working directory
119
#
120
#                   The function may be called multiple times
121
#                   to allow various globals to be reset - when the user has
122
#                   changed directory
123
#
124
# Inputs          : Nothing
125
#
126
# Returns         : Nothing
127
#
128
sub InitFileUtils
129
{
130
    #
131
    #   Setup current directory and drive
132
    #
133
 
247 dpurdie 134
    $CwdFull = Getcwd();                    # Current working dir
227 dpurdie 135
 
136
    $CwdDrive = '';
247 dpurdie 137
    $CwdDrive = substr( $CwdFull, 0, 2 )    # Saved Drive letter
227 dpurdie 138
        if ( ! $isUnix );
139
 
247 dpurdie 140
    $Cwd = StripDrive( $CwdFull );          # With drive spec striped
227 dpurdie 141
 
142
    Debug ("InitFileUtils: ScmHost     : $ScmHost");
247 dpurdie 143
    Debug ("InitFileUtils: CwdFull     : $CwdFull");
227 dpurdie 144
    Debug ("InitFileUtils: Cwd         : $Cwd");
145
    Debug ("InitFileUtils: CwdDrive    : $CwdDrive");
146
    Debug ("InitFileUtils: ScmPathSep  : $ScmPathSep");
147
}
148
 
149
 
150
#-------------------------------------------------------------------------------
151
# Function        : Getcwd
152
#
153
# Description     : Retrieve current working directory
154
#
155
# Inputs          : None
156
#
157
# Returns         : The current working directory
158
#
159
# Notes           : Don't use 'pwd' program as it gets symbolic links wrong
160
#
161
sub Getcwd
162
{
163
    my $cwd = getcwd();
164
    return $cwd;
165
}
166
 
167
#-------------------------------------------------------------------------------
168
# Function        : TouchFile
169
#
170
# Description     : touch a file
171
#                   Real use is to touch a marker file
172
#
173
# Inputs          : path        - path to the file
174
#
175
# Returns         : TRUE if an error occured in creating the file
176
#
177
sub TouchFile
178
{
179
    my ($path, $text) = @_;
180
    my $result = 0;
181
    Verbose ("Touching file: $path" );
182
    if ( ! -f $path )
183
    {
184
        open (TOUCH, ">>", $path) || ($result = 1);
185
        close (TOUCH);
186
    }
187
    else
188
    {
189
 
190
        #
191
        #   Modify the file
192
        #
193
        #   Need to physically modify the file
194
        #   Need to change the 'change time' on the file. Simply setting the
195
        #   last-mod and last-access is not enough to get past WIN32
196
        #   OR 'utime()' does not work as expected
197
        #
198
        #   Read in the first character of the file, rewind and write it
199
        #   out again.
200
        #
201
        my $data;
202
        open (TOUCH , "+<", $path ) || return 1;
203
        if ( read ( TOUCH, $data, 1 ) ) {
204
            seek ( TOUCH, 0, 0 );
205
            print TOUCH $data;
206
        }
207
        else
208
        {
209
            #
210
            #   File must have been of zero length
211
            #   Delete the file and create it
212
            #
213
            close (TOUCH);
214
            unlink ( $path );
215
            open (TOUCH, ">>", $path) || ($result = 1);
216
        }
217
        close (TOUCH);
218
    }
219
    return $result;
220
}
221
 
222
#-------------------------------------------------------------------------------
223
# Function        : FileIsNewer
224
#
225
# Description     : Test two files to see if the files are newer
226
#
227
# Inputs          : file1
228
#                   file2
229
#
230
# Returns         : Returns true if file1 is newer than file2 or file2 does not
231
#                   exist.
232
#
233
#                   If file 1 does not exist then it will return false
234
#
235
sub FileIsNewer
236
{
237
    my ($file1, $file2) = @_;
238
 
239
    my $f1_timestamp = (stat($file1))[9] || 0;
240
    my $f2_timestamp = (stat($file2))[9] || 0;
241
    my $result = $f1_timestamp > $f2_timestamp ? 1 : 0;
242
 
243
    Verbose2 ("FileIsNewer: TS: $f1_timestamp, File: $file1");
244
    Verbose2 ("FileIsNewer: TS: $f2_timestamp, File: $file2");
245
    Verbose2 ("FileIsNewer: $result" );
246
 
247
    return $result;
248
}
249
 
250
#-------------------------------------------------------------------------------
251
# Function        : Realpath
252
#
253
# Description     : Returns the 'real path'
254
#
255
# Inputs          : $path       - Path to process
256
#
257
# Returns         : The real path
258
#
259
sub Realpath
260
{
261
    my( $path ) = @_;
262
    my( $real, $cwd );
263
 
264
    $cwd = Getcwd();
265
    if (!chdir( $path )) {
266
        $real = "";
267
    } else {
268
        $real = Getcwd();
269
        Error ("FATAL: Realpath($path) could not restore directory ($cwd)." )
270
            unless (chdir( $cwd ));
271
    }
272
    Debug( "Realpath:   = $real ($path)" );
273
    return $real;
274
}
275
 
276
#-------------------------------------------------------------------------------
277
# Function        : Realfile
278
#
279
# Description     : Returns the 'real path'
280
#
281
# Inputs          : $path       - Path to process
282
#
283
# Returns         : The real path
284
#
285
#sub Realfile
286
#{
287
#    my( $path ) = @_;
288
#    my( $real, $cwd );
289
#
290
#    $cwd = Getcwd();
291
#    if (!chdir( $path )) {
292
#        $real = "";
293
#    } else {
294
#        $real = Getcwd();
295
#        Error ("FATAL: Realpath($path) could not restore directory ($cwd)." )
296
#            unless (chdir( $cwd ));
297
#    }
298
#    Debug( "Realpath:   = $real ($path)" );
299
#    return $real;
300
#}
301
 
302
#-------------------------------------------------------------------------------
303
# Function        : RelPath
304
#
305
# Description     : Return the relative path to the current working directory
306
#                   as provided in $Cwd
307
#
308
# Inputs          : Base directory to convert
309
#
310
# Returns         : Relative path from the current directory to the base directory
311
#
312
sub RelPath
313
{
314
    my ($base) = @_;
315
 
316
    my @base = split ('/', $base );
317
    my @here = split ('/', $Cwd );
318
    my $result;
319
 
320
    Debug("RelPath: Source: $base");
321
 
322
    return $base unless ( $base =~ m~^/~ );
323
 
324
    #
325
    #   Remove common bits from the head of both lists
326
    #
327
    while ( $#base >= 0 && $#here >= 0 && $base[0] eq $here[0] )
328
    {
329
        shift @base;
330
        shift @here;
331
    }
332
 
333
    #
334
    #   Need to go up some directories from here and then down into base
335
    #
336
    $result = '../' x ($#here + 1);
337
    $result .= join ( '/', @base);
338
    $result = '.' unless ( $result );
339
    $result =~ s~/$~~;
340
 
341
    Debug("RelPath: Result: $result");
342
    return $result;
343
}
344
 
345
#-------------------------------------------------------------------------------
346
# Function        : AbsPath
347
#
348
# Description     : Return the absolute path to the file
349
#                   Add the current directory if the path is absolute
350
#                   Clean up xxx/.. constructs
351
#
352
#                   If an absolute path is provided then it will simply be
353
#                   cleaned up.
354
#
355
# Assumption      : Absolute paths start with a "/" and do not have a drive letter
356
#
357
# Inputs          : Source file path
358
#
359
# Returns         : Cleaned abs path
360
#
361
sub AbsPath
362
{
363
    my ($dpath) = @_;
364
    my @result;
365
    my $drive;
366
 
367
    #
368
    #   If we have a relative path then prepend the current directory
369
    #   An absolute path is:
370
    #           /aaa/aa/aa
371
    #       or  c:/aaa/aa/aa
372
    #
373
    $dpath = $Cwd . '/' . $dpath
374
        unless ( $dpath =~ m~^/|\w:/~  );
375
    $dpath =~ s~//~/~g;
376
 
377
    #
378
    #   Walk the bits and remove ".." directories
379
    #       Done by pushing non-.. elements and poping last entry for .. elements.
380
    #   Have a leading "/" which is good.
381
    #
382
    foreach ( split ( '/', $dpath ) )
383
    {
384
        next if ( $_ eq '.' );
385
        unless ( $_ eq '..' )
386
        {
387
            push @result, $_;
388
        }
389
        else
390
        {
391
            Error ("Bad Pathname: $dpath")
392
                if ( $#result <= 0 );
393
            pop @result;
394
        }
395
    }
396
 
397
    #
398
    #   Create a nice directory name again.
399
    #
400
    return join ( '/', @result );
401
}
402
 
403
#-------------------------------------------------------------------------------
404
# Function        : FullPath
405
#
406
# Description     : Return the absolute path to the file - with driver letter
407
#                   Add the current directory if the path is absolute
408
#                   Clean up xxx/.. constructs
409
#
410
#                   If an absolute path is provided then it will simply be
411
#                   cleaned up.
412
#
413
# Inputs          : Source file path
414
#
415
# Returns         : Cleaned abs path
416
#
417
sub FullPath
418
{
419
    my $path = AbsPath (@_ );
229 dpurdie 420
    $path = $CwdDrive . $path unless ( $path =~ m~^\w:~  );
227 dpurdie 421
    return $path;
422
}
423
 
424
#-------------------------------------------------------------------------------
257 dpurdie 425
# Function        : TruePath
426
#
427
# Description     : Returns a case correct pathname
428
#                   Really only applicable to windows, under unix it returns
429
#                   its input path.
430
#
431
#                   Maintains a cache to speed up processing
432
#
433
# Inputs          : Confused path (Absolute with a driver letter)
434
#
435
# Returns         : Case Correct Path : Windows
436
#                   Input Path : Non Windows
437
#
438
my %TruePathCache;
439
my %DirRead;
440
sub TruePath
441
{
442
    my ($path) = @_;
443
    $path =~ tr~\\/~/~s;
444
 
445
    #
446
    #   On Unix systems the path is case sensitive to start with
447
    #   Can't get it wrong - can't do anything.
448
    #
449
    return $path if ( $isUnix );
450
 
451
    #
452
    #   If the path does not exist at all then return the user input
453
    #   Assume that the user will handle this later
454
    #
455
    unless ( -e $path )
456
    {
457
        Warning ("TruePath given invalid path: $path");
458
        return $path;
459
    }
460
 
461
    #
462
    #   Look in the cache - have we seen this before
463
    #
464
    if ( exists $TruePathCache{lc($path)} )
465
    {
466
        Verbose( "TruePath Cache Hit: $path");
467
        return $TruePathCache{lc($path)};
468
    }
469
 
470
    #
471
    #   Split the directory into components
472
    #
473
    my $TrueComponent = '';
474
    my @components = split ('/', $path );
475
    foreach my $elem ( @components )
476
    {
477
        Debug ("Process: $elem in $TrueComponent");
478
        my $tag;
479
        #
480
        #   Handle driver letter
481
        #
482
        if ( $elem =~ m~^[a-zA-Z]:$~ )
483
        {
484
            $elem = uc($elem);
485
            $TrueComponent = $elem;
486
 
487
            $tag = lc($TrueComponent);
488
            $TruePathCache{$tag} = $elem;
489
            Debug ("     Add: $elem");
490
            next;
491
        }
492
 
493
        #
494
        #   Ensure that we have read in containing directory
495
        #   Note: Append / to ensure we read root directories correctly
496
        #
497
        $TrueComponent .= '/';
498
        unless ( $DirRead{ $TrueComponent }  )
499
        {
500
            Debug ("Reading: $TrueComponent");
501
            opendir (TP, $TrueComponent ) or Error ("Cannot open $TrueComponent");
502
            my @dirlist = readdir TP;
503
            close TP;
504
            $DirRead {$TrueComponent } = 1;
505
 
506
            #
507
            #   Add cache entries for each path in the directory
508
            #
509
            foreach my $dir ( @dirlist )
510
            {
511
                next if ( $dir eq '.' );
512
                next if ( $dir eq '..' );
513
                my $fullpath = $TrueComponent . $dir;
514
                Debug ("     Add: $fullpath");
515
                $TruePathCache{lc($fullpath)} = $fullpath;
516
            }
517
        }
518
 
519
        #
520
        #   Now that we have populated the cache with data from the directory
521
        #   we can expect to find our desired entry in the cache.
522
        #
523
        $tag = lc($TrueComponent . $elem );
524
        if ( exists $TruePathCache{ $tag } )
525
        {
526
            $TrueComponent = $TruePathCache{ $tag };
527
        }
528
        else
529
        {
530
            DebugDumpData ("Cache", \%TruePathCache);
531
            Error ("TruePath Internal error. File may have been deleted: $tag");
532
        }
533
        Debug ("Have: $TrueComponent");
534
    }
535
 
536
    Verbose ("TruePath: $TrueComponent");
537
    return $TrueComponent;
538
}
539
 
540
#-------------------------------------------------------------------------------
227 dpurdie 541
# Function        : CleanPath
542
#
543
# Description     : Cleanup a path
544
#                   Remove xxx/.. constructs
545
#
546
# Note            : Will not perform error detection on badly formed
547
#                   absolute paths.
548
#
549
# Inputs          : Source file path
550
#
551
# Returns         : Clean absolute or relative path
552
#
553
#
554
sub CleanPath
555
{
556
    my ($dpath) = @_;
557
    my @result;
558
    Debug("CleanPath: Source: $dpath");
559
 
560
    #
561
    #   Cleanup the the user input. Remove double delimiters and ensure there
562
    #   is no trailing delemiter
563
    #
245 dpurdie 564
    $dpath =~ s~/+~/~g;
227 dpurdie 565
    $dpath =~ s~/$~~g;
566
 
567
    #
568
    #   Walk the bits and remove "xxx/.." directories
569
    #
570
    foreach ( split ( '/', $dpath ) )
571
    {
572
        if ( $_ ne '..' || $#result < 0 )
573
        {
574
            push @result, $_;
575
        }
576
        else
577
        {
578
            if ( $#result >= 0 )
579
            {
580
                my $last = pop @result;
581
                push (@result, $last, $_)
582
                    if ($last eq '..' || $last eq '');
583
            }
584
        }
585
    }
586
 
587
    my $result = join ( '/', @result );
588
    Debug("CleanPath: Result: $result");
589
    return $result;
590
}
591
 
592
#-------------------------------------------------------------------------------
593
# Function        : StripDrive
594
#
595
# Description     : Strip any leading drive speification
596
#
597
# Inputs          : $fname          - Path to process
598
#
599
# Returns         : Path, with drive letter stripped
600
#                   Will do nothing on Unix systems
601
#
602
sub StripDrive
603
{
604
    my( $fname ) = @_;                          # Full name
605
 
606
    $fname =~ s/^[A-Za-z]://g                  # leading drive spec
607
        if ( ! $isUnix );
608
    return $fname;
609
}
610
 
611
#-------------------------------------------------------------------------------
612
# Function        : StripDir
613
#
614
# Description     : Strip directory (returns file, including extension)
615
#
616
# Inputs          : $fname          - Path to process
617
#
618
# Returns         : filename + extension
619
#
620
sub StripDir
621
{
622
    my( $fname ) = @_;                          # Full name
623
    my( $idx );
624
 
625
    if (($idx = rindex($fname, "/")) == -1) {
626
        if (($idx = rindex($fname, "\\")) == -1) {
627
            return $fname;                      # No path ...
628
        }
629
    }
630
    return substr($fname, $idx+1, 512);
631
}
632
 
633
#-------------------------------------------------------------------------------
634
# Function        : StripExt
635
#
636
# Description     : Strip extension (return basename, plus any dir)
637
#
638
# Inputs          : $fname          - Path to process
639
#
640
# Returns         : basename, plus any dir
641
#                   Simply removes one extension
642
#
643
sub StripExt
644
{
645
    my( $fname ) = @_;
646
 
647
    $fname =~ s/(\S+)(\.\S+)/$1/;               # strip out trailing '.<ext>'
648
    return ($fname);
649
}
650
 
651
#-------------------------------------------------------------------------------
652
# Function        : StripFile
653
#
654
# Description     : Strip filename (returns extension)
655
#
656
# Inputs          : $fname          - Path to process
657
#
658
# Returns         : extension
659
#                   Will return an empty string if the input does not have an
660
#                   extension.
661
#
662
sub StripFile
663
{
664
    my( $fname ) = @_;
665
 
666
    $fname =~ s/(\S+)(\.\S+)/$2/;               # Strip out items before '.<ext>'
667
    return ("")                                 # No extension
668
        if ("$fname" eq "@_");
669
    return ($fname);
670
}
671
 
672
#-------------------------------------------------------------------------------
673
# Function        : StripFileExt
674
#
675
# Description     : Strip filename and ext (returns dir)
676
#
677
# Inputs          : $fname          - Path to process
678
#
679
# Returns         : Directory of a file path
680
#
681
 
682
 
683
#   StripFileExt( path ) ---
684
#       Strip filename and ext (returns dir)
685
#..
686
 
687
sub StripFileExt
688
{
689
    my( $fname ) = @_;                          # Full name
690
    my( $idx );
691
    my $dir;
692
 
693
    if (($idx = rindex($fname, "/")) == -1) {
694
        if (($idx = rindex($fname, "\\")) == -1) {
695
            return "";                          # No path ...
696
        }
697
    }
698
 
699
    return substr($fname, 0, $idx);
700
}
701
 
702
#-------------------------------------------------------------------------------
703
# Function        : StripDirExt
704
#
705
# Description     : Strip the directory and extension from a file
706
#                   Returning the base file. Optionally replace the extension
707
#                   with a user value
708
#
709
# Inputs          : Full path name
710
#                   Optional extension to be replaced
711
#
712
# Returns         :
713
#
714
sub StripDirExt
715
{
716
    my ($fname, $ext ) = (@_, '');
717
    $fname =~ s~.*[/\\]~~;                      # Strip directory
718
    $fname =~ s/\.[^.]+$/$ext/;
719
    return $fname;
720
}
721
 
722
 
723
#-------------------------------------------------------------------------------
724
# Function        : CleanDirName
725
#
726
# Description     : Clean up a directory path string
727
#                       1) Remove multiple //
728
#                       2) Remove multiple /./
729
#                       2) Remove leading ./
730
#                       3) Remove trailing /
731
#                       4) Remove /xxxx/../
732
#
733
# Inputs          : A dirty directory path
734
#
735
# Returns         : A clean directory path
736
#
737
sub CleanDirName
738
{
739
    my ( $dir ) = @_;
740
    $dir =~ s~//~/~g;                   # Kill multiple //
741
    $dir =~ s~/\./~/~g;                 # Kill multiple /./
742
    $dir =~ s~^\./~~;                   # Kill leading ./
743
    $dir = '.' unless ( $dir );         # Ensure we have a path
744
 
745
    #
746
    #   Remove /xxxxx/../ bits
747
    #
748
    unless ( $dir =~ m~^\.\./~  )
749
    {
750
        while ( $dir =~ s~
751
                        (^|/)               # Allow for stings that may not start with a /
752
                        [^/]+/\.\.          # xxxxx/.., where xxxx is anything other than a /
753
                        (/|$)               # Allow for strings ending with /..
754
                        ~$1~x               # Replace with the start character
755
              )
756
        {
757
            last if ( $dir =~ m~^\.\./~ );  # Too far. Stop now !
758
        }
759
    }
760
 
761
    $dir =~ s~/$~~;                     # No trailing /
762
    $dir =~ s~/\.$~~;                   # No trailing /.
763
    return $dir;
764
}
765
 
766
#-------------------------------------------------------------------------------
767
# Function        : DisplayPath
768
#
769
# Description     : Cleanup a path for display purposes
770
#                   Useful under windows to provide paths with \ that can be
771
#                   cut and pasted.
772
#
773
#                   If cygwin is located in the environment, then this function
774
#                   will not convert / to \.
775
#
776
# Inputs          : A path to modify
777
#
778
# Returns         : Modified path
779
#
780
sub DisplayPath
781
{
782
    my ($path) = @_;
783
    if ( ! $isUnix && ! $isCygWin )
784
    {
785
        $path =~ s~/~\\~g;
786
    }
787
    return $path;
788
}
789
 
790
1;
791
 
792