File Coverage

blib/lib/Data/CTable/Listing.pm
Criterion Covered Total %
statement 24 209 11.4
branch 0 82 0.0
condition 0 40 0.0
subroutine 8 21 38.1
pod 0 7 0.0
total 32 359 8.9


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             ## Emacs: -*- tab-width: 4; -*-
3              
4 1     1   858 use strict;
  1         2  
  1         56  
5              
6             package Data::CTable::Listing;
7              
8 1     1   9 use vars qw($VERSION); $VERSION = '0.1';
  1         3  
  1         68  
9              
10             =pod
11              
12             =head1 NAME
13              
14             Data::CTable::Listing - CTable holding file and directory listings
15              
16             =head1 SYNOPSIS
17              
18             ## Call from a shell script:
19             use Data::CTable::Listing;
20             exit !Data::CTable::Listing->script();
21              
22             This is an OO implementation of the guts of the "tls" perl script that
23             comes with the Data::CTable distribution.
24              
25             Please see Listing.pm for the full usage() message, or run the tls
26             perl script with the --help option.
27              
28             =head1 FURTHER INFO
29              
30             See the Data::CTable home page:
31              
32             http://christhorman.com/projects/perl/Data-CTable/
33              
34             =head1 AUTHOR
35              
36             Chris Thorman
37              
38             Copyright (c) 1995-2002 Chris Thorman. All rights reserved.
39              
40             This program is free software; you can redistribute it and/or modify
41             it under the same terms as Perl itself.
42              
43             =cut
44             {};
45              
46 1     1   7 use Data::CTable::Script; use vars qw(@ISA);
  1     1   2  
  1         38  
  1         6  
  1         1  
  1         461  
47             @ISA = qw(Data::CTable::Script);
48              
49             sub usage_message
50             {
51 0     0 0   my $this = shift();
52 0           my ($ScriptName) = @_;
53              
54 0           return(do{(my $doc = << 'END') =~ s/_SCR_/$ScriptName/g; $doc});
  0            
  0            
55            
56             _SCR_ [path path path...] [options]
57              
58             _SCR_ is a command-line tool similar to the Unix "ls" (directory
59             listing) command. It should run on any platform that has perl.
60              
61             It uses Data::CTable (Data::CTable::Listing sublcass) and
62             Data::ShowTable to build in memory a table of information about
63             specified files and directories and then output it to the console or a
64             file.
65              
66             Each command line path must be either a file or directory to be
67             included in the listing.
68              
69             If no paths are specified at all, the current directory is assumed.
70              
71             For each path argument that is a directory, an entry for each file in
72             that directory will also be included in the listing unless the
73             --nochildren option is specified. (You may suppress the top-level
74             item itself by specifying --notop). Other than the default single
75             level of children listed, recursion is not done unless you specify
76             --recurse.
77              
78             Options may appear in any order, before, after, or intermixed with the
79             path arguments. See below for options.
80              
81             In addition to the Path field which contains the absolute or relative
82             path name of each listed item, _SCR_ also derives a number of
83             additional fields which you may include in the listing.
84              
85             Available fields are:
86              
87             Main path field:
88              
89             Path Absolute or relative path of specified item
90              
91             Fields from stat($Path) (see --fields, --all, or --info):
92            
93             Device 0 dev device number of filesystem
94             INode 1 ino inode number
95             Mode 2 mode file mode (type and permissions)
96             NLink 3 nlink number of (hard) links to the file
97             UID 4 uid numeric user ID of file's owner
98             GID 5 gid numeric group ID of file's owner
99             RDev 6 rdev the device identifier (special files only)
100             Size 7 size total size of file, in bytes
101             ATime 8 atime last access time in seconds since the epoch
102             MTime 9 mtime last modify time in seconds since the epoch
103             CTime 10 ctime inode change time (NOT creation time)
104             BlkSize 11 blksize preferred block size for file system I/O
105             Blocks 12 blocks actual number of blocks allocated
106              
107             Fields from localtime($MTime) (see --fields, --all, or --localtime):
108             (see man ctime or struct tm):
109              
110             Sec 0 tm_sec The number of seconds after the minute, normally in
111             the range 0 to 59, but can be up to 61 to allow for
112             leap seconds.
113             Min 1 tm_min The number of minutes after the hour, in the range 0 to 59.
114             Hour 2 tm_hour The number of hours past midnight, in the range 0 to 23.
115             Day 3 tm_mday The day of the month, in the range 1 to 31.
116             TMon 4 tm_mon The number of months since January, in the range 0 to 11.
117             TYear 5 tm_year The number of years since 1900 (range 69 to 138)
118             WDay 6 tm_wday The number of days since Sunday, in the range 0 to 6.
119             YDay 7 tm_yday The number of days since January 1, in the range 0 to 365.
120             IsDST 8 tm_isdst A flag that indicates whether daylight saving
121             time is in effect at the time described. The
122             value is positive if daylight saving time is in
123             effect, zero if it is not, and negative if the
124             information is not available.
125              
126             Fields from File::Basename (see --fields, --all, or --base):
127              
128             Dir Directory component of Path
129             Base Base component of file name (without the Ext)
130             Ext Extension (any non-. characters after last . plus the . itself)
131              
132             Other derived fields (see --fields, --all, or --derived):
133              
134             File Filename component of Path (= Base + Ext)
135             Type File or directory
136              
137             Perms Permissions (from Mode), specified as an octal string
138             Owner Unix owner name: getpwuid(UID) (empty on Win)
139             Group Unix owner name: getgrgid(GID) (empty on Win)
140              
141             Mon TMon + 1 (range 1 to 12)
142             Year TYear + 1900 (range 1900 to 2038)
143              
144             RTime ModTime as human-readable string (localtime(MTIME)."")
145             Stamp String-sortable semi-numeric time stamp (based on MTIME)
146              
147             Field names are always built and output using mixed-case as indicated.
148              
149             However, case is ignored by the --fields or --sort options (you may
150             specify the field names in all lower-case, for example). You may also
151             specify any shorter form of any field name as long as it is not
152             ambiguous. (For example "fi" for File or "pa" for Path.)
153              
154             Available command-line options are:
155              
156             Miscellaneous
157             -------------
158              
159             Ignore all other options and print this help message instead.
160              
161             --help
162             -h
163              
164             Don't turn off progress() calls in the table.
165              
166             --verbose
167             -v
168              
169              
170             Controlling files (rows) included in listing
171             --------------------------------------------
172              
173             Don't include children of specified directories.
174              
175             --nochildren
176             --nochild
177             --noc
178              
179             Don't list specified directories themselves (but do list their
180             children unless --noc).
181              
182             --notop
183             --not
184              
185             Recursively include all sub-directories in listings (overrides
186             --nochildren).
187              
188             --recurse
189             -r
190              
191             Restrict listing to only directories (d) or files (f).
192              
193             --type=d
194             -ty=d
195              
196             --type=f
197             -ty=f
198              
199              
200             Setting which fields (columns) to include in output
201             ---------------------------------------------------
202              
203             Use the options below to specify the fields to be listed in the
204             output table, in order. If no fields are specified, the following
205             fields will be used:
206              
207             Path Owner Group Size RTime (except Path when --nopath)
208              
209             --fields f1,f2
210             --field f1 --field f2
211             -f=f1,f2,f3
212             -f="f1 f2 f3"
213              
214             Force all available fields to be included in output (overrides --fields).
215              
216             --all
217             -a
218              
219             Force all localtime-derived fields to be included in output (adds
220             any not yet specified in --fields).
221              
222             --localtime
223             -l
224              
225             Force all stat-derived fields to be included in output (adds
226             any not yet specified in --fields).
227              
228             --info
229             -i
230              
231             Force all File::Basename-derived fields to be included in output
232             (adds any not yet specified in --fields).
233              
234             --base
235             -b
236              
237             Force all other derived fields to be included in output.
238              
239             --derived
240             -de
241              
242             Don't automatically put the Path as first field in output (but do
243             include it if no other fields are specified).
244              
245             --nopath
246             --nop
247              
248              
249             Sort order
250             ----------
251              
252             Specify ordered list of field names to use to sort and sub-sort
253             the output.
254              
255             --sort f1 --sort f2
256             --sort f1,f2
257             -s=f1,f2,f3
258             -s="f1 f2 f3"
259              
260             If none are specified, the table is sorted ascending by Path.
261              
262             In fact, Path is always the LAST field in the sort order, whether
263             you specify it explicity or not. This guarantees that the sort
264             order is always unambiguous since no two items will have the same
265             Path.
266              
267             In the sort order, each field will be sorted ascending unless a
268             "+" is appended to its name (mnemonic: + sorts highest items
269             first). For example:
270              
271             -s=Size+
272             -s=WDay+,Size+
273              
274             Controlling the output method and its arguments
275             --------------------------------------------
276              
277             Specify name of method Data::CTable::Listing method to call in
278             order to output data, and any arguments to that method, separated
279             by commas, spaces or by multiple --output or -o options, .
280              
281             Default is the "format" method, which prints a table using the
282             Data::ShowTable, which of course must be installed to work.
283              
284             The write() method is another alternative. With no arguments, it
285             will write to STDOUT (and may be redirected to a file or other
286             script). With a single argument, it will write to a file by that
287             name. Multiple arguments are processed as named-parameter
288             arguments; and any documented named-parameter option to
289             Data::CTable::write() may be specified this way.
290              
291             Note: the default delimiter character for write() is comma. To
292             get tab-delimited output, see the --tabs option below.
293              
294             --output=write
295             -o=write,foo.txt
296             -o=write,_FileName,foo.txt,_LineEnding,mac,HeaderRow,0
297              
298             Shortcut to specify using the write method to write to STDOUT.
299             This is equivalent to "-o=write".
300              
301             --write
302             -w
303              
304             Specify tab-delimited output. This forces the "write" method
305             instead of the "format" method to be used to output the table.
306              
307             This is equivalent to specifying '--output=write,_FDelimiter,"\t"'
308             except it's briefer and easier to get your shell script to accept
309             it.
310              
311             --tabs
312             -ta
313              
314             Suppress a header row showing field names. This forces the
315             "write" method instead of the "format" method to be used to output
316             the table.
317              
318             This is equivalent to specifying '--output=write,_HeaderRow,0'.
319              
320             --noheader
321             -noh
322              
323             Specify line-endings. Endings by default will be whatever "\n" is
324             on the current system. To force them, specify one of the
325             following options. These options force the "write" method and are
326             equivalent to specifying equivalent to specifying
327             '--output=write,_LineEnding,mac', ...unix, ...dos, etc.
328              
329             --mac
330             -m
331             --unix
332             -u
333             --dos
334             -do
335              
336             This script is part of the Data::CTable distribution.
337              
338             Most of the script's work is done by the subclassed CTable object in
339             Data::CTable::Lister.
340              
341             You can use or subclass Data::CTable::Lister directly if you'd like to
342             use any aspect of this script's functionality in your own perl tools.
343              
344             See that module for detailed documentation of this script's behavior,
345             including the usage() message that prints when --help is requested.
346              
347             Copyright (c) 1995-2002 Chris Thorman. All rights reserved.
348              
349             This program is free software; you can redistribute it and/or modify
350             it under the same terms as Perl itself.
351              
352             See Data::CTable home page for further info:
353              
354             http://christhorman.com/projects/perl/Data-CTable/
355              
356             END
357 0           {};
358             }
359              
360             sub optionspec
361             {
362 0     0 0   my $Class = shift;
363              
364 0           my $Spec = {(
365             ##### Common options from the parent class..
366            
367 0           %{$Class->SUPER::optionspec},
368              
369             ##### Listed here for redundancy...
370              
371             ## Common options
372             "help" => 0 ,
373             "verbose" => 0 ,
374            
375             ## Filtd list
376             "fields=s" => [],
377            
378             ## Sorting
379             "sort=s" => [],
380            
381             ## Output method
382             "output=s" => [],
383              
384             ##### Plus the following options unique to this script:
385            
386             ## Data::CTable::Listing-specific options
387            
388             # Options controlling which files are listed
389             "notop" => 0,
390             "nochildren" => 0,
391             "recurse" => 0,
392             "type=s" => "",
393              
394             # Options controlling which fields are shown
395             "all" => 0,
396             "localtime" => 0,
397             "info" => 0,
398             "base" => 0,
399             "derived" => 0,
400             "nopath" => 0,
401              
402             # Output options
403             "write" => 0,
404             "tabs" => 0,
405             "noheader" => 0,
406             "mac" => 0,
407             "unix" => 0,
408             "dos" => 0,
409              
410             )};
411            
412 0           return($Spec);
413             }
414              
415             =pod
416              
417             =head1 METHODS
418            
419             $Class->run()
420              
421             Main entry point for the "tls" or "tls.pl" script included with the
422             Data::CTable distribution.
423              
424             =cut
425             {};
426              
427             sub run
428             {
429 0     0 0   my $Class = shift;
430 0           my ($Opts) = @_;
431            
432 1     1   9 use Data::CTable qw(path_info);
  1         3  
  1         122  
433            
434             ## Create an empty options hash in case we didn't get one.
435 0   0       $Opts ||= {};
436              
437             ## Find the paths and create a new file table using them.
438 0           my $this = $Class->make_file_table($Opts);
439            
440             ## Run the appropriate output method.
441 0           return($this->make_output());
442             }
443              
444             =pod
445              
446            
447             $Class->make_file_table()
448              
449             Instantiates a table object, gets and inserts file listing and other
450             fields, processes user's arguments, and generates output.
451              
452             =cut
453             {};
454              
455             sub make_file_table
456             {
457 0     0 0   my $Class = shift;
458 0           my ($Opts) = @_;
459            
460 1     1   7 use Data::CTable qw(min max);
  1         3  
  1         673  
461            
462             ## Make a table...
463             ## Default sort order is by path
464             ## Path sort is done as Text (case-insensitive)
465             ## All other fields sorted (and justified) as Integer by default
466             ## Max column width is max path length
467             ## Silent sorting
468             ## Path field is all paths specified
469             ## Fields S00..S12 contain results of calling stat()
470              
471 0 0         my $t = $Class->new
472             ({
473             ( _SortOrder => [qw(Path)]),
474             ( _SortSpecs => {( (Path => {SortType=>'Text' }),
475              
476             (File => {SortType=>'String'}),
477             (Type => {SortType=>'String'}),
478              
479             (Dir => {SortType=>'String'}),
480             (Base => {SortType=>'String'}),
481             (Ext => {SortType=>'String'}),
482              
483             (Perms => {SortType=>'String'}),
484             (Owner => {SortType=>'String'}),
485             (Group => {SortType=>'String'}),
486              
487             (RTime => {SortType=>'String'}),
488             (Stamp => {SortType=>'String'}),
489              
490             )}),
491             ( _DefaultSortType => 'Integer'),
492             ($Opts->{verbose} ? () :
493             ( _Progress => 0)),
494             ( _Opts => $Opts),
495             ( Path => []),
496             });
497            
498             ## Print a progress mess that will be ignored unless we're in verbose mode.
499 0           $t->progress("Searching...");
500              
501             ## Expand/filter, etc. the caller's specified paths and/or glob
502             ## expressions.
503 0           my $Paths = $Class->process_path_args($Opts);
504              
505             ## Find longest file path.
506 0           my $Longest; foreach (@$Paths) {$Longest = max($Longest, length)};
  0            
  0            
507              
508             ## Set the MaxWidth to something that will accommodate all
509             ## possible fields (longer of path, RTime)
510              
511 0           $t->{_MaxWidth} = max(25, $Longest); ## 25 = allow for RTime field
512              
513             ## Set the Path column
514 0           $t->col(Path => $Paths);
515              
516             ## Build the "stat" fields...
517 0           foreach my $FNum (-1..$#$Paths)
518             {
519             ## Get stats for the file...
520 0           my $Stats = [stat($Paths->[$FNum])];
521 0           foreach my $n ('00'..'12')
522             {
523             ## First time through (and only time through if no records
524             ## present), just make the columns.
525 0 0         $t->col("S$n"), next if $FNum == -1;
526              
527             ## Fill stat values into pre-sized vectors in the table.
528 0           $t->col("S$n")->[$FNum] = $Stats->[$n];
529             }
530             }
531              
532             ## Fix the order of Path, S00..S12 fields.
533 0           $t->fieldlist_freeze();
534              
535             ## Change some field names.
536 0           $t->col_rename(qw( S00 Device
537             S01 INode
538             S02 Mode
539             S03 NLink
540             S04 UID
541             S05 GID
542             S06 RDev
543             S07 Size
544             S08 ATime
545             S09 MTime
546             S10 CTime
547             S11 BlkSize
548             S12 Blocks
549             ));
550              
551             ## Build the "localtime" fields...
552 0           my $Times = $t->col('MTime');
553 0           foreach my $FNum (-1..$#$Times)
554             {
555             ## Get stats for the file...
556 0           my $TInfo = [localtime($Times->[$FNum])];
557 0           foreach my $n ('00'..'08')
558             {
559             ## First time through (and only time through if no records
560             ## present), just make the columns.
561 0 0         $t->col("T$n"), next if $FNum == -1;
562              
563             ## Fill stat values into pre-sized vectors in the table.
564 0           $t->col("T$n")->[$FNum] = $TInfo->[$n];
565             }
566             }
567              
568             ## Change some field names.
569 0           $t->col_rename(qw( T00 Sec
570             T01 Min
571             T02 Hour
572             T03 Day
573             T04 TMon
574             T05 TYear
575             T06 WDay
576             T07 YDay
577             T08 IsDST
578             ));
579              
580             ## Build the "basename" fields...
581 0           foreach my $FNum (-1..$#$Paths)
582             {
583             ## Get stats for the file...
584 1     1   10 use File::Basename qw(fileparse);
  1         2  
  1         530  
585 0 0         my $FInfo = [(fileparse($Paths->[$FNum], '\.[^\.]+'))[1,0,2]]
586             unless $FNum == -1;
587            
588 0           foreach my $n ('00'..'02')
589             {
590             ## First time through (and only time through if no records
591             ## present), just make the columns.
592 0 0         $t->col("F$n"), next if $FNum == -1;
593              
594             ## Fill stat values into pre-sized vectors in the table.
595 0           $t->col("F$n")->[$FNum] = $FInfo->[$n];
596             }
597             }
598              
599             ## Change some field names.
600 0           $t->col_rename(qw( F00 Dir
601             F01 Base
602             F02 Ext
603             ));
604              
605             ## Add a column showing the full file name.
606 0     0     $t->col(File => $t->calc(sub{"$main::Base$main::Ext"}));
  0            
607 0 0   0     $t->col(Type => $t->calc(sub{-d $main::Path ? "D" : "F"}));
  0            
608              
609             ## Add a Perms field showing permission component of (stat)[2]
610             ## formatted as a 4-digit octal number.
611              
612 0           $t->col(Perms => [map {sprintf("%04o", $_ & 0777)} @{$t->col('Mode')}]);
  0            
  0            
613              
614             ## Add columns translating the UID and GID into their string form.
615              
616 0           $t->col(Owner => [map {((eval{getpwuid($_)})[0]) } @{$t->col('UID' )}]);
  0            
  0            
  0            
617 0           $t->col(Group => [map {((eval{getgrgid($_)})[0]) } @{$t->col('GID' )}]);
  0            
  0            
  0            
618              
619             ## Add a column showing the full file name.
620 0     0     $t->col(Mon => $t->calc(sub{$main::TMon + 1}));
  0            
621 0     0     $t->col(Year => $t->calc(sub{$main::TYear + 1900}));
  0            
622              
623             ## Add some time stamps -- one human-readable and one computer-sortable.
624 0     0     $t->col(RTime => $t->calc(sub{localtime($main::MTime).""}));
  0            
625 1     1   1193 use POSIX;
  1         9434  
  1         7  
626 0     0     $t->col(Stamp => $t->calc(sub{strftime("%Y%m%d%H%M%S", localtime($main::MTime))}));
  0            
627            
628 0           return($t);
629             }
630              
631             =pod
632              
633             $Class->process_path_args()
634              
635             Generates the file listing by interpreting the file-related
636             command-line options.
637              
638             =cut
639             {};
640              
641             sub process_path_args
642             {
643 0     0 0   my $Class = shift;
644 0           my ($Opts) = @_;
645            
646             ## Get the paths option.
647 0           my $Paths = $Opts->{args};
648            
649             ## Get details about paths on the current platform
650 0           my ($Sep, $Up, $Cur) = @{path_info()}{qw(sep up cur)};
  0            
651            
652             ## Default file list is the current directory.
653 0 0 0       $Paths = [$Cur] unless (ref($Paths) eq 'ARRAY') && @$Paths;
654            
655             ## Glob the paths to support shell-like wildcards on all
656             ## platforms or when calling from a script.
657              
658 0 0         $Paths = [map {my $x=[glob $_]; @$x ? @$x : $_} @$Paths];
  0            
  0            
659              
660             ## Validate / canonicalize path arguments.
661 0 0         $Paths = [map
662             {
663             ## Ensure trailing $Sep on dir names
664 0           ($_) =~ s{([^\Q$Sep\E])$ }{$1$Sep}ox if -d;
665            
666             ## Filter/warn re: existence of items we're going to list
667 0           (-e $_ ?
668             $_ :
669 0 0         do{$Class->warn("No such file or directory '$_'"); ()});
  0            
670            
671             } @$Paths];
672            
673 0           my $Recurse = $Opts->{recurse};
674 0           my $NoTop = $Opts->{notop};
675 0   0       my $NoChildren = $Opts->{nochildren} && !$Recurse;
676 0           my $Type = $Opts->{type};
677              
678             ## Expand directory paths into their constituent components.
679 0           $Paths = [map
680             {
681 0           my $Path = $_;
682 0           (-d $Path ?
683            
684             ## Get a listing with $Path prepended to each element
685 0 0         @{$Class->list_dir($Path, $Sep, $Cur, $Recurse, $NoTop, $NoChildren)} :
686            
687             ## Otherwise it's just a bare file name.
688             $Path
689             );
690             } @$Paths];
691              
692 0 0         $Paths = [grep {-d} @$Paths] if $Type eq 'd';
  0            
693 0 0         $Paths = [grep {-f} @$Paths] if $Type eq 'f';
  0            
694            
695 0           return($Paths);
696             }
697              
698             =pod
699              
700             $t->make_output()
701              
702             Parses and processes the sorting, fieldlist, and output options, then
703             calls the appropriate output method. Returns the scalar buffer
704             reference returned by that method, if any; otherwise a ref to an empty
705             buffer.
706              
707             =cut
708             {};
709              
710             sub make_output
711             {
712 0     0 0   my $this = shift;
713              
714             ## Assume parsed Opts have been stored in the object in the _Opts slot.
715 0   0       my $Opts = $this->{_Opts} || {};
716              
717             ## Retrieve various output options
718 0           my $Write = $Opts->{write};
719 0           my $Tabs = $Opts->{tabs};
720 0   0       my $Mac = $Opts->{mac} && 'mac';
721 0   0       my $Unix = $Opts->{unix} && 'unix';
722 0   0       my $Dos = $Opts->{dos} && 'dos';
723 0           my $NoHeader = $Opts->{noheader};
724              
725             ## Retrieve the output --fields and --sort fields requested by the user.
726 0   0       my $Fields = $Opts->{fields} || [];
727 0   0       my $Sorts = $Opts->{sort } || [];
728              
729             ## First extract the specified sort directions into a hash so we can pull them in later.
730 0           my $SortDirs = {}; @$SortDirs{map {(/(\w+)/)[0]} @$Sorts} = map {(/\w+(\W)/)[0]} @$Sorts;
  0            
  0            
  0            
731 0           my $Sorts = [map {(/(\w+)/)[0]} @$Sorts];
  0            
732              
733             ## Allow for weird casing in the user's field lists...
734 0           my $FieldsAll = $this->fieldlist_all();
735 0           my $FieldCaseMap = {}; @$FieldCaseMap {map {lc} @$FieldsAll} = @$FieldsAll;
  0            
  0            
736              
737             ## Allow for shortened but unambiguous forms of field names to be used.
738 0           foreach my $Field (@$FieldsAll)
739             {
740 0           foreach (1..length($Field) - 1)
741             {
742 0           my $Str = lc(substr($Field, 0, $_));
743 0 0         (exists($FieldCaseMap->{$Str}) ?
744             (delete $FieldCaseMap->{$Str}) : ## Existing therefore ambiguous; disallow
745             ( $FieldCaseMap->{$Str} = $Field)); ## Not existing; add.
746             }
747             }
748            
749             ## Map all the field names...
750 0           $Fields = [map {(exists($FieldCaseMap->{lc($_)}) ?
  0            
751             $FieldCaseMap->{lc($_)} :
752 0 0         do{$this->warn("Unrecognized output field name (ignored): $_"); ()})} @$Fields];
  0            
753            
754             ## Map all the sort field names...
755 0           $Sorts = [map {(exists($FieldCaseMap->{lc($_)}) ?
  0            
756             $FieldCaseMap->{lc($_)} :
757 0 0         do{$this->warn("Unrecognized sort field name (ignored): $_"); ()})} @$Sorts];
  0            
758            
759 0           @$SortDirs{@$FieldCaseMap{keys %$SortDirs}} = values %$SortDirs;
760              
761             ## Add additional fields as requested.
762              
763 0           my $InfoFields = [qw( Device
764             INode
765             Mode
766             NLink
767             UID
768             GID
769             RDev
770             Size
771             ATime
772             MTime
773             CTime
774             BlkSize
775             Blocks
776             )];
777              
778 0           my $TimeFields = [qw( Sec
779             Min
780             Hour
781             Day
782             TMon
783             TYear
784             WDay
785             YDay
786             IsDST
787             )];
788              
789 0           my $BaseFields = [qw( Dir
790             Base
791             Ext)];
792              
793 0           my $DervFields = [qw( File
794             Type
795             Perms
796             Owner
797             Group
798             Mon
799             Year
800             RTime
801             Stamp
802             )];
803            
804 0 0         push @$Fields, @$InfoFields if $Opts->{info};
805 0 0         push @$Fields, @$TimeFields if $Opts->{localtime};
806 0 0         push @$Fields, @$BaseFields if $Opts->{base};
807 0 0         push @$Fields, @$DervFields if $Opts->{derived};
808 0 0         push @$Fields, (@$InfoFields, @$TimeFields, @$BaseFields, @$DervFields) if $Opts->{all};
809              
810             ## If the field list is empty, populate it.
811 0 0         $Fields = [($Opts->{nopath} ? () : 'Path'), qw(Owner Group Size RTime)] unless @$Fields;
    0          
812              
813             ## If the field list doesn't include "Path" already, then prepend
814             ## it unless --nopath was specified
815 0 0 0       unshift @$Fields, 'Path' unless ((grep {$_ eq 'Path'} @$Fields) || (@$Fields && $Opts->{nopath}));
  0   0        
816            
817             ## After adding in the above field groups, remove any duplicates
818 0 0         my $Fs = {}; $Fields = [grep {$Fs->{$_}++ ? () : $_} @$Fields];
  0            
  0            
819              
820             ## If the sort list doesn't include "Path" already, then append it.
821 0 0         push @$Sorts, 'Path' unless grep {$_ eq 'Path'} @$Sorts;
  0            
822              
823             ## Add a sort-direction specifier to the table for each field
824             ## based on the + signs we got or didn't get in the --sort fields
825             ## list.
826              
827 0           foreach my $Field (@$Sorts)
828             {
829 0 0         $this->sortspec($Field, {%{$this->sortspec($Field)},
  0            
830             SortDirection =>
831             ($SortDirs->{$Field} eq '+' ? -1 : 1)});
832             }
833            
834             ## die $this->dump($Sorts, $FieldCaseMap, $SortDirs, $this->sortspecs());
835            
836             ## Sort the table.
837 0           $this->sortorder($Sorts);
838 0           $this->sort();
839            
840             ## Set the field list for output.
841 0           $this->fieldlist($Fields);
842              
843             ## Process the output method
844 0           my $OutputArgs = $Opts->{output}; ## Already an array ref
845 0   0       my $Method = shift @$OutputArgs || "format";
846              
847             ## If "write" was specified, then we're going to force the "write"
848             ## method. The FileName "-" (meaning STDOUT) will override any
849             ## other name that may have been redundantly specified with
850             ## -o=write.
851              
852 0 0         if ($Write)
853             {
854 0           $Method = "write";
855 0 0         unshift @$OutputArgs, "_FileName" if @$OutputArgs == 1;
856 0           push @$OutputArgs, (_FileName => "-");
857             }
858              
859             ## If "tabs" was specified, then we're going to force the "write" method
860 0 0         if ($Tabs)
861             {
862 0           $Method = "write";
863 0 0         unshift @$OutputArgs, "_FileName" if @$OutputArgs == 1;
864 0           push @$OutputArgs, (_FDelimiter => "\t");
865             }
866              
867 0 0         if ($NoHeader)
868             {
869 0           $Method = "write";
870 0 0         unshift @$OutputArgs, "_FileName" if @$OutputArgs == 1;
871 0           push @$OutputArgs, (_HeaderRow => 0);
872             }
873              
874 0 0 0       if ($Mac || $Unix || $Dos)
      0        
875             {
876 0           $Method = "write";
877 0 0         unshift @$OutputArgs, "_FileName" if @$OutputArgs == 1;
878 0   0       push @$OutputArgs, (_LineEnding => ($Mac || $Unix || $Dos));
879             }
880              
881             ## Do the output.
882 0           my $Result = $this->$Method(@$OutputArgs);
883              
884            
885 0 0         return(ref($Result) eq 'SCALAR' ? $Result : \ '');
886             }
887              
888             =pod
889              
890             ## Class-level utility method
891             $Class->list_dir($Path, $Sep, $Cur, $Recurse, $NoTop, $NoChildren)
892              
893             Platform-neutral recursive directory lister routine.
894              
895             Caller must supply $Sep (platform's separator character) and $Cur
896             (platform's string meaning "current directory").
897              
898             If $Dir is empty/undef, then $Cur will be used as the starting point.
899              
900             $Recurse means recurse to list subdirectories.
901              
902             $NoTop means don't include $Dir itself in the listing.
903              
904             $NoChildren means don't include children of $Dir. This option is
905             ignored if $Recurse is true.
906              
907             =cut
908              
909             sub list_dir
910             {
911 0     0 0   my $Class = shift;
912 0           my ($Dir, $Sep, $Cur, $Recurse, $NoTop, $NoChildren) = @_;
913            
914 0           my $Names = [];
915              
916             ## Ensure trailing $Sep on path given by user.
917 0   0       ($Dir ||= $Cur) =~ s{([^\Q$Sep\E])$ }{$1$Sep}ox;
918            
919 0           my $D = \*DIR; ## We like file handle references.
920            
921 0 0         opendir($D, $Dir)
922             or $Class->warn("Can't open directory $Dir: $!"), goto done;
923            
924 0           $Names = [($NoTop ? () : $Dir), ## Include $Dir itself in listing.
925             ($NoChildren ? () :
926 0           map {my $Sub = "$Dir$_";
927 0           (-d $Sub ?
928             ("$Sub$Sep",
929             ($Recurse ?
930 0 0         @{$Class->list_dir($Sub, $Sep, $Cur, $Recurse, 'NoTop')} :
    0          
931             ())) :
932             $Sub)}
933 0 0         grep {!/^\.\.?$/} ## Omit . and .. entries.
    0          
934             readdir($D))];
935 0           closedir $D;
936            
937 0           done:
938             return($Names);
939             }
940              
941             1;