DIRECTORY
TextFind USING [CreateFromRope, SearchRope, MalformedPattern, Finder],
Commander USING [CommandProc, Register],
FS USING [defaultStreamOptions, EnumerateForNames, Error, NameProc, StreamOpen, StreamOptions],
IO USING [Close, EndOfStream, GetIndex, GetLine, int, PutChar, PutF, PutRope, PutText, rope, STREAM, text],
Rope USING [ROPE, Size, Fetch, Concat, Find],
RefText USING [TrustTextAsRope],
Ascii USING [Upper],
CommandTool USING [ParseToList];
Grep: Commander.CommandProc
-- [cmd: Commander.Handle] -- = {
Callable from the command interpreter. Expects a list of file names as arguments, and counts the number of words in the indicated files.
stdout: STREAM ← cmd.out;
stdin: STREAM ← cmd.in;
stderr: STREAM ← cmd.err;
cmdLine: LIST OF ROPE;
whoCares: LIST OF ROPE;
fileCount: INT ← 0;
totalNumberOfHits, totalFileHits, totalFilesExamined: INT ← 0;
pattern: TextFind.Finder;
interrupt: REF BOOL ← NEW[BOOL ← FALSE];
openOptions: FS.StreamOptions ← FS.defaultStreamOptions;
stopOnFirstError: BOOL;
ignoreTiogaFormatting: BOOL;
binaryFilesToo: BOOL;
literal, word, ignoreCase: BOOL; -- Interesting pattern options
oneHitPerLine, fileNamesOnly, textOnly, positionsOnly, regularOutput, verbose: BOOL; -- Display options.
line: REF TEXT ← NEW[TEXT[200]];
UsageMessage:
PROC [] = {
IO.PutF[stderr, "Usage: GREP [switches] <pattern> <fileNames>\n"];
};
GrepFile:
FS.NameProc = {
file: STREAM;
continue ← TRUE;
IF ~binaryFilesToo
THEN
FOR l:
LIST
OF
ROPE ← binaryFileExtensions, l.rest
UNTIL l =
NIL
DO
IF Rope.Find[fullFName, l.first, 0, FALSE] # -1 THEN RETURN;
ENDLOOP;
file ←
FS.StreamOpen[fullFName, read, openOptions !
FS.Error => {
IO.PutF[stderr, "Could not open \"%g\" -- %g\n", IO.rope[fullFName], IO.rope[error.explanation]];
GOTO openFailed;
}];
IF verbose
THEN
IO.PutF[stderr, "Searching \"%g\"...", IO.rope[fullFName]];
GrepStream[file, fullFName];
IO.Close[file];
continue ← ~interrupt^;
EXITS
openFailed => {
interrupt^ ← interrupt^ OR stopOnFirstError;
continue ← ~interrupt^;
};
};
GrepStream:
PROC [file:
STREAM, fullFName:
ROPE] = {
found: BOOL;
at, atEnd, before, after: INT;
numberOfHits: INT ← 0;
DO
-- Match the pattern against the next line
position: INT ~ IO.GetIndex[file];
start: INT ← 0;
line ← IO.GetLine[file, line ! IO.EndOfStream => EXIT];
DO
-- Keep matching the pattern until it fails.
[found, at, atEnd, before, after] ←
TextFind.SearchRope[pattern, RefText.TrustTextAsRope[line], start, line.length, interrupt];
IF ~found THEN EXIT;
IF verbose
AND numberOfHits = 0
THEN
IO.PutF[stdout, "\n"];
numberOfHits ← numberOfHits + 1;
SELECT
TRUE
FROM
fileNamesOnly => {
IO.PutF[stdout, "%g\n", IO.rope[fullFName]];
GOTO prematureExit;
};
textOnly => IO.PutF[stdout, "%g\n", IO.text[line]];
positionsOnly => IO.PutF[stdout, "%g (%g)\n", IO.rope[fullFName], IO.int[position+at]];
regularOutput => {
IF numberOfHits = 1
THEN
IO.PutF[stdout, "%g\n", IO.rope[fullFName]];
IO.PutF[stdout, " (%g): ", IO.int[position+at]];
IF line.length > 50
THEN {
start: INT ← MAX[0, at-20];
end: INT ← MIN[line.length, start+50];
len: INT ← end-start;
dots: BOOL ← end < line.length;
IF start > 0 THEN IO.PutRope[stdout, "..."];
FOR i:
INT
IN [0..len)
DO
line[i] ← line[i+start];
ENDLOOP;
line.length ← len;
IO.PutText[stdout, line];
IF dots THEN IO.PutRope[stdout, "..."];
}
ELSE
IO.PutText[stdout, line];
IO.PutChar[stdout, '\n];
};
verbose => IO.PutF[stdout, "%g (%g): %g\n", IO.rope[fullFName], IO.int[position+at], IO.text[line]];
ENDCASE => ERROR;
start ← atEnd;
IF oneHitPerLine THEN EXIT;
ENDLOOP;
REPEAT
prematureExit => NULL;
ENDLOOP;
IF numberOfHits > 0
THEN {
totalNumberOfHits ← totalNumberOfHits + numberOfHits;
totalFileHits ← totalFileHits + 1;
};
IF verbose
THEN
IF numberOfHits = 0
THEN
IO.PutF[stdout, "no matches found.\n"]
ELSE
IO.PutF[stdout, "Matched %g times.\n", IO.int[numberOfHits]];
totalFilesExamined ← totalFilesExamined + 1;
};
cmdLine ← CommandTool.ParseToList[cmd].list;
[literal, cmdLine, whoCares] ← GetSwitch["-pattern", cmdLine];
literal ← ~literal;
[ignoreCase, cmdLine, whoCares] ← GetSwitch["-caseSensitive", cmdLine];
ignoreCase ← ~ignoreCase;
[word, cmdLine, whoCares] ← GetSwitch["-word", cmdLine];
[oneHitPerLine, cmdLine, whoCares] ← GetSwitch["-oncePerLine", cmdLine];
[fileNamesOnly, cmdLine, whoCares] ← GetSwitch["-fileNamesOnly", cmdLine];
[textOnly, cmdLine, whoCares] ← GetSwitch["-textOnly", cmdLine];
[positionsOnly, cmdLine, whoCares] ← GetSwitch["-positionsOnly", cmdLine];
[verbose, cmdLine, whoCares] ← GetSwitch["-verbose", cmdLine];
regularOutput ← ~(fileNamesOnly OR textOnly OR positionsOnly OR verbose);
[stopOnFirstError, cmdLine, whoCares] ← GetSwitch["-stopOnFirstError", cmdLine];
[binaryFilesToo, cmdLine, whoCares] ← GetSwitch["-binaryFilesToo", cmdLine];
[ignoreTiogaFormatting, cmdLine, whoCares] ← GetSwitch["-ignoreTiogaFormatting", cmdLine];
IF SwitchesLeft[cmdLine, stderr] THEN GOTO prematureExit;
IF cmdLine =
NIL
THEN {
UsageMessage[];
GOTO prematureExit;
};
pattern ← TextFind.CreateFromRope[pattern: cmdLine.first, literal: literal, word: word, ignoreCase: ignoreCase ! TextFind.MalformedPattern => {
IO.PutF[stderr, "Syntax error in pattern \"%g\"\n", IO.rope[cmdLine.first]];
GOTO prematureExit;}];
cmdLine ← cmdLine.rest; -- Get rid of the pattern.
openOptions[tiogaRead] ← ignoreTiogaFormatting;
IF cmdLine =
NIL
THEN
GrepStream[stdin, "Standard input"]
ELSE
FOR l:
LIST
OF
ROPE ← cmdLine, l.rest
UNTIL l =
NIL
DO
FS.EnumerateForNames[DefaultToHighestGeneration[l.first], GrepFile];
IF interrupt^ THEN GOTO prematureExit;
ENDLOOP;
IF regularOutput
OR verbose
THEN
IO.PutF[stderr, "Files examined: %g, files matched: %g, number of matches: %g\n",
IO.int[totalFilesExamined], IO.int[totalFileHits], IO.int[totalNumberOfHits]];
EXITS
prematureExit => NULL;
}.