Subversion Repositories DevTools

Rev

Rev 6387 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
227 dpurdie 1
########################################################################
6177 dpurdie 2
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
227 dpurdie 3
#
4
# Module name   : jats.sh
5
# Module type   : Makefile system
6
# Compiler(s)   : n/a
7
# Environment(s): jats
8
#
9
# Description   : A class to Locate Files
10
#                 Supportes:
11
#                   Wilcards
12
#                   Exclusions
13
#                   Recursion
14
#
15
# Usage:
16
#               my $search = JatsLocateFiles::new('JatsLocateFiles');
17
#               my $search = JatsLocateFiles->new();
18
#                  $search->recurse ( );
7418 dpurdie 19
#                  $search->filter_in ( );          -- Supports multiple arguments
20
#                  $search->filter_in_re ( );       -- Supports multiple arguments
21
#                  $search->filter_out ( );         -- Supports multiple arguments
22
#                  $search->filter_out_re ( );      -- Supports multiple arguments
227 dpurdie 23
#                  $search->base_dir ()
24
#                  $search->has_filter ( );
7418 dpurdie 25
#                  $search->has_in_filter ( );
26
#                  $search->has_out_filter ( );
227 dpurdie 27
#                  $search->full_path ( );
28
#                  $search->dirs_only ( );
379 dpurdie 29
#                  $search->dirs_too();             # Dirs have '/' appended
227 dpurdie 30
#                  $search->search ( );
31
#                  $search->results ( );
7418 dpurdie 32
#                  $search->set_list ( arrayRef );
227 dpurdie 33
#
34
#......................................................................#
35
 
255 dpurdie 36
require 5.006_001;
227 dpurdie 37
use strict;
38
use warnings;
39
 
40
package JatsLocateFiles;
41
use JatsError;
42
use File::Find;
43
use ArrayHashUtils;
44
 
45
#-------------------------------------------------------------------------------
46
# Function        : new
47
#
48
# Description     : Create a new instance of a searcher
49
#
50
# Inputs          : class           - Class Name
51
#                   options
52
#                   Hash of user options
53
#                   Useful keys are:
54
#                       'recurse'
55
#                       'base_dir'
56
#                       'full_path'
57
#                       'dirs_only'
58
#
59
# Returns         : A reference to the new object of the specified class
60
#
61
sub new {
62
    my $class = shift;
63
    my $self  = {};
64
    $self->{full_path} = 0;
65
    $self->{recurse}  = 0;
66
    $self->{dirs_only}  = 0;
263 dpurdie 67
    $self->{dirs_too} = 0;
227 dpurdie 68
    $self->{exclude}  = [];
69
    $self->{include}  = [];
70
    $self->{base_dir} = undef;
7418 dpurdie 71
    $self->{search_list} = undef;
227 dpurdie 72
    $self->{results}  = [];
73
    bless ($self, $class);
74
 
75
    #
76
    #   Process user arguments.
77
    #   These are treated as options. Leading '--' is optional
78
    #
79
    foreach ( @_ )
80
    {
81
        my $opt = '--' . $_;
82
        $opt =~ s~^----~--~;
83
        $self->option ($opt) || Error( "JatsLocateFiles:new. Unknown initialiser: $_");
84
    }
85
    return $self;
86
}
87
 
88
#-------------------------------------------------------------------------------
89
# Function        : recurse
90
#                   filter_in
91
#                   filter_in_re
92
#                   filter_out
93
#                   filter_out_re
94
#                   base_dir
95
#                   has_filter
7418 dpurdie 96
#                   has_in_filter;
97
#                   has_out_filter;
227 dpurdie 98
#                   results
99
#                   full_path
100
#                   dirs_only
263 dpurdie 101
#                   dirs_too                    - Include dirs in the search
7418 dpurdie 102
#                   set_list
227 dpurdie 103
#
104
# Description     : Accessor functions
105
#
106
# Inputs          : class
107
#                   One argument (optional)
108
#
109
# Returns         : Current value of the data item
110
#
111
sub recurse
112
{
113
    my $self = shift;
114
    if (@_) { $self->{recurse} = shift }
115
    return $self->{recurse};
116
}
117
 
118
sub full_path
119
{
120
    my $self = shift;
121
    if (@_) { $self->{full_path} = shift }
122
    return $self->{full_path};
123
}
124
 
125
sub dirs_only
126
{
127
    my $self = shift;
128
    if (@_) { $self->{dirs_only} = shift }
129
    return $self->{dirs_only};
130
}
131
 
263 dpurdie 132
sub dirs_too
133
{
134
    my $self = shift;
135
    if (@_) { $self->{dirs_too} = shift }
136
    return $self->{dirs_too};
137
}
138
 
227 dpurdie 139
sub filter_in
140
{
141
    my $self = shift;
7418 dpurdie 142
    foreach (@_) { push @{$self->{include}}, glob2pat( $_ ) };
227 dpurdie 143
    return $self->{include};
144
}
145
 
146
sub filter_in_re
147
{
148
    my $self = shift;
7418 dpurdie 149
    foreach (@_) { push @{$self->{include}}, $_ };
227 dpurdie 150
    return $self->{include};
151
}
152
 
153
sub filter_out
154
{
155
    my $self = shift;
7418 dpurdie 156
    foreach (@_) { push @{$self->{exclude}}, glob2pat( $_ ) };
227 dpurdie 157
    return $self->{exclude};
158
}
159
 
160
sub filter_out_re
161
{
162
    my $self = shift;
7418 dpurdie 163
    foreach (@_) { push @{$self->{exclude}}, $_ };
227 dpurdie 164
    return $self->{exclude};
165
}
166
 
167
sub base_dir
168
{
169
    my $self = shift;
170
    if (@_) { $self->{base_dir} = shift }
171
    return $self->{base_dir};
172
}
173
 
174
sub has_filter
175
{
176
    my $self = shift;
177
    return ( ( @{$self->{include}} || @{$self->{exclude}} ) );
178
}
179
 
7418 dpurdie 180
sub has_in_filter
181
{
182
    my $self = shift;
183
    return ( @{$self->{include}} );
184
}
185
 
186
sub has_out_filter
187
{
188
    my $self = shift;
189
    return ( @{$self->{exclude}} );
190
}
191
 
227 dpurdie 192
sub results
193
{
194
    my $self = shift;
195
    $self->{results} = \() unless ( exists $self->{results} );
196
    return wantarray ? @{$self->{results}} : 1 + $#{$self->{results}};
197
}
198
 
7418 dpurdie 199
sub set_list
200
{
201
    my $self = shift;
202
    if (@_) { $self->{search_list} = shift};
203
    Error('JatsLocateFiles:set_list expects an ARRAY ref') unless (ref $self->{search_list} eq 'ARRAY' );
204
 
205
}
206
 
227 dpurdie 207
#-------------------------------------------------------------------------------
208
# Function        : option
209
#
210
# Description     : Function to simplify the processing of search arguments
211
#                   Given an argument this function will act on it or
212
#                   return false
213
#
214
# Inputs          : option          - One possible standard search option
215
#
216
# Returns         : True            - Option is a search option and its been
217
#                                     processed
218
#
219
sub option
220
{
221
    my ($self, $opt) = @_;
222
    my $result = 1;
223
 
224
    if ( $opt =~ m/^--Dir=(.+)/ ) {
225
        $self->base_dir ($1);
226
 
227
    } elsif ( $opt =~ m/^--Recurse/ ) {
228
        $self->recurse(1);
229
 
230
    } elsif ( $opt =~ m/^--NoRecurse/ ) {
331 dpurdie 231
        $self->recurse(0);
227 dpurdie 232
 
4094 dpurdie 233
    } elsif ( $opt =~ m/^--NoFullPath/ ) {              # Default. Returned items relative to search base        
227 dpurdie 234
        $self->full_path(0);
235
 
4094 dpurdie 236
    } elsif ( $opt =~ m/^--FullPath/ ) {                # Prepend base path to returned items
227 dpurdie 237
        $self->full_path(1);
238
 
4094 dpurdie 239
    } elsif ( $opt =~ m/^--FileListOnly/ ) {            # Default. Return files only 
227 dpurdie 240
        $self->full_path(0);
241
        $self->dirs_only(0);
242
 
4094 dpurdie 243
    } elsif ( $opt =~ m/^--DirListOnly/ ) {             # Return dirs that contain selected files
227 dpurdie 244
        $self->dirs_only(1);
245
 
4094 dpurdie 246
    } elsif ( $opt =~ m/^--DirsToo/ ) {                 # Return files and directories
263 dpurdie 247
        $self->dirs_too(1);
248
 
4094 dpurdie 249
    } elsif ( $opt =~ m/^--DirsOnly/ ) {                # Ignore non-directory elements    
250
        $self->dirs_only(2);
251
        $self->dirs_too(1);
252
 
227 dpurdie 253
    } elsif ( $opt =~ m/^--FilterIn=(.+)/ ) {
254
        $self->filter_in ( $1 );
255
 
256
    } elsif ( $opt =~ m/^--FilterInRe=(.+)/ ) {
257
        $self->filter_in_re ( $1 );
258
 
259
    } elsif ( $opt =~ m/^--FilterOut=(.+)/ ) {
260
        $self->filter_out ( $1 );
261
 
262
    } elsif ( $opt =~ m/^--FilterOutRe=(.+)/ ) {
263
        $self->filter_out_re ( $1 );
264
 
7418 dpurdie 265
    } elsif ( $opt =~ m/^--SetList=(.+)/ ) {
266
        $self->set_list ( $1 );
267
 
227 dpurdie 268
    } else {
269
        $result = 0;
270
 
271
    }
272
 
273
    return $result;
274
}
275
 
276
#-------------------------------------------------------------------------------
277
# Function        : search
278
#
279
# Description     : This function performs the search for files as specified
280
#                   by the arguments already provided
281
#
282
# Inputs          : base_dir (Optional)
283
#
284
# Returns         : List of files that match the search criteria
285
#                   The base directory is not prepended
286
#                   The list is a simple list of file names
287
#
288
 
289
my @search_list;             # Must be global to avoid closure problems
290
my $search_len;
263 dpurdie 291
my $search_base_dir;
292
my $search_dirs_too;
4094 dpurdie 293
my $search_no_files;
227 dpurdie 294
 
295
sub search
296
{
297
    my $self = shift;
298
    $self->{base_dir} = $_[0] if (defined $_[0] );
299
    $self->{results} = ();
300
 
301
    #
302
    #   Ensure user has provided enough info
303
    #
7418 dpurdie 304
    Error ("JatsLocateFiles: No base directory provided") unless ( $self->{base_dir} || $self->{search_list} );
227 dpurdie 305
 
306
    @search_list = ();
7418 dpurdie 307
    if ( $self->{base_dir} ) {
308
        #
309
        #   Clean up the user dir. Remove any trailing / as we will be adding it back
310
        #
311
        $self->{base_dir} =~ s~/*$~~g;
227 dpurdie 312
 
7418 dpurdie 313
        #
314
        #   Init recursion information
315
        #   Needed to avoid closure interactions
316
        #
317
        $search_len = 1 + length( $self->{base_dir} );
318
 
319
        #
320
        #   Create a list of candidate files
321
        #   If we are recursing the subtree, then this is a little harder
322
        #   If we are not recursing then we can't simply glob the directory as
323
        #   not all files are processed.
324
        #
325
        #   Will end up with a list of files that don't include $dir
326
        #
327
        if ( -d $self->{base_dir} )
227 dpurdie 328
        {
7418 dpurdie 329
            if ( $self->{recurse} )
227 dpurdie 330
            {
7418 dpurdie 331
                $search_dirs_too = $self->{dirs_too};
332
                $search_base_dir = $self->{base_dir};
333
                $search_no_files = ($self->{dirs_only} == 2);
334
                sub find_file_wanted
335
                {
336
                    return if ( $search_no_files && ! -d $_ );              # skip if current is not a dir (assume file) and we only match dirs
337
                    return if ( !$search_dirs_too && -d $_ );               # skip if current is dir and we are not including dirs
338
                    return if ( $search_base_dir eq $File::Find::name );    # skip if current is base_dir as we dont include it
339
                    my $file = $File::Find::name;
340
                    $file .= '/' if ( -d $_ && ! $search_no_files);
341
                    push @search_list, substr($file, $search_len );
342
                }
343
 
344
                #
345
                #       Under Unix we need to follow symbolic links, but Perl's
346
                #       Find:find does not work with -follow under windows if the source
347
                #       path contains a drive letter.
348
                #
349
                #       Solution. Only use follow under non-windows systems.
350
                #                 Works as Windows does not have symlinks (yet).
351
                #
352
                my $follow_opt =  ($ENV{GBE_UNIX}  > 0);
353
 
354
                File::Find::find( {wanted => \&find_file_wanted, follow_fast => $follow_opt, follow_skip => 2 }, $self->{base_dir} );
227 dpurdie 355
            }
7418 dpurdie 356
            else
227 dpurdie 357
            {
7418 dpurdie 358
                local *DIR ;
359
                opendir DIR, $self->{base_dir} || die ("Cannot open $self->{base_dir}");
360
                foreach ( readdir( DIR ) )
361
                {
362
                    next if /^\Q.\E$/;
363
                    next if /^\Q..\E$/;
364
                    next if ( !$self->{dirs_too} && -d "$self->{base_dir}/$_" );
365
                    push @search_list, $_;
366
                }
367
                closedir DIR;
227 dpurdie 368
            }
369
        }
370
    }
7418 dpurdie 371
    else
372
    {
373
        # User has provided the search list to be processed
374
        $self->{full_path} = 0;
375
        @search_list = @{$self->{search_list}};
376
    }
227 dpurdie 377
 
378
 
379
    my @result;
380
    if ( @{$self->{include}} || @{$self->{exclude}} )
381
    {
382
        #
383
        #   Filtering is present
384
        #   Apply the filterin rules and then the filter out rules
385
        #   If no filter-in rules, then assume that all files are allowed in and
386
        #   simply apply the filter-out rules.
387
        #
388
        my @patsin  = map { qr/$_/ } @{$self->{include}};
389
        my @patsout = map { qr/$_/ } @{$self->{exclude}};
390
 
391
    #    map { print "Include:$_\n"; } @{$self->{include}};
392
    #    map { print "Exclude:$_\n"; } @{$self->{exclude}};
393
 
394
 
395
        file:
396
        foreach my $rfile ( @search_list )
397
        {
398
            my $file = '/' . $rfile;
399
            if ( @{$self->{include}} )
400
            {
401
                my $in = 0;
402
                for my $pat (@patsin)
403
                {
404
                    if ( $file =~ /$pat/ )
405
                    {
406
                        $in = 1;
407
                        last;
408
                    }
409
                }
410
    #print "------- Not included $file\n" unless $in;
411
                next unless ( $in );
412
            }
413
 
414
            for my $pat (@patsout)
415
            {
416
    #print "------- REJECT $file :: $pat \n" if ( $file =~ /$pat/ );
417
                next file if ( $file =~ /$pat/ );
418
            }
419
            push @result, $rfile;
420
        }
421
    }
422
    else
423
    {
424
        @result = @search_list ;
425
    }
426
    @search_list = ();
7418 dpurdie 427
    $self->{results} = [];
227 dpurdie 428
 
429
    #
430
    #   Reattach the base directory, if required
431
    #       full_path  : Prepend full path
432
    #       dirs_only  : return list of dirs that have files
433
    #   Extract dirs only
434
    #
6387 dpurdie 435
    my %fileList;
227 dpurdie 436
    foreach  ( @result )
437
    {
438
        my $path;
6387 dpurdie 439
        if ( $self->{full_path} ) {
227 dpurdie 440
            $path = $self->{base_dir} . '/' . $_;
6387 dpurdie 441
        } else {
227 dpurdie 442
            $path = $_;
443
        }
444
 
6387 dpurdie 445
        if ( $self->{dirs_only} == 1 ) {
227 dpurdie 446
            $path =~ s~/[^/]*$~~;
447
        }
448
 
6387 dpurdie 449
        #
450
        #   Add to results - if not already present
451
        #   Dont use UniquePush - its slow over large lists
452
        #
453
        unless (exists $fileList{$path} ) {
454
            push( @{$self->{results}}, $path);
455
            $fileList{$path} = 1;
456
        }
227 dpurdie 457
    }
458
#DebugDumpData ("Search", $self);
459
    return @{$self->{results}};
460
}
461
 
462
#-------------------------------------------------------------------------------
463
# Function        : glob2pat
464
#
465
# Description     : Convert four shell wildcard characters into their equivalent
466
#                   regular expression; all other characters are quoted to
467
#                   render them literals.
468
#
469
# Inputs          : Shell style wildcard pattern
470
#
471
# Returns         : Perl RE
472
#
473
 
474
sub glob2pat
475
{
476
    my $globstr = shift;
477
    $globstr =~ s~^/~~;
478
    my %patmap = (
479
        '*' => '[^/]*',
480
        '?' => '[^/]',
481
        '[' => '[',
482
        ']' => ']',
273 dpurdie 483
        '-' => '-',
227 dpurdie 484
    );
485
    $globstr =~ s{(.)} { $patmap{$1} || "\Q$1" }ge;
486
    return '/' . $globstr . '$';
487
}
488
 
489
#------------------------------------------------------------------------------
490
1;