MODULE SearchTools;
IMPORT
Streams, Commands, Options, Files, Strings, UTF8Strings, Texts, TextUtilities;
TYPE
SearchString = ARRAY 256 OF CHAR;
SearchStringUCS = ARRAY 256 OF Texts.Char32;
Parameters = POINTER TO RECORD
repeat: BOOLEAN;
END;
PatternParameters = POINTER TO RECORD (Parameters)
searchString : SearchString;
replaceString : SearchString;
END;
Statistics = OBJECT
VAR
nofFiles : LONGINT;
nofMatches, nofConflicts, nofErrors : LONGINT;
verbose : BOOLEAN;
abort : BOOLEAN;
PROCEDURE &Reset*;
BEGIN
nofFiles := 0;
nofMatches := 0; nofErrors := 0; nofConflicts := 0;
verbose := FALSE; abort := FALSE;
END Reset;
PROCEDURE Show(w : Streams.Writer);
BEGIN
w.Int(nofMatches, 0); w.String(" matches of "); w.Int(nofFiles, 0); w.String(" files");
IF (nofConflicts > 0) THEN w.String(", "); w.Int(nofConflicts, 0); w.String(" conflict(s)"); END;
IF (nofErrors > 0) THEN w.String(", "); w.Int(nofErrors, 0); w.String(" error(s)"); END;
w.String("."); w.Ln;
END Show;
END Statistics;
EnumProc = PROCEDURE (CONST filename : Files.FileName; param : Parameters; stats : Statistics; context : Commands.Context);
PROCEDURE FindString(CONST filename : Files.FileName; param : Parameters; stats : Statistics; context : Commands.Context);
VAR text : Texts.Text; pos, format, res : LONGINT; searchString : SearchStringUCS; idx : LONGINT; nbrOfHits : LONGINT;
BEGIN
ASSERT(param IS PatternParameters);
nbrOfHits := 0;
idx := 0;
WITH param:PatternParameters DO
UTF8Strings.UTF8toUnicode(param.searchString, searchString, idx);
END;
NEW(text);
TextUtilities.LoadAuto(text, filename, format, res);
IF (res = 0) THEN
text.AcquireRead;
pos := TextUtilities.Pos(searchString, 0, text);
WHILE (pos > 0) DO
INC(nbrOfHits);
pos := TextUtilities.Pos(searchString, pos + 1, text);
END;
text.ReleaseRead;
IF (nbrOfHits > 0) THEN
INC(stats.nofMatches);
context.out.String(filename);
IF stats.verbose THEN
context.out.String(" ("); context.out.Int(nbrOfHits, 0); context.out.String(" hits"); context.out.String(")");
END;
context.out.Ln;
END;
ELSE
INC(stats.nofErrors);
context.error.String("Coult not load text: "); context.error.String(filename); context.error.Ln;
END;
END FindString;
PROCEDURE ReplaceString(CONST filename : Files.FileName; param : Parameters; stats : Statistics; context : Commands.Context);
VAR
text : Texts.Text; pos, format, res : LONGINT;
searchString, replaceString : SearchStringUCS; idx : LONGINT;
searchStringLen, replaceStringLen : LONGINT;
replaceCount : LONGINT;
conflict : BOOLEAN;
PROCEDURE Replace(pos, len : LONGINT; CONST replString : SearchStringUCS);
BEGIN
text.Delete(pos, len);
text.InsertUCS32(pos, replString);
len := TextUtilities.UCS32StrLength(replString);
END Replace;
BEGIN
ASSERT(param IS PatternParameters);
replaceCount := 0; conflict := FALSE;
WITH param:PatternParameters DO
idx := 0; UTF8Strings.UTF8toUnicode(param.searchString, searchString, idx);
idx := 0; UTF8Strings.UTF8toUnicode(param.replaceString, replaceString, idx);
END;
searchStringLen := TextUtilities.UCS32StrLength(searchString);
replaceStringLen := TextUtilities.UCS32StrLength(replaceString);
NEW(text);
TextUtilities.LoadAuto(text, filename, format, res);
IF (res = 0) THEN
text.AcquireWrite;
pos := TextUtilities.Pos(replaceString, 0, text);
IF (pos > 0) THEN INC(stats.nofConflicts); conflict := TRUE; END;
pos := TextUtilities.Pos(searchString, 0, text);
WHILE (pos > 0) DO
INC(replaceCount);
Replace(pos, searchStringLen, replaceString);
pos := TextUtilities.Pos(searchString, pos + replaceStringLen, text);
END;
text.ReleaseWrite;
IF (replaceCount > 0) THEN
INC(stats.nofMatches);
context.out.String(filename);
IF stats.verbose THEN
context.out.String(" ("); context.out.Int(replaceCount, 0); context.out.String(" replacements)");
IF conflict THEN context.out.String(" CONFLICT"); END;
END;
context.out.Ln;
res := -1;
IF (format = 0) THEN TextUtilities.StoreOberonText(text, filename, res);
ELSIF (format = 1) THEN TextUtilities.StoreText(text, filename, res);
ELSIF (format = 2) THEN TextUtilities.ExportUTF8(text, filename, res);
ELSE
INC(stats.nofErrors);
context.error.String("Could not store text: "); context.error.String(filename);
context.error.String(" (Format unknown)"); context.error.Ln;
END;
IF (res # 0) THEN
INC(stats.nofErrors);
context.error.String("Could not store text: "); context.error.String(filename); context.error.Ln;
END;
END;
ELSE
INC(stats.nofErrors);
context.error.String("Could not load text: "); context.error.String(filename); context.error.Ln;
END;
END ReplaceString;
PROCEDURE FindStringRaw(CONST filename : Files.FileName; param : Parameters; stats : Statistics; context : Commands.Context);
VAR
r : Files.Reader;
f : Files.File;
m: LONGINT;
p : PatternParameters;
BEGIN
ASSERT(param IS PatternParameters);
p := param (PatternParameters);
m := Strings.Length(p.searchString);
f := Files.Old(filename);
IF f # NIL THEN
Files.OpenReader(r, f, 0);
SearchPatternRaw(r,NIL, p.searchString);
IF r.res=0 THEN
context.out.String(filename); context.out.Ln; context.out.Update;
RETURN;
END;
ELSE
context.error.String("Could not open file "); context.error.String(filename); context.error.Ln;
context.error.Update;
END
END FindStringRaw;
PROCEDURE SearchPatternRaw*(r : Streams.Reader; w: Streams.Writer; CONST pattern: ARRAY OF CHAR);
VAR
d : ARRAY 256 OF LONGINT;
cb : Strings.String;
pos, cpos, i, j, k, m, shift : LONGINT;
BEGIN
m := Strings.Length(pattern);
NEW(cb, m);
WHILE (r.res = 0 ) & (cpos < m) DO
cb[cpos] := r.Get();
INC(cpos);
END;
IF r.res = 0 THEN
FOR i := 0 TO 255 DO d[i] := m END;
FOR i := 0 TO m-2 DO d[ORD(pattern[i])] := m - i - 1 END;
i := m;
LOOP
j := m; k := i;
REPEAT DEC(k); DEC(j);
UNTIL (j < 0) OR (pattern[j] # cb[k MOD m]);
IF j<0 THEN EXIT END;
shift := d[ORD(cb[(i-1) MOD m])];
i := i + shift;
WHILE (cpos < i) & (r.res = 0) DO
pos:=cpos MOD m;
IF w#NIL THEN w.Char(cb[pos]);END;
cb[pos] := r.Get();
INC(cpos);
END;
IF r.res#0 THEN EXIT END;
END;
IF w#NIL THEN w.Update END;
END;
END SearchPatternRaw;
PROCEDURE Enumerate(CONST pattern : ARRAY OF CHAR; param : Parameters; proc : EnumProc; stats : Statistics; context : Commands.Context);
VAR
enum : Files.Enumerator;
filename : Files.FileName;
fileflags : SET;
time, date, size, nofMatches : LONGINT;
BEGIN
ASSERT(proc # NIL);
NEW(enum); enum.Open(pattern, {});
WHILE enum.GetEntry(filename, fileflags, time, date, size) & ~stats.abort DO
IF ~(Files.Directory IN fileflags) THEN
REPEAT
nofMatches := stats.nofMatches;
proc(filename, param, stats, context);
UNTIL ~param.repeat OR (stats.nofMatches = nofMatches);
context.out.Update; context.error.Update;
INC(stats.nofFiles);
END;
END;
enum.Close;
END Enumerate;
PROCEDURE Unescape (CONST source: ARRAY OF CHAR; VAR dest: ARRAY OF CHAR);
VAR si, di: LONGINT;
BEGIN
si := 0; di := 0;
WHILE source[si] # 0X DO
IF (source[si] = '\') & (source[si + 1] # 0X) THEN
INC (si);
CASE source[si] OF
| 't': dest[di] := 09X;
| 'n': dest[di] := 0AX;
| 'r': dest[di] := 0DX;
| 'w': dest[di] := 20X;
ELSE dest[di] := source[si];
END;
ELSE
dest[di] := source[si];
END;
INC (si); INC (di);
END;
dest[di] := 0X;
END Unescape;
PROCEDURE Find*(context : Commands.Context);
VAR
options : Options.Options;
filePattern : Files.FileName; searchString : SearchString;
param : PatternParameters;
stats : Statistics;
BEGIN
NEW(options);
options.Add("v", "verbose", Options.Flag);
options.Add("f", "formatted", Options.Flag);
options.Add("r", "repeat", Options.Flag);
IF options.Parse(context.arg, context.error) THEN
filePattern := ""; searchString := "";
context.arg.SkipWhitespace; context.arg.String(filePattern);
context.arg.SkipWhitespace; context.arg.String(searchString);
IF (searchString # "") THEN
NEW(stats);
stats.verbose := options.GetFlag("verbose");
NEW(param);
param.repeat := options.GetFlag("repeat");
Unescape(searchString, param.searchString);
IF stats.verbose THEN
context.out.String("Searching '"); context.out.String(searchString); context.out.String("' in ");
context.out.String(filePattern); context.out.String("..."); context.out.Ln; context.out.Update;
END;
IF options.GetFlag("formatted") THEN
Enumerate(filePattern, param, FindString, stats, context);
ELSE
Enumerate(filePattern, param, FindStringRaw, stats, context);
END;
IF stats.verbose THEN
stats.Show(context.out);
END;
ELSE
context.error.String("No valid search string parameter"); context.error.Ln;
END;
END;
END Find;
PROCEDURE Replace*(context : Commands.Context);
VAR
options : Options.Options;
filePattern : Files.FileName; searchString, replaceString : SearchString;
param : PatternParameters;
stats : Statistics;
BEGIN
NEW(options);
options.Add("v", "verbose", Options.Flag);
options.Add("r", "repeat", Options.Flag);
IF options.Parse(context.arg, context.error) THEN
filePattern := ""; searchString := ""; replaceString := "";
context.arg.SkipWhitespace; context.arg.String(filePattern);
context.arg.SkipWhitespace; context.arg.String(searchString);
context.arg.SkipWhitespace; context.arg.String(replaceString);
IF (searchString # "") THEN
NEW(stats);
stats.verbose := options.GetFlag("verbose");
NEW(param);
param.repeat := options.GetFlag("repeat");
Unescape(searchString, param.searchString);
Unescape(replaceString, param.replaceString);
IF stats.verbose THEN
context.out.String("Replacing '"); context.out.String(searchString); context.out.String("' by '"); context.out.String(replaceString);
context.out.String("' in "); context.out.String(filePattern); context.out.String("... "); context.out.Ln;
END;
Enumerate(filePattern, param, ReplaceString, stats, context);
IF stats.verbose THEN
stats.Show(context.out);
END;
ELSE
context.error.String("No valid search string parameter"); context.error.Ln;
END;
END;
END Replace;
END SearchTools.
SearchTools.Find E:/WinaosNewCommands/source/*.Mod Objects ~
SearchTools.Find E:/WinaosNewCommands/winaos/src/*.Mod Commands ~
SearchTools.Replace E:/WinaosNewCommands/source/*.Mod AosCommands Commands ~
SearchTools.Replace E:/WinaosNewCommands/winaos/src/*.Mod AosCommands Commands ~
SystemTools.FreeDownTo SearchTools ~