summaryrefslogtreecommitdiff
path: root/appl/lib/filepat.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/lib/filepat.b')
-rw-r--r--appl/lib/filepat.b169
1 files changed, 169 insertions, 0 deletions
diff --git a/appl/lib/filepat.b b/appl/lib/filepat.b
new file mode 100644
index 00000000..adbf0e0a
--- /dev/null
+++ b/appl/lib/filepat.b
@@ -0,0 +1,169 @@
+implement Filepat;
+
+include "sys.m";
+ sys: Sys;
+
+include "readdir.m";
+ rdir: Readdir;
+
+include "filepat.m";
+
+expand(pat: string): list of string
+{
+ if(sys == nil){
+ sys = load Sys Sys->PATH;
+ }
+ if(rdir == nil){
+ rdir = load Readdir Readdir->PATH;
+ }
+ (nil, elem) := sys->tokenize(pat, "/");
+ if(elem == nil)
+ return filepat1(pat, nil, 0);
+
+ files: list of string;
+ if(pat[0] == '/')
+ files = "/" :: nil;
+
+ while(elem != nil){
+ files = filepat1(hd elem, files, tl elem!=nil);
+ if(files == nil)
+ break;
+ elem = tl elem;
+ }
+ return files;
+}
+
+filepat1(pat: string, files: list of string, mustbedir: int): list of string
+{
+ if(files == nil)
+ return filepatdir(pat, "", nil, mustbedir);
+
+ # reverse list; will rebuild in forward order
+ r: list of string;
+ while(files != nil){
+ r = hd files :: r;
+ files = tl files;
+ }
+ files = r;
+
+ nfiles: list of string = nil;
+ while(files != nil){
+ nfiles = filepatdir(pat, hd files, nfiles, mustbedir);
+ files = tl files;
+ }
+ return nfiles;
+}
+
+filepatdir(pat: string, dir: string, files: list of string, mustbedir: int): list of string
+{
+ if(pat=="." || pat=="..") {
+ if(dir=="/" || dir=="")
+ files = (dir + pat) :: files;
+ else
+ files = (dir + "/" + pat) :: files;
+ return files;
+ }
+ dirname := dir;
+ if(dir == "")
+ dirname = ".";
+ # sort into descending order means resulting list will ascend
+ (d, n) := rdir->init(dirname, rdir->NAME|rdir->DESCENDING|rdir->COMPACT);
+ if(d == nil)
+ return files;
+
+ # suppress duplicates
+ for(i:=1; i<n; i++)
+ if(d[i-1].name == d[i].name){
+ d[i-1:] = d[i:];
+ n--;
+ i--;
+ }
+
+ for(i=0; i<n; i++){
+ if(match(pat, d[i].name) && (mustbedir==0 || (d[i].mode&Sys->DMDIR))){
+ if(dir=="/" || dir=="")
+ files = (dir + d[i].name) :: files;
+ else
+ files = (dir + "/" + d[i].name) :: files;
+ }
+ }
+ return files;
+}
+
+match(pat, name: string): int
+{
+ n := 0;
+ p := 0;
+ while(p < len pat){
+ r := pat[p++];
+ case r{
+ '*' =>
+ pat = pat[p:];
+ if(len pat==0)
+ return 1;
+ for(; n<=len name; n++)
+ if(match(pat, name[n:]))
+ return 1;
+ return 0;
+ '[' =>
+ if(n == len name)
+ return 0;
+ s := name[n++];
+ matched := 0;
+ invert := 0;
+ first := 1;
+ esc: int;
+ while(p < len pat){
+ (p, r, esc) = char(pat, p);
+ if(first && !esc && r=='^'){
+ invert = 1;
+ first = 0;
+ continue;
+ }
+ first = 0;
+ if(!esc && r==']')
+ break;
+ lo, hi: int;
+ (p, lo, hi) = range(pat, p-1);
+ if(lo<=s && s<=hi)
+ matched = 1;
+ }
+ if(!(!esc && r==']') || invert==matched)
+ return 0;
+ '?' =>
+ if(n==len name)
+ return 0;
+ n++;
+ '\\' =>
+ if(n==len name || p==len pat || pat[p++]!=name[n++])
+ return 0;
+ * =>
+ if(n==len name || r!=name[n++])
+ return 0;
+ }
+ }
+ return n == len name;
+}
+
+# return character or range (a-z)
+range(pat: string, p: int): (int, int, int)
+{
+ (q, lo, nil) := char(pat, p);
+ (q1, hi, esc) := char(pat, q);
+ if(!esc && hi=='-'){
+ (q1, hi, nil) = char(pat, q1);
+ return (q1, lo, hi);
+ }
+ return (q, lo, lo);
+}
+
+# return possibly backslash-escaped next character
+char(pat: string, p: int): (int, int, int)
+{
+ if(p == len pat)
+ return (p, 0, -1);
+ r := pat[p++];
+ if(p==len pat || r!='\\')
+ return (p, r, 0);
+ return (p+1, pat[p], 1);
+}