Skip to content
Snippets Groups Projects
tree-1.6.0-matchdirs.patch 11.1 KiB
Newer Older
From 59d7a2237500c9439b758910e951bcd5dc48c657 Mon Sep 17 00:00:00 2001
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
Date: Thu, 4 Apr 2013 08:43:05 -0700
Subject: [PATCH] Add --matchdirs and --caseinsensitive to matching
The new --matchdirs option causes pattern matching to include
the full contents of any directories that match the pattern,
including sub-directories.

The --caseinsensitive option simply makes pattern matching case
insensitive.
 CHANGES    |  5 ++++
 README     |  4 ++++
 doc/tree.1 | 17 +++++++++++++-
 tree.c     | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++-----------
 4 files changed, 91 insertions(+), 14 deletions(-)
diff --git a/CHANGES b/CHANGES
index 10f2e20..004b86a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,8 @@
+Version 1.6.1
+  - Added --matchdirs flag so that patterns match against directory names and
+    then subsequently descend into sub-directories.
+  - Added --caseinsensitive flag so patterns match without regards to case.
+
 Version 1.6.0
   - Re-org of code into multiple files, split HTML and Unix listdir() into
     separate functions, various code cleanups and optimizations.
diff --git a/README b/README
index ab58d82..58767e8 100644
--- a/README
+++ b/README
@@ -141,6 +141,10 @@ Ujjwal Kumar
   - Suggested that tree backslash spaces like ls does for script use.  Made
     output more like ls.
 
+Jason A. Donenfeld
+  - Added --matchdirs flag for matching patterns against directories.
+  - Added --caseinsensitive flag for case insensitive pattern matching.
+
 And many others whom I've failed to keep track of.  I should have started
 this list years ago.
 
diff --git a/doc/tree.1 b/doc/tree.1
index 4b80852..41d0815 100644
--- a/doc/tree.1
+++ b/doc/tree.1
@@ -21,7 +21,7 @@
 .SH NAME
 tree \- list contents of directories in a tree-like format.
 .SH SYNOPSIS
-\fBtree\fP [\fB-acdfghilnpqrstuvxACDFQNSUX\fP] [\fB-L\fP \fIlevel\fP [\fB-R\fP]] [\fB-H\fP \fIbaseHREF\fP] [\fB-T\fP \fItitle\fP] [\fB-o\fP \fIfilename\fP] [\fB--nolinks\fP] [\fB-P\fP \fIpattern\fP] [\fB-I\fP \fIpattern\fP] [\fB--inodes\fP] [\fB--device\fP] [\fB--noreport\fP] [\fB--dirsfirst\fP] [\fB--version\fP] [\fB--help\fP] [\fB--filelimit\fP \fI#\fP] [\fB--si\fP] [\fB--prune\fP] [\fB--du\fP] [\fB--timefmt\fP \fIformat\fP] [\fIdirectory\fP ...]
+\fBtree\fP [\fB-acdfghilnpqrstuvxACDFQNSUX\fP] [\fB-L\fP \fIlevel\fP [\fB-R\fP]] [\fB-H\fP \fIbaseHREF\fP] [\fB-T\fP \fItitle\fP] [\fB-o\fP \fIfilename\fP] [\fB--nolinks\fP] [\fB-P\fP \fIpattern\fP] [\fB-I\fP \fIpattern\fP] [\fB--inodes\fP] [\fB--device\fP] [\fB--noreport\fP] [\fB--dirsfirst\fP] [\fB--version\fP] [\fB--help\fP] [\fB--filelimit\fP \fI#\fP] [\fB--si\fP] [\fB--prune\fP] [\fB--du\fP] [\fB--timefmt\fP \fIformat\fP] [\fB--caseinsensitive\fP] [\fB--matchdirs\fP] [\fIdirectory\fP ...]
 .br
 .SH DESCRIPTION
 \fITree\fP is a recursive directory listing program that produces a depth
@@ -123,6 +123,19 @@ Prints (implies -D) and formats the date according to the format string
 which uses the \fBstrftime\fP(3) syntax.
 .PP
 .TP
+.B --caseinsensitive
+If a match pattern is specified by the -P option, this will cause the pattern
+to match without regards to the case of each letter.
+.PP
+.TP
+.B --matchdirs
+If a match pattern is specified by the -P option, this will cause the pattern
+to be applied to directory names (in addition to filenames).  In the event of a
+match on the directory name, matching is disabled for the directory's
+contents. If the --prune option is used, empty folders that match the pattern
+will not be pruned.
+.PP
+.TP
 .B -o \fIfilename\fP
 Send output to \fIfilename\fP.
 .PP
@@ -314,6 +327,8 @@ Steve Baker (ice@mama.indstate.edu)
 HTML output hacked by Francesc Rocher (rocher@econ.udg.es)
 .br
 Charsets and OS/2 support by Kyosuke Tokoro (NBG01720@nifty.ne.jp)
+.br
+Directory and case matching by Jason A. Donenfeld (Jason@zx2c4.com)
 
 .SH BUGS AND NOTES
 Tree does not prune "empty" directories when the -P and -I options are used by
diff --git a/tree.c b/tree.c
index 19cf368..388aae5 100644
--- a/tree.c
+++ b/tree.c
@@ -19,16 +19,17 @@
 
 #include "tree.h"
 
-static char *version ="$Version: $ tree v1.6.0 (c) 1996 - 2011 by Steve Baker, Thomas Moore, Francesc Rocher, Kyosuke Tokoro $";
-static char *hversion="\t\t tree v1.6.0 %s 1996 - 2011 by Steve Baker and Thomas Moore <br>\n"
+static char *version ="$Version: $ tree v1.6.0 (c) 1996 - 2014 by Steve Baker, Thomas Moore, Francesc Rocher, Kyosuke Tokoro, Jason A. Donenfeld $";
+static char *hversion="\t\t tree v1.6.0 %s 1996 - 2014 by Steve Baker and Thomas Moore <br>\n"
 		      "\t\t HTML output hacked and copyleft %s 1998 by Francesc Rocher <br>\n"
-		      "\t\t Charsets / OS/2 support %s 2001 by Kyosuke Tokoro\n";
+		      "\t\t Charsets / OS/2 support %s 2001 by Kyosuke Tokoro <br>\n"
+		      "\t\t Directory and case matching %s 2014 by Jason A. Donenfeld\n";
 
 /* Globals */
 bool dflag, lflag, pflag, sflag, Fflag, aflag, fflag, uflag, gflag;
 bool qflag, Nflag, Qflag, Dflag, inodeflag, devflag, hflag, Rflag;
 bool Hflag, siflag, cflag, Xflag, duflag, pruneflag;
-bool noindent, force_color, nocolor, xdev, noreport, nolinks, flimit, dirsfirst, nosort;
+bool noindent, force_color, nocolor, xdev, noreport, nolinks, flimit, dirsfirst, nosort, matchdirs, caseinsensitive;
 char *pattern = NULL, *ipattern = NULL, *host = NULL, *title = "Directory Tree", *sp = " ";
 char *timefmt = NULL;
 const char *charset = NULL;
@@ -75,12 +76,13 @@ int main(int argc, char **argv)
   char sizebuf[64];
   off_t size = 0;
   mode_t mt;
+  bool needfulltree;
 
   q = p = dtotal = ftotal = 0;
   aflag = dflag = fflag = lflag = pflag = sflag = Fflag = uflag = gflag = FALSE;
   Dflag = qflag = Nflag = Qflag = Rflag = hflag = Hflag = siflag = cflag = FALSE;
   noindent = force_color = nocolor = xdev = noreport = nolinks = FALSE;
-  dirsfirst = nosort = inodeflag = devflag = Xflag = FALSE;
+  caseinsensitive = matchdirs = dirsfirst = nosort = inodeflag = devflag = Xflag = FALSE;
   duflag = pruneflag = FALSE;
   flimit = 0;
   dirs = xmalloc(sizeof(int) * (maxdirs=4096));
@@ -350,6 +352,16 @@ int main(int argc, char **argv)
 	      Dflag = TRUE;
 	      break;
 	    }
+	    if (!strncmp("--matchdirs",argv[i],11)) {
+	      j = strlen(argv[i])-1;
+	      matchdirs = TRUE;
+	      break;
+	    }
+	    if (!strncmp("--caseinsensitive",argv[i],17)) {
+	      j = strlen(argv[i])-1;
+	      caseinsensitive = TRUE;
+	      break;
+	    }
 	  }
 	default:
 	  fprintf(stderr,"tree: Invalid argument -`%c'.\n",argv[i][j]);
@@ -387,16 +399,17 @@ int main(int argc, char **argv)
   parse_dir_colors();
   initlinedraw(0);
 
+  needfulltree = duflag || pruneflag || matchdirs;
   /* Set our listdir function and sanity check options. */
   if (Hflag) {
-    listdir = (duflag || pruneflag)? html_rlistdir : html_listdir;
+    listdir = needfulltree ? html_rlistdir : html_listdir;
     Xflag = FALSE;
   } else if (Xflag) {
-    listdir = (duflag || pruneflag)? xml_rlistdir : xml_listdir;
+    listdir = needfulltree ? xml_rlistdir : xml_listdir;
     colorize = FALSE;
     colored = FALSE; /* Do people want colored XML output? */
   } else {
-    listdir = (duflag || pruneflag)? unix_rlistdir : unix_listdir;
+    listdir = needfulltree ? unix_rlistdir : unix_listdir;
   }
   if (dflag) pruneflag = FALSE;	/* You'll just get nothing otherwise. */
 
@@ -517,7 +530,8 @@ void usage(int n)
 	"usage: tree [-acdfghilnpqrstuvxACDFQNSUX] [-H baseHREF] [-T title ] [-L level [-R]]\n"
 	"\t[-P pattern] [-I pattern] [-o filename] [--version] [--help] [--inodes]\n"
 	"\t[--device] [--noreport] [--nolinks] [--dirsfirst] [--charset charset]\n"
-	"\t[--filelimit[=]#] [--si] [--timefmt[=]<f>] [<directory list>]\n");
+	"\t[--filelimit[=]#] [--si] [--timefmt[=]<f>] [--matchdirs]\n"
+	"\t[--caseinsensitive] [<directory list>]\n");
   if (n < 2) exit(0);
   fprintf(stderr,
 	"  ------- Listing options -------\n"
@@ -534,6 +548,8 @@ void usage(int n)
 	"  --charset X   Use charset X for terminal/HTML and indentation line output.\n"
 	"  --filelimit # Do not descend dirs with more than # files in them.\n"
 	"  --timefmt <f> Print and format time according to the format <f>.\n"
+	"  --matchdirs   Include directory names in -P pattern matching.\n"
+	"  --caseinsensitive Match files without regards to case in -P pattern matching.\n"
 	"  -o filename   Output to file instead of stdout.\n"
 	"  -------- File options ---------\n"
 	"  -q            Print non-printable characters as '?'.\n"
@@ -689,6 +705,8 @@ struct _info **getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **e
   struct _info **dir, **sav, **p, *sp;
   struct stat sb;
   int n;
+  u_long lev_tmp;
+  char *tmp_pattern = NULL, *start_rel_path;
   
   *err = NULL;
   if (Level >= 0 && lev > Level) return NULL;
@@ -696,7 +714,29 @@ struct _info **getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **e
     stat(d,&sb);
     dev = sb.st_dev;
   }
+
+  // if the directory name matches, turn off pattern matching for contents
+  if (matchdirs && pattern) {
+    lev_tmp = lev;
+    for (start_rel_path = d + strlen(d); start_rel_path != d; --start_rel_path) {
+      if (*start_rel_path == '/')
+        --lev_tmp;
+      if (lev_tmp <= 0) {
+        if (*start_rel_path)
+          ++start_rel_path;
+        break;
+      }
+    }
+    if (patmatch(start_rel_path,pattern) == 1) {
+      tmp_pattern = pattern;
+      pattern = NULL;
+    }
+  }
   sav = dir = read_dir(d,&n);
+  if (tmp_pattern) {
+    pattern = tmp_pattern;
+    tmp_pattern = NULL;
+  }
   if (dir == NULL) {
     *err = scopy("error opening dir");
     return NULL;
@@ -745,7 +785,9 @@ struct _info **getfulltree(char *d, u_long lev, dev_t dev, off_t *size, char **e
 	saveino((*dir)->inode, (*dir)->dev);
 	(*dir)->child = getfulltree(path,lev+1,dev,&((*dir)->size),&((*dir)->err));
       }
-      if (pruneflag && (*dir)->child == NULL) {
+      // prune empty folders, unless they match the requested pattern
+      if (pruneflag && (*dir)->child == NULL &&
+	  !(matchdirs && pattern && patmatch((*dir)->name,pattern) == 1)) {
 	sp = *dir;
 	for(p=dir;*p;p++) *p = *(p+1);
 	n--;
@@ -869,8 +911,19 @@ char *gnu_getcwd()
 }
 
 /*
+ * Returns a lower case version of the argument if case insensitive
+ * mode is enabled. Note that this is static and inline so that we
+ * do not incur penalty when this is running in a tight loop.
+ */
+static inline char cond_lower(char c)
+{
+  return caseinsensitive ? tolower(c) : c;
+}
+
+/*
  * Patmatch() code courtesy of Thomas Moore (dark@mama.indstate.edu)
  * '|' support added by David MacMahon (davidm@astron.Berkeley.EDU)
+ * Case insensitive support added by Jason A. Donenfeld (Jason@zx2c4.com)
  * returns:
  *    1 on a match
  *    0 on a mismatch
@@ -918,11 +971,11 @@ int patmatch(char *buf, char *pat)
 	  pat += 2;
 	  if(*pat == '\\' && *pat)
 	    pat++;
-	  if(*buf >= m && *buf <= *pat)
+	  if(cond_lower(*buf) >= cond_lower(m) && cond_lower(*buf) <= cond_lower(*pat))
 	    match = n;
 	  if(!*pat)
 	    pat--;
-	} else if(*buf == *pat) match = n;
+	} else if(cond_lower(*buf) == cond_lower(*pat)) match = n;
 	pat++;
       }
       buf++;
@@ -940,7 +993,7 @@ int patmatch(char *buf, char *pat)
       if(*pat)
 	pat++;
     default:
-      match = (*buf++ == *pat);
+      match = (cond_lower(*buf++) == cond_lower(*pat));
       break;
     }
     pat++;