summaryrefslogtreecommitdiff
path: root/systems/texlive/tlnet/tlpkg/tlperl/site/lib/File/Listing.pm
blob: b94c5b3910ced9c4dad0b7218996265a4528f415 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
package File::Listing;

use strict;
use warnings;
use Carp ();
use HTTP::Date qw(str2time);
use base qw( Exporter );

# ABSTRACT: Parse directory listing
our $VERSION = '6.14'; # VERSION

sub Version { $File::Listing::VERSION; }

our @EXPORT = qw(parse_dir);

sub parse_dir ($;$$$)
{
   my($dir, $tz, $fstype, $error) = @_;

   $fstype ||= 'unix';
   $fstype = "File::Listing::" . lc $fstype;

   my @args = $_[0];
   push(@args, $tz) if(@_ >= 2);
   push(@args, $error) if(@_ >= 4);

   $fstype->parse(@args);
}


sub line { Carp::croak("Not implemented yet"); }
sub init { } # Dummy sub


sub file_mode ($)
{
    Carp::croak("Input to file_mode() must be a 10 character string.")
        unless length($_[0]) == 10;

    # This routine was originally borrowed from Graham Barr's
    # Net::FTP package.

    local $_ = shift;
    my $mode = 0;
    my($type);

    s/^(.)// and $type = $1;

    # When the set-group-ID bit (file mode bit 02000) is set, and the group
    # execution bit (file mode bit 00020) is unset, and it is a regular file,
    # some implementations of `ls' use the letter `S', others use `l' or `L'.
    # Convert this `S'.

    s/[Ll](...)$/S$1/;

    while (/(.)/g) {
        $mode <<= 1;
        $mode |= 1 if $1 ne "-" &&
                      $1 ne "*" &&
                      $1 ne 'S' &&
                      $1 ne 'T';
    }

    $mode |= 0004000 if /^..s....../i;
    $mode |= 0002000 if /^.....s.../i;
    $mode |= 0001000 if /^........t/i;

    # De facto standard definitions. From 'stat.h' on Solaris 9.

    $type eq "p" and $mode |= 0010000 or        # fifo
    $type eq "c" and $mode |= 0020000 or        # character special
    $type eq "d" and $mode |= 0040000 or        # directory
    $type eq "b" and $mode |= 0060000 or        # block special
    $type eq "-" and $mode |= 0100000 or        # regular
    $type eq "l" and $mode |= 0120000 or        # symbolic link
    $type eq "s" and $mode |= 0140000 or        # socket
    $type eq "D" and $mode |= 0150000 or        # door
      Carp::croak("Unknown file type: $type");

    $mode;
}


sub parse
{
   my($pkg, $dir, $tz, $error) = @_;

   # First let's try to determine what kind of dir parameter we have
   # received.  We allow both listings, reference to arrays and
   # file handles to read from.

   if (ref($dir) eq 'ARRAY') {
       # Already split up
   }
   elsif (ref($dir) eq 'GLOB') {
       # A file handle
   }
   elsif (ref($dir)) {
      Carp::croak("Illegal argument to parse_dir()");
   }
   elsif ($dir =~ /^\*\w+(::\w+)+$/) {
      # This scalar looks like a file handle, so we assume it is
   }
   else {
      # A normal scalar listing
      $dir = [ split(/\n/, $dir) ];
   }

   $pkg->init();

   my @files = ();
   if (ref($dir) eq 'ARRAY') {
       for (@$dir) {
           push(@files, $pkg->line($_, $tz, $error));
       }
   }
   else {
       local($_);
       while (my $line = <$dir>) {
           chomp $line;
           push(@files, $pkg->line($line, $tz, $error));
       }
   }
   wantarray ? @files : \@files;  ## no critic (Freenode::Wantarray)
}



package File::Listing::unix;

use HTTP::Date qw(str2time);

our @ISA = qw(File::Listing);

# A place to remember current directory from last line parsed.
our $curdir;

sub init
{
    $curdir = '';
}


sub line
{
    shift; # package name
    local($_) = shift;
    my($tz, $error) = @_;

    s/\015//g;
    #study;

    my ($kind, $size, $date, $name);
    if (($kind, $size, $date, $name) =
        /^([\-\*FlrwxsStTdD]{10})                 # Type and permission bits
         .*                                       # Graps
         \D(\d+)                                  # File size
         \s+                                      # Some space
         (\w{3}\s+\d+\s+(?:\d{1,2}:\d{2}|\d{4})|\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})  # Date
         \s+                                      # Some more space
         (.*)$                                    # File name
        /x )

    {
        return if $name eq '.' || $name eq '..';
        $name = "$curdir/$name" if length $curdir;
        my $type = '?';
        if ($kind =~ /^l/ && $name =~ /(.*) -> (.*)/ ) {
            $name = $1;
            $type = "l $2";
        }
        elsif ($kind =~ /^[\-F]/) { # (hopefully) a regular file
            $type = 'f';
        }
        elsif ($kind =~ /^[dD]/) {
            $type = 'd';
            $size = undef;  # Don't believe the reported size
        }
        return [$name, $type, $size, str2time($date, $tz),
              File::Listing::file_mode($kind)];

    }
    elsif (/^(.+):$/ && !/^[dcbsp].*\s.*\s.*:$/ ) {
        my $dir = $1;
        return () if $dir eq '.';
        $curdir = $dir;
        return ();
    }
    elsif (/^[Tt]otal\s+(\d+)$/ || /^\s*$/) {
        return ();
    }
    elsif (/not found/    || # OSF1, HPUX, and SunOS return
             # "$file not found"
             /No such file/ || # IRIX returns
             # "UX:ls: ERROR: Cannot access $file: No such file or directory"
                               # Solaris returns
             # "$file: No such file or directory"
             /cannot find/     # Windows NT returns
             # "The system cannot find the path specified."
             ) {
        return () unless defined $error;
        &$error($_) if ref($error) eq 'CODE';
        warn "Error: $_\n" if $error eq 'warn';
        return ();
    }
    elsif ($_ eq '') {       # AIX, and Linux return nothing
        return () unless defined $error;
        &$error("No such file or directory") if ref($error) eq 'CODE';
        warn "Warning: No such file or directory\n" if $error eq 'warn';
        return ();
    }
    else {
        # parse failed, check if the dosftp parse understands it
        File::Listing::dosftp->init();
        return(File::Listing::dosftp->line($_,$tz,$error));
    }

}



package File::Listing::dosftp;

use HTTP::Date qw(str2time);

our @ISA = qw(File::Listing);

# A place to remember current directory from last line parsed.
our $curdir;



sub init
{
    $curdir = '';
}


sub line
{
    shift; # package name
    local($_) = shift;
    my($tz, $error) = @_;

    s/\015//g;

    my ($date, $size_or_dir, $name, $size);

    # 02-05-96  10:48AM                 1415 src.slf
    # 09-10-96  09:18AM       <DIR>          sl_util
    if (($date, $size_or_dir, $name) =
        /^(\d\d-\d\d-\d\d\s+\d\d:\d\d\wM)         # Date and time info
         \s+                                      # Some space
         (<\w{3}>|\d+)                            # Dir or Size
         \s+                                      # Some more space
         (.+)$                                    # File name
        /x )
    {
        return if $name eq '.' || $name eq '..';
        $name = "$curdir/$name" if length $curdir;
        my $type = '?';
        if ($size_or_dir eq '<DIR>') {
            $type = "d";
            $size = ""; # directories have no size in the pc listing
        }
        else {
            $type = 'f';
            $size = $size_or_dir;
        }
        return [$name, $type, $size, str2time($date, $tz), undef];
    }
    else {
        return () unless defined $error;
        &$error($_) if ref($error) eq 'CODE';
        warn "Can't parse: $_\n" if $error eq 'warn';
        return ();
    }

}



package File::Listing::vms;
our @ISA = qw(File::Listing);

package File::Listing::netware;
our @ISA = qw(File::Listing);



package File::Listing::apache;

our @ISA = qw(File::Listing);


sub init { }


sub line {
    shift; # package name
    local($_) = shift;
    my($tz, $error) = @_; # ignored for now...

    s!</?t[rd][^>]*>! !g;  # clean away various table stuff
    if (m!<A\s+HREF=\"([^?\"]+)\">.*</A>.*?(\d+)-([a-zA-Z]+|\d+)-(\d+)\s+(\d+):(\d+)\s+(?:([\d\.]+[kMG]?|-))!i) {
        my($filename, $filesize) = ($1, $7);
        my($d,$m,$y, $H,$M) = ($2,$3,$4,$5,$6);
        if ($m =~ /^\d+$/) {
            ($d,$y) = ($y,$d) # iso date
        }
        else {
            $m = _monthabbrev_number($m);
        }

        $filesize = 0 if $filesize eq '-';
        if ($filesize =~ s/k$//i) {
            $filesize *= 1024;
        }
        elsif ($filesize =~ s/M$//) {
            $filesize *= 1024*1024;
        }
        elsif ($filesize =~ s/G$//) {
            $filesize *= 1024*1024*1024;
        }
        $filesize = int $filesize;

        require Time::Local;
        my $filetime = Time::Local::timelocal(0,$M,$H,$d,$m-1,_guess_year($y));
        my $filetype = ($filename =~ s|/$|| ? "d" : "f");
        return [$filename, $filetype, $filesize, $filetime, undef];
    }

    # the default listing doesn't include timestamps or file sizes
    # but we don't want to grab navigation links, so we ignore links
    # that have a non-trailing slash / character or ?
    elsif(m!<A\s+HREF=\"([^?/\"]+/?)\">.*</A>!i) {
        my $filename = $1;
        my $filetype = ($filename =~ s|/$|| ? "d" : "f");
        return [$filename, $filetype, undef, undef, undef];
    }

    return ();
}


sub _guess_year {
    my $y = shift;

    # if the year is already four digit then we shouldn't do
    # anything to modify it.
    if ($y >= 1900) {
        # do nothing

    # TODO: for hysterical er historical reasons we assume 9x is in the
    # 1990s we should probably not do that, but I don't have any examples
    # where apache provides two digit dates so I am leaving this as-is
    # for now.  Possibly the right thing is to not handle two digit years.
    } elsif ($y >= 90) {
        $y = 1900+$y;
    }

    # TODO: likewise assuming 00-89 are 20xx is long term probably wrong.
    elsif ($y < 100) {
        $y = 2000+$y;
    }
    $y;
}


sub _monthabbrev_number {
    my $mon = shift;
    +{'Jan' => 1,
      'Feb' => 2,
      'Mar' => 3,
      'Apr' => 4,
      'May' => 5,
      'Jun' => 6,
      'Jul' => 7,
      'Aug' => 8,
      'Sep' => 9,
      'Oct' => 10,
      'Nov' => 11,
      'Dec' => 12,
     }->{$mon};
}


1;

__END__

=pod

=encoding UTF-8

=head1 NAME

File::Listing - Parse directory listing

=head1 VERSION

version 6.14

=head1 SYNOPSIS

 use File::Listing qw(parse_dir);
 $ENV{LANG} = "C";  # dates in non-English locales not supported
 foreach my $file (parse_dir(`ls -l`)) {
     my ($name, $type, $size, $mtime, $mode) = @$file;
     next if $type ne 'f'; # plain file
     #...
 }
 
 # directory listing can also be read from a file
 open my $listing, "zcat ls-lR.gz|";
 $dir = parse_dir($listing, '+0000');

=head1 DESCRIPTION

This module exports a single function called C<parse_dir>, which can be
used to parse directory listings.

=head1 FUNCTIONS

=head2 parse_dir

 my $dir = parse_dir( $listing );
 my $dir = parse_dir( $listing, $time_zone );
 my $dir = parse_dir( $listing, $time_zone, $type );
 my $dir = parse_dir( $listing, $time_zone, $type, $error );
 my @files = parse_dir( $listing );
 my @files = parse_dir( $listing, $time_zone );
 my @files = parse_dir( $listing, $time_zone, $type );
 my @files = parse_dir( $listing, $time_zone, $type, $error );

The first parameter (C<$listing>) is the directory listing to parse.
It can be a scalar, a reference to an array of directory lines or a
glob representing a filehandle to read the directory listing from.

The second parameter (C<$time_zone>) is the time zone to use when
parsing time stamps in the listing. If this value is undefined,
then the local time zone is assumed.

The third parameter (C<$type>) is the type of listing to assume.
Currently supported formats are C<'unix'>, C<'apache'> and
C<'dosftp'>. The default value is C<'unix'>. Ideally, the listing
type should be determined automatically.

The fourth parameter (C<$error>) specifies how unparseable lines
should be treated. Values can be C<'ignore'>, C<'warn'> or a code reference.
Warn means that the perl warn() function will be called.  If a code
reference is passed, then this routine will be called and the return
value from it will be incorporated in the listing.  The default is
C<'ignore'>.

Only the first parameter is mandatory.

 # list context
 foreach my $file (parse_dir($listing)) {
     my($name, $type, $size, $mtime, $mode) = @$file;
 }
 
 # scalar context
 my $dir = parse_dir($listing);
 foreach my $file (@$dir) {
     my($name, $type, $size, $mtime, $mode) = @$file;
 }

The return value from parse_dir() is a list of directory entries.
In a scalar context the return value is a reference to the list.
The directory entries are represented by an array consisting of:

=over 4

=item name

The name of the file.

=item type

One of: C<f> file, C<d> directory, C<l> symlink, C<?> unknown.

=item size

The size of the file.

=item time

The number of seconds since January 1, 1970.

=item mode

Bitmask a la the mode returned by C<stat>.

=back

=head1 SEE ALSO

=over 4

=item L<File::Listing::Ftpcopy>

Provides the same interface but uses XS and the parser implementation from C<ftpcopy>.

=back

=head1 AUTHOR

Original author: Gisle Aas

Current maintainer: Graham Ollis E<lt>plicease@cpan.orgE<gt>

Contributors:

Adam Kennedy

Adam Sjogren

Alex Kapranoff

Alexey Tourbin

Andreas J. Koenig

Bill Mann

Bron Gondwana

DAVIDRW

Daniel Hedlund

David E. Wheeler

David Steinbrunner

Erik Esterer

FWILES

Father Chrysostomos

Gavin Peters

Graeme Thompson

Hans-H. Froehlich

Ian Kilgore

Jacob J

Mark Stosberg

Mike Schilli

Ondrej Hanak

Peter John Acklam

Peter Rabbitson

Robert Stone

Rolf Grossmann

Sean M. Burke

Simon Legner

Slaven Rezic

Spiros Denaxas

Steve Hay

Todd Lipcon

Tom Hukins

Tony Finch

Toru Yamaguchi

Ville Skyttä

Yuri Karaban

Zefram

amire80

jefflee

john9art

mschilli

murphy

phrstbrn

ruff

sasao

uid39246

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 1996-2020 by Gisle Aas.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut