-- 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];
}}.