-- UniqueImpl.mesa -- edited by Schmidt (February 3, 1983 6:03 pm) -- based on version of SHayes (15-Sep-81 19:47:34) DIRECTORY Ascii: TYPE USING [ControlZ], Buttons: TYPE USING [Button, ButtonProc, Create], Containers: TYPE USING [ChildXBound, ChildYBound, Container, Create], FileIO: TYPE USING[Open, OpenFailed], IO: TYPE USING[Close, CreateInputStreamFromRope, EndOfStream, GetChar, Handle, Put, PutChar, PutF, PutRope, rope, string], Labels: TYPE USING [Create, Label, Set], List: TYPE USING[CompareProc, DReverse, UniqueSort], Menus: TYPE USING [CreateEntry, CreateMenu, InsertMenuEntry, Menu, MenuProc], Process: TYPE USING[Detach], Rope: TYPE USING[Cat, Compare, Equal, Fetch, Flatten, FromChar, Length, ROPE], Rules: TYPE USING [Create, Rule], TypeScript: TYPE USING[TS, Create], UECP: TYPE USING[Argv, Parse], UserExec: TYPE USING[CommandProc, RegisterCommand], ViewerClasses: TYPE USING [Viewer], ViewerOps: TYPE USING [PaintViewer, SetMenu, SetOpenHeight], ViewerIO: TYPE USING[CreateViewerStreams], ViewerTools: TYPE USING [GetContents, GetSelectionContents, MakeNewTextViewer, SetSelection]; UniqueImpl: CEDAR PROGRAM IMPORTS Buttons, Containers, FileIO, IO, Labels, List, Menus, Process, Rope, Rules, TypeScript, UECP, UserExec, ViewerIO, ViewerOps, ViewerTools = { Uppercase: TYPE = CHAR ['A..'Z]; Lowercase: TYPE = CHAR ['a..'z]; Digits: TYPE = CHAR ['0..'9]; Mode: TYPE = {files, extensions, words, lines, numbers}; Global: TYPE = REF GlobalRecord; GlobalRecord: TYPE = RECORD[ container: Containers.Container _ NIL, ttyTypeScript: TypeScript.TS _ NIL, modeButton: Buttons.Button _ NIL, modeLabel: Labels.Label _ NIL, fileNameButton: Buttons.Button _ NIL, fileNameViewer: ViewerClasses.Viewer _ NIL, in: IO.Handle _ NIL, out: IO.Handle _ NIL, ch: CHAR _ ' , eof: BOOL _ FALSE, list: LIST OF Rope.ROPE _ NIL, mode: Mode _ files ]; entryHeight: CARDINAL = 15; entryVSpace: CARDINAL = 10; entryHSpace: CARDINAL = 10; -- mds usage oneGlobal: Global; modeString: ARRAY Mode OF Rope.ROPE _ ["Files", "Extensions", "Words", "Lines", "Numbers"]; -- end of mds usage BuildOuter: PROC RETURNS[g: Global] = { menu: Menus.Menu _ Menus.CreateMenu[]; g _ NEW[GlobalRecord _ []]; g.container _ Containers.Create[info: [name: "UniqueWindow", iconic: FALSE, scrollable: FALSE]]; -- first row of menu items Menus.InsertMenuEntry[menu, Menus.CreateEntry["Go", GoProc, g]]; -- ViewerOps.SetMenu[g.container, menu]; BuildUserInput[g]; ViewerOps.PaintViewer[g.container, all]; [out: g.out] _ ViewerIO.CreateViewerStreams[viewer: g.ttyTypeScript, name: NIL]; }; BuildUserInput: PROC[g: Global] = { heightSoFar: CARDINAL _ 0; l: ViewerClasses.Viewer; rule: Rules.Rule; CreateButton: PROC[bname, lname: Rope.ROPE, newLine: BOOL, drawRule: BOOL _ FALSE] RETURNS[button: Buttons.Button, label: Labels.Label] = { x: CARDINAL; IF newLine THEN { IF l = NIL THEN heightSoFar _ entryVSpace/2 ELSE heightSoFar _ heightSoFar + entryVSpace + l.wh; IF drawRule THEN { rule _ Rules.Create[info: [parent: g.container, wx: 0, wy: heightSoFar, ww: 0, wh: 1]]; Containers.ChildXBound[g.container, rule]; heightSoFar _ heightSoFar + entryVSpace; }; x _ 0; } ELSE x _ l.wx + l.ww + entryHSpace; l _ button _ Buttons.Create[info: [name: bname, parent: g.container, border: FALSE, wx: x, wy: heightSoFar], proc: PushButton, clientData: g]; IF lname ~= NIL THEN l _ label _ Labels.Create[info: [name: lname, parent: g.container, wx: button.wx + button.ww + entryHSpace, wy: heightSoFar, border: TRUE]]; }; [g.modeButton, g.modeLabel] _ CreateButton["Mode:", "AppendWords", TRUE]; IF g.mode = files THEN Labels.Set[g.modeLabel, "Files"]; [g.fileNameButton,] _ CreateButton["FileName:", NIL, FALSE]; l _ g.fileNameViewer _ ViewerTools.MakeNewTextViewer[info: [parent: g.container, wx: l.wx+l.ww+entryHSpace, wy: heightSoFar, ww: 100, wh: entryHeight, data: NIL, scrollable: FALSE, border: FALSE], paint: FALSE]; Containers.ChildXBound[g.container, g.fileNameViewer]; heightSoFar _ heightSoFar+entryVSpace+l.wh; -- now the line above the typescript rule _ Rules.Create[info: [parent: g.container, wx: 0, wy: heightSoFar, ww: 0, wh: 1]]; Containers.ChildXBound[g.container, rule]; heightSoFar _ heightSoFar+entryVSpace; -- now the typescript g.ttyTypeScript _ TypeScript.Create[info: [parent: g.container, wx: 0, wy: heightSoFar, ww: 0, wh: 800, border: FALSE]]; -- 800 due to viewers bug Containers.ChildXBound[g.container, g.ttyTypeScript]; Containers.ChildYBound[g.container, g.ttyTypeScript]; ViewerOps.SetOpenHeight[g.container, heightSoFar + 50]; }; PushButton: Buttons.ButtonProc = { g: Global _ NARROW[clientData]; SELECT NARROW[parent, ViewerClasses.Viewer] FROM g.fileNameButton => ViewerTools.SetSelection[g.fileNameViewer, NIL]; g.modeButton => { g.mode _ IF g.mode = numbers THEN files ELSE SUCC[g.mode]; Labels.Set[g.modeLabel, modeString[g.mode]]; }; ENDCASE => ERROR; }; GoProc: Menus.MenuProc = { g: Global _ NARROW[clientData]; ProcessFileName[g, ViewerTools.GetContents[g.fileNameViewer]]; IF g.in ~= NIL THEN { p: PROCESS _ FORK DriveUnique[g, g.mode]; TRUSTED {Process.Detach[p];} }; }; -- this procedure may be forked DriveUnique: PROC[g: Global, mode: Mode] = { ENABLE ABORTED => GOTO out; g.list _ NIL; SELECT mode FROM words => Things[g, MatchId]; lines => Things[g, MatchLine]; numbers => Things[g, MatchNumber]; extensions => FileNames[g, compareExts]; files => FileNames[g, compare]; ENDCASE => ERROR; g.out.Put[IO.string["\n\n-------------------------\n\n"L]]; g.in.Close[]; g.in _ NIL; EXITS out => g.out.PutF["Aborted.\n"]; }; ProcessFileName: PROC[g: Global, fileName: Rope.ROPE] = { g.eof _ FALSE; g.in _ NIL; IF fileName.Length[] = 0 THEN { r: Rope.ROPE _ ViewerTools.GetSelectionContents[]; IF r.Length[] = 0 THEN { g.out.PutF["Error - No filename or selection given.\n\n"]; GOTO NoInput }; g.in _ IO.CreateInputStreamFromRope[r]; } ELSE { g.in _ FileIO.Open[fileName, read ! FileIO.OpenFailed => { g.out.PutF["Error - Can't open %s\n\n", IO.rope[fileName]]; GOTO NoInput }]; }; InCh[g]; EXITS NoInput => NULL; }; InCh: PROC[g: Global] = { IF ~g.eof AND g.in#NIL THEN g.ch _ g.in.GetChar[ ! IO.EndOfStream => { g.eof _ TRUE; g.ch _ '\000; CONTINUE }]; }; Trash: PROC[g: Global] = { DO SELECT g.ch FROM ' , '\t, '\n => InCh[g]; Ascii.ControlZ => Z[g]; ENDCASE => EXIT; ENDLOOP; }; Z: PROC[g: Global] = { IF g.ch = Ascii.ControlZ THEN WHILE g.ch # '\n DO InCh[g] ENDLOOP; }; compareExts: List.CompareProc = { name1: Rope.ROPE _ NARROW[ref1]; name2: Rope.ROPE _ NARROW[ref2]; ext1: Rope.ROPE; ext2: Rope.ROPE; i: INT; FOR i IN [0..name1.Length[]) UNTIL name1.Fetch[i] = '. DO ENDLOOP; ext1 _ Rope.Flatten[name1, i]; FOR i IN [0..name2.Length[]) UNTIL name2.Fetch[i] = '. DO ENDLOOP; ext2 _ Rope.Flatten[name2, i]; RETURN[ SELECT Rope.Compare[ext1, ext2, FALSE] FROM less => less, greater => greater, ENDCASE => SELECT Rope.Compare[name1, name2, FALSE] FROM less => less, greater => greater, ENDCASE => equal]; }; compare: List.CompareProc = { name1: Rope.ROPE _ NARROW[ref1]; name2: Rope.ROPE _ NARROW[ref2]; RETURN[ SELECT Rope.Compare[name1, name2, FALSE] FROM less => less, greater => greater, ENDCASE => equal] }; MatchId: PROC [g: Global] RETURNS[s: Rope.ROPE] = { s _ NIL; WHILE (g.ch = '$) OR (g.ch = '-) OR (g.ch IN Uppercase) OR (g.ch IN Lowercase) OR (g.ch IN Digits) DO s _ Rope.Cat[s, Rope.FromChar[g.ch]]; InCh[g] ENDLOOP; }; MatchNumber: PROC [g: Global] RETURNS[s: Rope.ROPE] = { s _ NIL; WHILE (g.ch IN Digits) DO s _ Rope.Cat[s, Rope.FromChar[g.ch]]; InCh[g] ENDLOOP; }; MatchLine: PROC [g: Global] RETURNS[s: Rope.ROPE] = { s _ NIL; UNTIL (g.ch = '\n) OR g.eof DO s _ Rope.Cat[s, Rope.FromChar[g.ch]]; InCh[g]; Z[g]; ENDLOOP; IF g.ch = '\n THEN { s _ Rope.Cat[s, Rope.FromChar[g.ch]]; InCh[g] }; }; Things: PROC [g: Global, p: PROC[g: Global] RETURNS[Rope.ROPE]] = { s: Rope.ROPE; WHILE ~g.eof DO UNTIL (s _ p[g]).Length[] ~= 0 OR g.eof DO InCh[g]; Trash[g] ENDLOOP; IF s.Length[] # 0 THEN g.list _ CONS[s, g.list]; ENDLOOP; ProcessAndPrint[g, compare]; }; ProcessAndPrint: PROC[g: Global, cProc: List.CompareProc] = TRUSTED { g.list _ LOOPHOLE[List.DReverse[LOOPHOLE[g.list]]]; g.list _ LOOPHOLE[List.UniqueSort[LOOPHOLE[g.list], cProc]]; PrintList[g]; }; FileNames: PROC [g: Global, cProc: List.CompareProc] = BEGIN s, t: Rope.ROPE; WHILE ~g.eof DO UNTIL ((s_MatchId[g]).Length[] ~= 0 AND g.ch = '.) OR g.eof DO InCh[g]; Trash[g] ENDLOOP; InCh[g]; IF (t_MatchId[g]).Length[] ~= 0 THEN { s _ Rope.Cat[s, ".", t]; g.list _ CONS[s, g.list]; }; ENDLOOP; ProcessAndPrint[g, cProc]; END; PrintList: PROC[g: Global] = { FOR l: LIST OF Rope.ROPE _ g.list, l.rest UNTIL l = NIL DO SmartOutString[g, l.first]; ENDLOOP; }; SmartOutString: PROC [g: Global, s: Rope.ROPE] = { g.out.PutRope[s]; IF s.Fetch[s.Length[] - 1] # '\n THEN g.out.PutChar[' ]; }; UniqueExecProc: UserExec.CommandProc = { argv: UECP.Argv _ UECP.Parse[event.commandLine]; filename: Rope.ROPE _ NIL; mode: Mode _ files; IF argv.argc > 1 THEN { mode _ SELECT TRUE FROM Rope.Equal[argv[1], modeString[files], FALSE] => files, Rope.Equal[argv[1], modeString[extensions], FALSE] => extensions, Rope.Equal[argv[1], modeString[words], FALSE] => words, Rope.Equal[argv[1], modeString[lines], FALSE] => lines, Rope.Equal[argv[1], modeString[numbers], FALSE] => numbers, ENDCASE => files; }; IF argv.argc > 2 THEN { filename _ argv[2]; }; ProcessFileName[oneGlobal, filename]; IF oneGlobal.in ~= NIL THEN DriveUnique[oneGlobal, mode]; }; { -- start code oneGlobal _ BuildOuter[]; UserExec.RegisterCommand["Unique", UniqueExecProc]; }}.