SearchTools.Mod 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. MODULE SearchTools; (** AUTHOR "staubesv"; PURPOSE "Some simple search tools"; *)
  2. IMPORT
  3. Streams, Commands, Options, Files, Strings, UTF8Strings, Texts, TextUtilities;
  4. TYPE
  5. SearchString = ARRAY 256 OF CHAR;
  6. SearchStringUCS = ARRAY 256 OF Texts.Char32;
  7. Parameters = POINTER TO RECORD
  8. repeat: BOOLEAN;
  9. subdirectory: BOOLEAN;
  10. END;
  11. PatternParameters = POINTER TO RECORD (Parameters)
  12. searchString : SearchString;
  13. replaceString : SearchString;
  14. END;
  15. Statistics = OBJECT
  16. VAR
  17. nofFiles : LONGINT;
  18. nofMatches, nofConflicts, nofErrors : LONGINT;
  19. verbose : BOOLEAN;
  20. abort : BOOLEAN;
  21. PROCEDURE &Reset*;
  22. BEGIN
  23. nofFiles := 0;
  24. nofMatches := 0; nofErrors := 0; nofConflicts := 0;
  25. verbose := FALSE; abort := FALSE;
  26. END Reset;
  27. PROCEDURE Show(w : Streams.Writer);
  28. BEGIN
  29. w.Int(nofMatches, 0); w.String(" matches of "); w.Int(nofFiles, 0); w.String(" files");
  30. IF (nofConflicts > 0) THEN w.String(", "); w.Int(nofConflicts, 0); w.String(" conflict(s)"); END;
  31. IF (nofErrors > 0) THEN w.String(", "); w.Int(nofErrors, 0); w.String(" error(s)"); END;
  32. w.String("."); w.Ln;
  33. END Show;
  34. END Statistics;
  35. EnumProc = PROCEDURE (CONST filename : Files.FileName; param : Parameters; stats : Statistics; context : Commands.Context);
  36. PROCEDURE FindString(CONST filename : Files.FileName; param : Parameters; stats : Statistics; context : Commands.Context);
  37. VAR text : Texts.Text; pos, format: LONGINT; res : WORD; searchString : SearchStringUCS; idx : LONGINT; nbrOfHits : LONGINT;
  38. BEGIN
  39. ASSERT(param IS PatternParameters);
  40. nbrOfHits := 0;
  41. idx := 0;
  42. WITH param:PatternParameters DO
  43. UTF8Strings.UTF8toUnicode(param.searchString, searchString, idx);
  44. END;
  45. NEW(text);
  46. TextUtilities.LoadAuto(text, filename, format, res);
  47. IF (res = 0) THEN
  48. text.AcquireRead;
  49. pos := TextUtilities.Pos(searchString, 0, text);
  50. WHILE (pos > 0) DO
  51. INC(nbrOfHits);
  52. pos := TextUtilities.Pos(searchString, pos + 1, text);
  53. END;
  54. text.ReleaseRead;
  55. IF (nbrOfHits > 0) THEN
  56. INC(stats.nofMatches);
  57. context.out.String(filename);
  58. IF stats.verbose THEN
  59. context.out.String(" ("); context.out.Int(nbrOfHits, 0); context.out.String(" hits"); context.out.String(")");
  60. END;
  61. context.out.Ln;
  62. END;
  63. ELSE
  64. INC(stats.nofErrors);
  65. context.error.String("Coult not load text: "); context.error.String(filename); context.error.Ln;
  66. END;
  67. END FindString;
  68. PROCEDURE ReplaceString(CONST filename : Files.FileName; param : Parameters; stats : Statistics; context : Commands.Context);
  69. VAR
  70. text : Texts.Text; pos, format: LONGINT; res: WORD;
  71. searchString, replaceString : SearchStringUCS; idx : LONGINT;
  72. searchStringLen, replaceStringLen : LONGINT;
  73. replaceCount : LONGINT;
  74. conflict : BOOLEAN;
  75. (* Replace string at position <pos> of length <len> with <replString> *)
  76. PROCEDURE Replace(pos, len : LONGINT; CONST replString : SearchStringUCS);
  77. BEGIN
  78. text.Delete(pos, len);
  79. text.InsertUCS32(pos, replString);
  80. len := TextUtilities.UCS32StrLength(replString);
  81. END Replace;
  82. BEGIN
  83. ASSERT(param IS PatternParameters);
  84. replaceCount := 0; conflict := FALSE;
  85. WITH param:PatternParameters DO
  86. idx := 0; UTF8Strings.UTF8toUnicode(param.searchString, searchString, idx);
  87. idx := 0; UTF8Strings.UTF8toUnicode(param.replaceString, replaceString, idx);
  88. END;
  89. searchStringLen := TextUtilities.UCS32StrLength(searchString);
  90. replaceStringLen := TextUtilities.UCS32StrLength(replaceString);
  91. NEW(text);
  92. TextUtilities.LoadAuto(text, filename, format, res);
  93. IF (res = 0) THEN
  94. text.AcquireWrite;
  95. pos := TextUtilities.Pos(replaceString, 0, text);
  96. IF (pos > 0) THEN INC(stats.nofConflicts); conflict := TRUE; END;
  97. pos := TextUtilities.Pos(searchString, 0, text);
  98. WHILE (pos > 0) DO
  99. INC(replaceCount);
  100. Replace(pos, searchStringLen, replaceString);
  101. pos := TextUtilities.Pos(searchString, pos + replaceStringLen, text);
  102. END;
  103. text.ReleaseWrite;
  104. IF (replaceCount > 0) THEN
  105. INC(stats.nofMatches);
  106. context.out.String(filename);
  107. IF stats.verbose THEN
  108. context.out.String(" ("); context.out.Int(replaceCount, 0); context.out.String(" replacements)");
  109. IF conflict THEN context.out.String(" CONFLICT"); END;
  110. END;
  111. context.out.Ln;
  112. res := -1;
  113. IF (format = 0) THEN TextUtilities.StoreOberonText(text, filename, res);
  114. ELSIF (format = 1) THEN TextUtilities.StoreText(text, filename, res);
  115. ELSIF (format = 2) THEN TextUtilities.ExportUTF8(text, filename, res);
  116. ELSE
  117. INC(stats.nofErrors);
  118. context.error.String("Could not store text: "); context.error.String(filename);
  119. context.error.String(" (Format unknown)"); context.error.Ln;
  120. END;
  121. IF (res # 0) THEN
  122. INC(stats.nofErrors);
  123. context.error.String("Could not store text: "); context.error.String(filename); context.error.Ln;
  124. END;
  125. END;
  126. ELSE
  127. INC(stats.nofErrors);
  128. context.error.String("Could not load text: "); context.error.String(filename); context.error.Ln;
  129. END;
  130. END ReplaceString;
  131. (* Boyer-Moore match for streams. This procedure opens a file as character stream and does not take special care of formatting information.
  132. It also doesn't do statistics but just outputs the filename if a match occurs and returns after the first match *)
  133. PROCEDURE FindStringRaw(CONST filename : Files.FileName; param : Parameters; stats : Statistics; context : Commands.Context);
  134. VAR
  135. r : Files.Reader;
  136. f : Files.File;
  137. m: LONGINT;
  138. p : PatternParameters;
  139. BEGIN
  140. ASSERT(param IS PatternParameters);
  141. p := param (PatternParameters);
  142. m := Strings.Length(p.searchString);
  143. f := Files.Old(filename);
  144. IF f # NIL THEN
  145. Files.OpenReader(r, f, 0);
  146. SearchPatternRaw(r,NIL, p.searchString);
  147. IF r.res=0 THEN
  148. context.out.String(filename); context.out.Ln; context.out.Update;
  149. RETURN;
  150. END;
  151. ELSE
  152. context.error.String("Could not open file "); context.error.String(filename); context.error.Ln;
  153. context.error.Update;
  154. END
  155. END FindStringRaw;
  156. (* Boyer-Moore match for streams. This procedure opens a file as character stream and does not take special care of formatting information.
  157. It also doesn't do statistics but just outputs the filename if a match occurs and returns after the first match.
  158. If a Streams.Writer is provided, it is fed with the data in the interval before each <pattern> location.
  159. Postcondition:
  160. - If pattern is found, r.res=0 and Reader is positioned after <pattern> ; otherwise r.res#0 *)
  161. PROCEDURE SearchPatternRaw*(r : Streams.Reader; w: Streams.Writer; CONST pattern: ARRAY OF CHAR);
  162. VAR
  163. d : ARRAY 256 OF LONGINT;
  164. cb : Strings.String;
  165. pos, cpos, i, j, k, m, shift : LONGINT;
  166. BEGIN
  167. m := Strings.Length(pattern);
  168. NEW(cb, m);
  169. WHILE (r.res = 0 ) & (cpos < m) DO
  170. cb[cpos] := r.Get();
  171. INC(cpos);
  172. END;
  173. IF r.res = 0 THEN
  174. FOR i := 0 TO 255 DO d[i] := m END;
  175. FOR i := 0 TO m-2 DO d[ORD(pattern[i])] := m - i - 1 END;
  176. i := m;
  177. LOOP
  178. j := m; k := i;
  179. REPEAT DEC(k); DEC(j);
  180. UNTIL (j < 0) OR (pattern[j] # cb[k MOD m]);
  181. IF j<0 THEN EXIT END;
  182. shift := d[ORD(cb[(i-1) MOD m])];
  183. i := i + shift;
  184. WHILE (cpos < i) & (r.res = 0) DO
  185. pos:=cpos MOD m;
  186. IF w#NIL THEN w.Char(cb[pos]);END;
  187. cb[pos] := r.Get();
  188. INC(cpos);
  189. END;
  190. IF r.res#0 THEN EXIT END;
  191. END;
  192. IF w#NIL THEN w.Update END;
  193. END;
  194. END SearchPatternRaw;
  195. PROCEDURE Enumerate(CONST path, pattern : ARRAY OF CHAR; param : Parameters; proc : EnumProc; stats : Statistics; context : Commands.Context);
  196. VAR
  197. enum : Files.Enumerator;
  198. filename : Files.FileName;
  199. fileflags : SET;
  200. time, date, size, nofMatches : LONGINT;
  201. BEGIN
  202. ASSERT(proc # NIL);
  203. IF path # "" THEN Files.JoinPath(path, pattern, filename) END;
  204. NEW(enum); enum.Open(filename, {});
  205. WHILE enum.GetEntry(filename, fileflags, time, date, size) & ~stats.abort DO
  206. IF ~(Files.Directory IN fileflags) THEN
  207. REPEAT
  208. nofMatches := stats.nofMatches;
  209. proc(filename, param, stats, context);
  210. UNTIL ~param.repeat OR (stats.nofMatches = nofMatches);
  211. context.out.Update; context.error.Update;
  212. INC(stats.nofFiles);
  213. END;
  214. END;
  215. IF param.subdirectory THEN
  216. Files.JoinPath(path, "*", filename);
  217. enum.Open(filename, {});
  218. WHILE enum.GetEntry(filename, fileflags, time, date, size) & ~stats.abort DO
  219. IF (Files.Directory IN fileflags) THEN
  220. Enumerate(filename, pattern, param, proc, stats, context);
  221. END;
  222. END;
  223. END;
  224. enum.Close;
  225. END Enumerate;
  226. PROCEDURE Unescape (CONST source: ARRAY OF CHAR; VAR dest: ARRAY OF CHAR);
  227. VAR si, di: LONGINT;
  228. BEGIN
  229. si := 0; di := 0;
  230. WHILE source[si] # 0X DO
  231. IF (source[si] = '\') & (source[si + 1] # 0X) THEN
  232. INC (si);
  233. CASE source[si] OF
  234. | 't': dest[di] := 09X;
  235. | 'n': dest[di] := 0AX;
  236. | 'r': dest[di] := 0DX;
  237. | 'w': dest[di] := 20X;
  238. ELSE dest[di] := source[si];
  239. END;
  240. ELSE
  241. dest[di] := source[si];
  242. END;
  243. INC (si); INC (di);
  244. END;
  245. dest[di] := 0X;
  246. END Unescape;
  247. (** List all files that match <filePattern> and contain the specified <searchString> *)
  248. PROCEDURE Find*(context : Commands.Context); (** [Optinos] filePattern searchString *)
  249. VAR
  250. options : Options.Options;
  251. filePattern, path, pattern : Files.FileName; searchString : SearchString;
  252. param : PatternParameters;
  253. stats : Statistics;
  254. BEGIN
  255. NEW(options);
  256. options.Add("v", "verbose", Options.Flag);
  257. options.Add("f", "formatted", Options.Flag);
  258. options.Add("r", "repeat", Options.Flag);
  259. options.Add("s", "subdirectory", Options.Flag);
  260. IF options.Parse(context.arg, context.error) THEN
  261. filePattern := ""; searchString := "";
  262. context.arg.SkipWhitespace; context.arg.String(filePattern);
  263. context.arg.SkipWhitespace; context.arg.String(searchString);
  264. IF (searchString # "") THEN
  265. NEW(stats);
  266. stats.verbose := options.GetFlag("verbose");
  267. NEW(param);
  268. param.repeat := options.GetFlag("repeat");
  269. param.subdirectory := options.GetFlag("subdirectory");
  270. Unescape(searchString, param.searchString);
  271. IF stats.verbose THEN
  272. context.out.String("Searching '"); context.out.String(searchString); context.out.String("' in ");
  273. context.out.String(filePattern); context.out.String("..."); context.out.Ln; context.out.Update;
  274. END;
  275. Files.SplitPath(filePattern, path, pattern);
  276. IF options.GetFlag("formatted") THEN
  277. Enumerate(path, pattern, param, FindString, stats, context);
  278. ELSE
  279. Enumerate(path, pattern, param, FindStringRaw, stats, context);
  280. END;
  281. IF stats.verbose THEN
  282. stats.Show(context.out);
  283. END;
  284. ELSE
  285. context.error.String("No valid search string parameter"); context.error.Ln;
  286. END;
  287. END;
  288. END Find;
  289. (** Replace all occurences of <searchString> by <replaceString> in files matching to <filePattern> *)
  290. PROCEDURE Replace*(context : Commands.Context); (** filePattern searchString replaceString *)
  291. VAR
  292. options : Options.Options;
  293. filePattern, path, pattern : Files.FileName; searchString, replaceString : SearchString;
  294. param : PatternParameters;
  295. stats : Statistics;
  296. BEGIN
  297. NEW(options);
  298. options.Add("v", "verbose", Options.Flag);
  299. options.Add("r", "repeat", Options.Flag);
  300. options.Add("s", "subdirectory", Options.Flag);
  301. IF options.Parse(context.arg, context.error) THEN
  302. filePattern := ""; searchString := ""; replaceString := "";
  303. context.arg.SkipWhitespace; context.arg.String(filePattern);
  304. context.arg.SkipWhitespace; context.arg.String(searchString);
  305. context.arg.SkipWhitespace; context.arg.String(replaceString);
  306. IF (searchString # "") THEN
  307. WHILE (searchString # "") DO
  308. context.out.String("Replacing '"); context.out.String(searchString); context.out.String("' by '"); context.out.String(replaceString); context.out.Ln;
  309. NEW(stats);
  310. stats.verbose := options.GetFlag("verbose");
  311. NEW(param);
  312. param.repeat := options.GetFlag("repeat");
  313. param.subdirectory := options.GetFlag("subdirectory");
  314. Unescape(searchString, param.searchString);
  315. Unescape(replaceString, param.replaceString);
  316. IF stats.verbose THEN
  317. context.out.String("Replacing '"); context.out.String(searchString); context.out.String("' by '"); context.out.String(replaceString);
  318. context.out.String("' in "); context.out.String(filePattern); context.out.String("... "); context.out.Ln;
  319. END;
  320. Files.SplitPath(filePattern, path, pattern);
  321. Enumerate(path, pattern, param, ReplaceString, stats, context);
  322. IF stats.verbose THEN
  323. stats.Show(context.out);
  324. END;
  325. context.out.String("done."); context.out.Ln;
  326. context.arg.SkipWhitespace; context.arg.String(searchString);
  327. context.arg.SkipWhitespace; context.arg.String(replaceString);
  328. END;
  329. ELSE
  330. context.error.String("No valid search string parameter"); context.error.Ln;
  331. END;
  332. END;
  333. END Replace;
  334. END SearchTools.
  335. System.Free SearchTools ~
  336. SearchTools.Find -s -r E:/Highdim/Hardware/*.xml "?{" ~
  337. SearchTools.Find E:/WinaosNewCommands/source/*.Mod Objects ~
  338. SearchTools.Find E:/WinaosNewCommands/winaos/src/*.Mod Commands ~
  339. SearchTools.Replace E:/WinaosNewCommands/source/*.Mod AosCommands Commands ~
  340. SearchTools.Replace E:/WinaosNewCommands/winaos/src/*.Mod AosCommands Commands ~
  341. System.FreeDownTo SearchTools ~