From 59d7a2237500c9439b758910e951bcd5dc48c657 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" 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
\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
\n" "\t\t HTML output hacked and copyleft %s 1998 by Francesc Rocher
\n" - "\t\t Charsets / OS/2 support %s 2001 by Kyosuke Tokoro\n"; + "\t\t Charsets / OS/2 support %s 2001 by Kyosuke Tokoro
\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[=]] []\n"); + "\t[--filelimit[=]#] [--si] [--timefmt[=]] [--matchdirs]\n" + "\t[--caseinsensitive] []\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 Print and format time according to the format .\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++; -- 1.9.2