Package: src/packages/filetools.fdoc
File Tools¶
key | file |
---|---|
flx_ls.flx | $PWD/src/tools/flx_ls.flx |
flx_cp_tool.flx | $PWD/src/tools/flx_cp.flx |
flx_grep.flx | $PWD/src/tools/flx_grep.flx |
flx_replace.flx | $PWD/src/tools/flx_replace.flx |
flx_batch_replace.flx | $PWD/src/tools/flx_batch_replace.flx |
flx_renumber.flx | $PWD/src/tools/flx_renumber.flx |
flx_cp.flx | share/lib/std/felix/flx_cp.flx |
File System Tools.¶
The tools perform basic tasks the same way on all platforms. Our tools use RE2 (Perl) regular expressions for wildcarding instead of globs. All tools treat the file system as flat: directories don’t exist. Structured filenames do. Tools creating files always auto-create directories for this reason.
Most of the tools are stubs wrapping core library functionality.
Note: the regular expressions must match completely.
File list flx_ls
.¶
List all files in a given master directory matching the specified pattern. The resulting list is relative to the master directory.
Note: regular expressions must match completely!
//[flx_ls.flx]
fun dbg(s:string):string={ println s; return s; }
//println$ System::args ();
//println$ "argc=" + str System::argc;
var dir =
if System::argc < 2 then Directory::getcwd()
else System::argv 1
endif
;
var regex =
if System::argc < 3 then ".*"
else System::argv 2
endif
;
//println$ "Dir=" dir;
//println$ "Files in dir " + dir + "=";
iter (proc (s:string) { println s; }) (FileSystem::regfilesin (dir, regex));
File copy flx_cp
.¶
This tool copies files using regular expressions with a replacement pattern. The tool is safe in that it guarrantees the copy is one-to-one and the target files do not overlap the source files. If this condition isn’t met the copy fails as a whole.
The copy is done like flx_ls
scanning for structured
filenames in a given master directory matching a given
pattern. The destination replacement pattern is must include
any required prefix (the master directory is only used for
searching as an optimisation). The encoding \n
where
n is a digit from 0 to 9 represents a subgroup of the match.
Note: regular expressions must match completely!
//[flx_cp_tool.flx]
include "std/felix/flx_cp";
fun dbg(s:string):string={ println s; return s; }
//println$ System::args ();
//println$ "argc=" + str System::argc;
var dir = "";
var regex = "";
var target = "";
var live = true;
var verbose = false;
for var i in 1 upto System::argc do
var arg = System::argv i;
if arg == "--test" do live = false;
elif arg == "-v" or arg == "--verbose" do verbose = true;
elif arg.[0] == char "-" do
println$ "Unknown option '" + arg+"'";
System::exit(1);
elif dir == "" do dir = arg;
elif regex == "" do regex = arg;
elif target == "" do target = arg;
done
done
if dir == "" do println$ "Missing directory name (arg1)"; System::exit(1);
elif regex == "" do println$ "Missing regex (arg2)"; System::exit(1);
elif target == "" do println$ "Missing target (arg3)"; System::exit(1);
done
if verbose do println$ "#Dir='" + dir + "', pattern='"+regex+"', dst='"+target+"'"; done
var re = Re2::RE2 regex;
CopyFiles::copyfiles (dir, re, target, live, verbose);
System::exit(0);
//[flx_cp.flx]
class CopyFiles {
proc processfiles
(var process: string * string -> bool)
(basedir:string, re:RE2, tpat:string, live:bool, verbose:bool)
{
var ds = StrDict::strdict[string] ();
var sd = StrDict::strdict[string] ();
var dirs = StrDict::strdict[bool] ();
var n = re.NumberOfCapturingGroups;
var v = varray[StringPiece]$ (n+1).size, StringPiece "";
//println$ "flx_cp:CopyFiles:processfiles regexp= " + re.pattern;
// Process a single filename and add it to the pending copy queue
proc addfile(f:string)
{
if Re2::Match(re, StringPiece f, 0, ANCHOR_BOTH, v.stl_begin, v.len.int)
do
var src = Filename::join (basedir, f);
var replacements = Empty[string * string];
for var k in 0 upto n do
replacements = Cons (("${" + str k + "}",v.k.string), replacements);
done
dst := search_and_replace replacements tpat;
//println$ "Copy " + src + " -> " + dst;
sd.add src dst;
if ds.haskey dst do
eprintln$ "Duplicate target " + dst;
System::exit(1);
done
ds.add dst src;
iter
(proc (x:string) { dirs.add x true; })
(Filename::directories dst)
;
done
}
// Recursively collect files within dir to be copied. dir is relative to basedir.
proc rfi(dir: string)
{
if dir != "." and dir != ".." do
match Directory::filesin(Filename::join (basedir,dir)) with
| #None => ;
| Some files =>
List::iter
(proc (f:string)
{ if f != "." and f != ".." do
var d = Filename::join (dir,f);
val t = FileStat::filetype (Filename::join (basedir,d));
match t with
| #REGULAR => addfile d;
| #DIRECTORY => rfi d;
| _ => ;
endmatch;
done
}
)
files
;
endmatch;
done
}
rfi ("");
// Check that no source file is clobbered
match src, dst in sd.iterator do
if sd.haskey dst do
eprintln$ "Target clobbers src: " + dst;
System::exit(1);
done
done
// Create target directories
match dir, _ in dirs.iterator do
if verbose do println$ "mkdir " + dir; done
if live do
err:=Directory::mkdir(dir);
if err !=0 do
if errno != EEXIST do
eprintln$ "Mkdir, err=" + strerror() + " .. ignoring";
done
done
done
done
// And finally, do the actual copying
match src, dst in sd.iterator do
if verbose do print$ "cp " + src + " " + dst; done
if live do
if process(src, dst) do
if verbose do println " #done"; done
else
eprintln "COPY FAILED";
System::exit 1;
done
else
if verbose do println$ " #proposed"; done
done
done
}
proc copyfiles(basedir:string, re:RE2, tpat:string, live:bool, verbose:bool) =>
processfiles (FileSystem::filecopy) (basedir, re, tpat, live, verbose)
;
proc copyfiles(basedir:string, re:string, tpat:string, live:bool, verbose:bool) =>
copyfiles(basedir, RE2 re, tpat, live, verbose)
;
}
Searching for strings flx_grep
.¶
This tool works like grep except the files being searched use a master directory and regular expression for selection. Any line in any of those files matching the given regexp completely are listed.
//[flx_grep.flx]
var dir =
if System::argc < 2 then Directory::getcwd()
else System::argv 1
endif
;
var fregex =
if System::argc < 3 then ".*"
else System::argv 2
endif
;
var lregex =
if System::argc < 4 then ".*"
else System::argv 3
endif
;
var grexp = RE2 lregex;
//println$ "Dir=" dir;
//println$ "Files in dir " + dir + "=";
for file in FileSystem::regfilesin (dir, fregex) do
// println$ file;
var lines = load (Filename::join dir file);
var count = 0;
for line in split (lines,char "\n") do
++count;
if line \in grexp do
println$ file+":"+str count+": " line;
done
done
done
Replace substrings in a file.¶
This tool replaces patterns found in a single file with another pattern and outputs the result to standard output.
//[flx_replace.flx]
var filename = System::argv 1;
var re = System::argv 2;
var r = System::argv 3;
if System::argc != 4 do
println$ "Usage: flx_replace filename regexp replacement";
println$ " replacement may contain \\1 \\2 etc for matching subgroups";
System::exit 1;
done
var x = load filename;
var cre = RE2 re;
var result = search_and_replace (x, 0uz, cre, r);
print result;
Batch Replace¶
This program combines flx_cp
and flx_replace
to perform
a wildcarded safe copy of a set of files from one location
to another with renaming, and also replaces any lines in
any of the files matching some pattern with another string
specified by a replacement.
//[flx_batch_replace.flx]
include "std/felix/flx_cp";
fun dbg(s:string):string={ println s; return s; }
//println$ System::args ();
//println$ "argc=" + str System::argc;
var dir = "";
var regex = "";
var target = "";
var search = "";
var replace = "";
var live = true;
var verbose = false;
for var i in 1 upto System::argc do
var arg = System::argv i;
if arg == "--test" do live = false;
elif arg == "-v" or arg == "--verbose" do verbose = true;
elif arg.[0] == char "-" do
println$ "Unknown option '" + arg+"'";
System::exit(1);
elif dir == "" do dir = arg;
elif regex == "" do regex = arg;
elif target == "" do target = arg;
elif search == "" do search = arg;
elif replace == "" do replace = arg;
done
done
if dir == "" do println$ "Missing directory name (arg1)"; System::exit(1);
elif regex == "" do println$ "Missing regex (arg2)"; System::exit(1);
elif target == "" do println$ "Missing target (arg3)"; System::exit(1);
elif search == "" do println$ "Missing search regex (arg4)"; System::exit(1);
elif replace == "" do println$ "Missing replace spec (arg5)"; System::exit(1);
done
if verbose do println$ "#Dir='" + dir + "', pattern='"+regex+"', dst='"+target+"'"; done
var searchre = RE2 search;
gen sandr (src: string, dst:string) =
{
var text = load src;
var result = search_and_replace (text, 0uz, searchre, replace);
save (dst, result);
return true;
}
var filere = Re2::RE2 regex;
CopyFiles::processfiles sandr (dir, filere, target, live, verbose);
System::exit(0);
Renumbering.¶
This tool analyses a single directory looking for files whose basename matches a pattern containing a number in a fixed position.
It then renumbers all the files with a number greater or equal to a specified value, adding or subtracting a certain amount to make space in the sequence or fill a gap in it.
It was designed for document renumbering, especially Felix
tutorial documents, since the Felix webserver automatically
calculates Next and Prev links when it asked to display
an fdoc
file with a numerical suffix of two digits.
However it can be used on any sequenced file set.
//[flx_renumber.flx]
// File renumbering
if System::argc < 4 do
println "Usage: rentut dir regexp first dst";
println "For tutorial try:";
println r" dir = 'src/web'";
println r" re = 'tut_(\d*)\\.fdoc'";
System::exit(1);
done
s_dir := System::argv 1;
s_re := System::argv 2;
s_first := System::argv 3;
s_moveto := System::argv 4;
first := size s_first;
moveto := size s_moveto;
re := RE2(s_re);
if first == moveto do
println$ "src = dst, not moving anything";
System::exit 0;
done
println$ "Renumber files in " + s_dir+ " matching "+"'"+s_re+"'"+" from " + str first + " to " + str moveto;
docs := FileSystem::regfilesin(s_dir, re);
var files = varray docs;
// direction: if first < moveto, we're moving up, so we have to start at the end and work down.
// if first > moveto, we're moving down, so we have to start at the start and work up.
comparator := if first < moveto then \gt of (string * string) else \lt of (string * string) endif;
sort comparator of (string * string) files;
println$ "Files = " + str files;
var groups : array[StringPiece,2];
iter
(proc(var f:string){
println f;
res := Match(re, StringPiece f,0,ANCHOR_BOTH,C_hack::cast[+StringPiece] (&groups),2);
if res do
//println$ "Group 1 = " + str (groups.1);
n := size (str (groups.1));
if n >= first do
m := n + moveto - first;
s := f"%02d" m.int;
soffset := groups.1.data - (&f).stl_begin;
var newf = f;
replace(&newf,soffset.size,2uz,s);
res2 := FileSystem::rename_file(
Filename::join (s_dir,f),
Filename::join (s_dir,newf)
);
if res2 != 0 do
println$ "Rename " + f + " -> " + newf + " failed";
else
println$ f + " -> " + newf;
done
else
// println$ str n + " Unchanged";
done
else
println "NO match";
done
})
files;