fixed wording in this script.
[feisty_meow.git] / scripts / files / filename_helper.pl
1 #!/usr/bin/perl
2
3 ###############################################################################
4 #                                                                             #
5 #  Name   : filename_helper                                                   #
6 #  Author : Chris Koeritz                                                     #
7 #  Rights : Copyright (C) 1996-$now by Author                                 #
8 #                                                                             #
9 #  Purpose:                                                                   #
10 #                                                                             #
11 #    Support for manipulating filenames.                                      #
12 #                                                                             #
13 ###############################################################################
14 #  This program is free software; you can redistribute it and/or modify it    #
15 #  under the terms of the GNU General Public License as published by the Free #
16 #  Software Foundation; either version 2 of the License or (at your option)   #
17 #  any later version.  See: "http://www.gruntose.com/Info/GNU/GPL.html" for a #
18 #  version of the License.  Please send any updates to "fred@gruntose.com".   #
19 ###############################################################################
20
21 use Env qw(OS IS_MSYS);
22
23 ############################################################################
24
25 #hmmm: make this lower-level, a script that is inherited by all perl scripts.
26
27 sub yeti_interrupt_handler {
28   die "caught an interrupt; exiting.\n";
29 }
30
31 # hook in a ctrl-c catcher, since that seems to be universally needed.
32 sub install_interrupt_catcher {
33   $SIG{INT} = 'yeti_interrupt_handler';
34   $SIG{QUIT} = 'yeti_interrupt_handler';
35 #print "mapped int and quit signals\n";
36   return 0
37 }
38
39 ############################################################################
40
41 # takes an array of filenames (each possibly containing spaces and/or
42 # wildcards) and resolves it to a useful list of actual files.
43
44 sub glob_list {
45   local(@temp_list) = @_;  # the names we're given.
46   local(@to_return) = ();  # the final form of the name list.
47 #print "temp list is @temp_list\n";
48
49   # scan through the list we're given.
50   foreach $entry (@temp_list) {
51 #print "entry is $entry\n";
52     local(@chopped_filename) = &split_filename($entry);
53 #print "chopped 0=$chopped_filename[0]\n";
54 #print "chopped 1=$chopped_filename[1]\n";
55     if ( (@chopped_filename[0] eq ".") || (@chopped_filename[0] eq "..") ) {
56       # add the simple directory name into the list.
57       push @to_return, $chopped_filename[0];
58       next;
59     }
60     if (@chopped_filename[1] eq ".") {
61       # add a directory that didn't have more pattern attached.
62       push @to_return, $chopped_filename[0];
63       next;
64     }
65     opendir WHERE, $chopped_filename[0];  # open the directory up.
66     local(@files_found) = readdir(WHERE);
67     closedir WHERE;
68     foreach $possible_name (@files_found) {
69       # zoom through the list and see if we need to add it to the ones
70       # matching the passed in patterns.
71 #      if ( ($possible_name eq ".") || ($possible_name eq "..") ) { 
72 #        # skip the directory entries.
73 #        print "skipping dir entries\n";
74 #        next;
75 #      }
76       # we need to process this a bit; directory patterns are different.
77       local($match) = $chopped_filename[1];
78       $match =~ s/\./\\./g;  # replace periods with escaped ones.
79       $match =~ s/\*/.*/g;  # replace asterisks with dot star.
80       $match =~ s/\+/\\+/g;  # escape plusses.
81       $match = "^" . $match . "\$";  # make sure the whole thing matches.
82 #print "possibname is $possible_name\n";
83       if ($possible_name =~ /$match/) {
84         # this one matches so add it.
85         push @to_return, $chopped_filename[0] . $possible_name;
86       }
87     }
88   }
89   return @to_return;
90 }
91
92 ############################################################################
93
94 # reports if two file names are the same file.
95
96 sub same_file {
97   local($file1, $file2) = @_;
98  
99   ($dev1, $ino1, $junk1) = stat $file1;
100   ($dev2, $ino2, $junk2) = stat $file2;
101
102   return ($dev1 == $dev2) && ($ino1 == $ino2);
103 }
104
105 ############################################################################
106
107 # splits a filename into a directory and file specification.
108
109 sub split_filename {
110   local($chewed_name) = &remove_trailing_slashes(@_);
111   $chewed_name = &canonicalize($chewed_name);
112   $chewed_name = &patch_name_for_pc($chewed_name);
113   if ($chewed_name =~ /\//) {
114     # there's a slash in there.
115     local($directory_part) = $chewed_name;
116     $directory_part =~ s/^(.*\/)[^\/]*$/\1/;
117     local($file_part) = $chewed_name;
118     $file_part =~ s/^.*\/([^\/]*)$/\1/;
119     if ($file_part eq "") {
120       # if there was no file specification, just add a non-matching spec.
121       $file_part = ".";
122     }
123     return ($directory_part, $file_part);
124   } elsif ($chewed_name eq ".") {
125     return (".", "");
126   } elsif ($chewed_name eq "..") {
127     return ("..", "");
128   } else {
129     # no slash in this name, so we fix that and also make sure we match
130     # the whole name.
131     return ("./", $chewed_name);
132   }
133 }
134
135 ############################################################################
136
137 # returns the base part of the filename; this omits any directories.
138
139 sub basename {
140   local(@parts) = &split_filename(@_);
141   return $parts[1];
142 }
143
144 # returns the directory part of the filename.
145
146 sub dirname {
147   local(@parts) = &split_filename(@_);
148   return $parts[0];
149 }
150
151 # returns the extension found on the filename, if any.
152 sub extension {
153   local($base) = &basename(@_);
154 #printf "base is $base\n";
155   local($found) = -1;
156   for (local($i) = length($base) - 1; $i >= 0; $i--) {
157 #printf "char is " . substr($base, $i, 1) . "\n";
158     if (substr($base, $i, 1) eq '.') {
159       $found = $i;
160 #printf "got period found is $found\n";
161       last;
162     }
163   }
164   if ($found >=0) {
165     return substr($base, $found, length($base) - $found);
166   }
167   return "";  # no extension seen.
168 }
169
170 # returns the portion of the filename without the extension.
171 sub non_extension {
172   local($full) = &remove_trailing_slashes(@_);
173   $full = &canonicalize($full);
174   $full = &patch_name_for_pc($full);
175   local($ext) = &extension($full);
176   local($to_remove) = length($ext);
177   return substr($full, 0, length($full) - $to_remove);
178 }
179
180 ############################################################################
181
182 # removes all directory slashes (either '/' or '\') from the end of a string.
183
184 sub remove_trailing_slashes {
185   local($directory_name) = @_;
186   # start looking at the end of the string.
187   local($inspection_point) = length($directory_name) - 1;
188   while ($inspection_point > 0) {
189     # examine the last character in the string to see if it's a slash.
190     local($final_char) = substr($directory_name, $inspection_point, 1);
191     # leave the loop if it's not a slash.
192     if ( ($final_char ne "/") && ($final_char ne "\\") ) { last; }
193     chop($directory_name);  # remove the slash.
194     $inspection_point--;  # check the new last character.
195   }
196
197   return $directory_name;
198 }
199
200 ############################################################################
201
202 # returns the proper directory separator for this platform.  this requires
203 # an environment variable called "OS" for non-Unix operating systems.  the
204 # valid values for that are shown below.
205
206 sub directory_separator {
207   if ( ($OS eq "Windows_NT") || ($OS eq "Windows_95") 
208       || ($OS eq "DOS") || ($OS eq "OS2") ) { return "\\"; }
209   else { return "/"; }
210 }
211
212 ############################################################################
213
214 # these mutate the directory slashes in a directory name.
215
216 # the one we use most frequently; it uses the unix slash.
217 sub canonicalize {
218   return &canonicalizer(@_, "/");
219 }
220
221 # one that turns names into the style native on the current platform.
222 sub native_canonicalize {
223   return &canonicalizer(@_, &directory_separator());
224 }
225
226 # one that explicitly uses pc style back-slashes.
227 sub pc_canonicalize {
228   return &canonicalizer(@_, "\\");
229 }
230
231 # one that explicitly does unix style forward slashes.
232 sub unix_canonicalize {
233   return &canonicalizer(@_, "/");
234 }
235
236 # this more general routine gets a directory separator passed in.  it then
237 # replaces all the separators with that one.
238 sub canonicalizer {
239   local($directory_name) = $_[0];
240   local($dirsep) = $_[1];
241
242 #print "old dir name is \"$directory_name\"\n";
243   
244   if ($OS =~ /win/i) {
245 #somewhat abbreviated check; only catches windoze systems, not dos or os2.
246     # IS_MSYS is calculated by feisty meow scripts startup; it will be
247     # non-empty if this is the msys tool kit.
248     if (length($IS_MSYS) > 0) {
249       # msys utilities version (http://www.mingw.org)
250       $directory_name =~ s/^(.):[\\\/](.*)$/\/\1\/\2/;
251     } else {
252       # cygwin utilities version (http://www.cygwin.com)
253       $directory_name =~ s/^(.):[\\\/](.*)$/\/cygdrive\/\1\/\2/;
254     }
255 #print "new dir name is \"$directory_name\"\n";
256   }
257
258   # turn all the non-default separators into the default.
259   for (local($j) = 0; $j < length($directory_name); $j++) {
260     if ( (substr($directory_name, $j, 1) eq "\\") 
261         || (substr($directory_name, $j, 1) eq "/") ) {
262       substr($directory_name, $j, 1) = $dirsep;
263     }
264   }
265   # remove all occurrences of double separators except for the first
266   # double set, which could be a UNC filename.
267   local($saw_sep) = 0;
268   for (local($i) = 1; $i < length($directory_name); $i++) {
269     # iterate through the string looking for redundant separators.
270     if (substr($directory_name, $i, 1) eq $dirsep) {
271       # we found a separator character.
272       if ($saw_sep) {
273         # we had just seen a separator, so this is two in a row.
274         local($head, $tail) = (substr($directory_name, 0, $i - 1),
275             substr($directory_name, $i, length($directory_name) - 1));
276         $directory_name = $head . $tail;
277           # put the name back together without this redundant character.
278         $i--;  # skip back one and try again.
279       } else {
280         # we have now seen a separator.
281         $saw_sep = 1;
282       }
283     } else {
284       # this character was not a separator.
285       $saw_sep = 0;
286     }
287   }
288   if ($directory_name =~ /^.:$/) {
289     # fix a dos style directory that's just X:, since we don't want the
290     # current directory to be used on that device.  that's too random.
291     # instead, we assume they meant the root of the drive.
292     $directory_name = $directory_name . "/";
293   }
294   return $directory_name;
295 }
296
297 ############################################################################
298
299 # fixes a PC directory name if it is only a drive letter plus colon.
300
301 sub patch_name_for_pc {
302   local($name) = @_;
303 #print "name=$name\n";
304   if (length($name) != 2) { return $name; }
305   local($colon) = substr($name, 1, 1);
306 #print "colon=$colon\n";
307   # check whether the string needs patching.
308   if ($colon eq ":") {
309     # name is currently in feeble form of "X:"; fix it.
310     $name = $name . '/';
311   }
312 #print "returning=$name\n";
313   return $name;
314 }
315
316 ############################################################################
317
318 # tells whether a filename is important or not.  the unimportant category
319 # can usually be safely ignored or deleted.
320
321 sub important_filename {
322   local($name) = &basename($_[0]);
323   
324   # these are endings that we consider unimportant.  where a caret is used
325   # at the front, we will match only the whole string.  double slashes are
326   # used before periods to ensure we match a real period character.
327   local(@junk_files) = ("~", "^\\.#.*", "^\\._.*", "\\.aps", "\\.bak",
328       "^binaries",
329       "\\.clw", "^cpdiff_tmp\\.txt", "^\\.ds_store", "^diffs\\.txt",
330       "^diff_tmp\\.txt", "\\.dsp", "\\.dsw", "\\.gid", "gmon\\.out", "\\.isr",
331       "^isconfig\\.ini", "\\.log", "^manifest.txt", "^obj",
332       "\\.obj", "\\.output", "\\.plg", "^RCa.*", "^Release", "\\.res",
333       "\\.sbr", ".*scc", "^Setup\\.dbg", "^Setup\\.inx",
334       "^Setup\\.map", "^Setup\\.obs", "^Selenium_.*Login.html",
335       "\\.stackdump", "^string1033\\.txt", "\\.suo", "\\.swp",
336       "^thumbs.db", "\\.tmp", "^trans\\.tbl", "\\.user", "_version\\.h",
337       "_version\\.rc", "^waste", "\\.ws4", "\\.wsm");
338
339   foreach $temp (@junk_files) {
340     $temp = $temp . '$';
341     if ($name =~ /${temp}/i) { return 0; }
342       # we hit a match on it being unimportant.
343   }
344
345   return 1;  # anything else is considered important.
346 }
347
348 ############################################################################
349
350 sub sanitize_name {
351   return &patch_name_for_pc
352       (&remove_trailing_slashes
353           (&canonicalize(@_)));
354 }
355
356 ############################################################################
357
358 sub get_drive_letter {
359   local($path) = @_;
360   if (substr($path, 0, 1) =~ /[a-zA-Z]/) {
361     if (substr($path, 1, 1) eq ":") { return substr($path, 0, 1); }
362   }
363   return "";
364 }
365
366 ############################################################################
367
368 sub remove_drive_letter {
369   local($path) = @_;
370   if (substr($path, 0, 1) =~ /[a-zA-Z]/) {
371     if (substr($path, 1, 1) eq ":") { return substr($path, 2); }
372   }
373   return $path;
374 }
375
376 ############################################################################
377
378 # these return their argument with the case flipped to lower or upper case.
379
380 sub lower {
381   local($name) = @_;
382   $name =~ tr/A-Z/a-z/;
383   return $name;
384 }
385
386 sub upper {
387   local($name) = @_;
388   $name =~ tr/a-z/A-Z/;
389   return $name;
390 }
391
392 ############################################################################
393
394 # recursively deletes a directory that is passed as the single parameter.
395 # from http://developer.novell.com/wiki/index.php/Recursive_Directory_Remove
396 sub recursive_delete {
397   my $dir = shift;
398   local *DIR;
399
400   opendir DIR, $dir or die "opendir $dir: $!";
401   my $found = 0;
402   while ($_ = readdir DIR) {
403     next if /^\.{1,2}$/;
404     my $path = "$dir/$_";
405     unlink $path if -f $path;
406     recursive_delete($path) if -d $path;
407   }
408   closedir DIR;
409   rmdir $dir or print "error - $!";
410 }
411
412 ############################################################################
413
414 1;
415