<> <> <> 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]; GrepImpl: CEDAR PROGRAM IMPORTS Commander, FS, IO, CommandTool, RefText, TextFind, Ascii, Rope = { ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; binaryFileExtensions: LIST OF ROPE _ LIST[".bcd", ".press", ".symbols", ".tipc", ".boot", ".versionmap", ".bittable"]; Grep: Commander.CommandProc -- [cmd: Commander.Handle] -- = { <> 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] \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; }; GetSwitch: PROC [switch: ROPE, cmdLine: LIST OF ROPE, prefixLen: INT _ 2] RETURNS [present: BOOL _ FALSE, newCmdLine: LIST OF ROPE, previous: LIST OF ROPE _ NIL] = { IF cmdLine = NIL THEN RETURN; IF Prefix[switch, cmdLine.first, prefixLen] THEN { present _ TRUE; newCmdLine _ cmdLine.rest; } ELSE { newCmdLine _ cmdLine; FOR l: LIST OF ROPE _ cmdLine, l.rest UNTIL l.rest = NIL DO IF Prefix[switch, l.rest.first, prefixLen] THEN { l.rest _ l.rest.rest; present _ TRUE; previous _ l; RETURN; }; ENDLOOP; }; }; SwitchesLeft: PROC [cmdLine: LIST OF ROPE, stderr: STREAM, switchChar: CHAR _ '-] RETURNS [switchesLeft: BOOL _ FALSE] = { FOR l: LIST OF ROPE _ cmdLine, l.rest UNTIL l = NIL DO IF Rope.Size[l.first] >= 1 THEN IF Rope.Fetch[l.first] = switchChar THEN { IO.PutF[stderr, "Invalid switch: \"%g\"\n", IO.rope[l.first]]; switchesLeft _ TRUE; }; ENDLOOP; }; Prefix: PROC [r1, r2: ROPE, length: INT, ignoreCase: BOOL _ TRUE] RETURNS [BOOL] = { r2Len: INT _ r2.Size[]; r1Len: INT _ r1.Size[]; IF r1Len < length THEN ERROR; IF r2Len > r1Len OR r2Len < length THEN RETURN[FALSE]; IF ~ignoreCase THEN FOR i: INT IN [0..r2Len) DO IF r1.Fetch[i] # r2.Fetch[i] THEN RETURN[FALSE]; ENDLOOP ELSE FOR i: INT IN [0..r2Len) DO IF Ascii.Upper[r1.Fetch[i]] # Ascii.Upper[r2.Fetch[i]] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; DefaultToHighestGeneration: PROC [filePattern: ROPE] RETURNS [ROPE] = { IF Rope.Find[filePattern, "!"] = -1 THEN RETURN[Rope.Concat[filePattern,"!H"]] ELSE RETURN[filePattern]; }; Commander.Register[ key: "Grep", proc: Grep, doc: "Searches files for lines that match a pattern."]; }.