CommandToolLookupImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) November 4, 1985 8:38:06 pm PST
DIRECTORY
Commander,
CommandTool,
CommandToolLookup USING [LOR, LORA, WithRulesProc],
FS,
IO,
Process,
Rope,
RopeList;
CommandToolLookupImpl: CEDAR PROGRAM
IMPORTS Commander, CommandTool, FS, IO, Process, Rope, RopeList
EXPORTS CommandToolLookup
= BEGIN OPEN CommandToolLookup;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
DoLookup: PUBLIC PROC [cmd: Commander.Handle, arg: ROPE] RETURNS [paths: LORNIL, procData: Commander.CommandProcHandle ← NIL] = {
DoSearch: PROC [arg: ROPE, exact: BOOL] RETURNS [merged: LORNIL, procData: Commander.CommandProcHandle ← NIL] = {
cmdPaths: LORNIL;
loadPaths: LORNIL;
cmPaths: LORNIL;
Process.CheckForAbort[];
[cmdPaths, procData] ← FindMatchingCommands[root: arg, requireExact: exact, searchRules: searchRules];
merged ← RemoveDuplicateShorts[cmdPaths, NIL];
IF exact AND merged # NIL AND merged.rest = NIL THEN
RETURN [LIST[merged.first], procData];
IF merged # NIL AND merged.rest # NIL THEN
RETURN [merged, procData];
Process.CheckForAbort[];
loadPaths ← FindMatchingFiles[root: arg, defaultExtension: ".load", requireExact: exact, searchRules: searchRules];
merged ← RemoveDuplicateShorts[merged, loadPaths];
IF exact AND merged # NIL AND merged.rest = NIL THEN
RETURN [LIST[merged.first], NIL];
Process.CheckForAbort[];
cmPaths ← FindMatchingFiles[root: arg, defaultExtension: ".cm", requireExact: exact, searchRules: searchRules];
merged ← RemoveDuplicateShorts[merged, cmPaths];
IF exact AND merged # NIL AND merged.rest = NIL THEN
RETURN [LIST[merged.first], NIL];
};
searchRules: REF ← MaybeAddWorkingDir[CommandTool.GetProp[cmd, $SearchRules]];
the search rules now in effect
[paths, procData] ← DoSearch[arg, TRUE];
IF paths = NIL THEN
Now try the search all over, except that we don't have to have an exact match
[paths, procData] ← DoSearch[arg, FALSE];
};
FindMatchingFiles: PUBLIC PROC [root: ROPE, defaultExtension: ROPE, requireExact: BOOLTRUE, searchRules: REF ANY] RETURNS [paths: LORNIL] = {
eachRule: WithRulesProc = {
[rule: ROPE] RETURNS [stop: BOOLFALSE]
pat: ROPEFS.ExpandName[name: pattern, wDir: rule
! FS.Error => GO TO zilch;
].fullFName;
angles ← CountAngles[pat];
FS.EnumerateForNames[pattern: pat, proc: eachName
! FS.Error => GO TO zilch;
];
EXITS zilch => {};
};
eachName: FS.NameProc = {
[fullFName: ROPE] RETURNS [continue: BOOL]
IF angles = CountAngles[fullFName] THEN {
new: LOR = LIST[fullFName];
IF pathsTail = NIL THEN paths ← new ELSE pathsTail.rest ← new;
pathsTail ← new;
};
RETURN [TRUE];
};
pattern: ROPE ← root;
len: INT ← Rope.Length[pattern];
bang, dot, dir, star: INT ← len;
pathsTail: LORNIL;
local: BOOLTRUE;
angles: NAT ← 0;
Scan through the file name looking for the various kinds of punctuation.
FOR pos: INT IN [0..len) DO
SELECT Rope.Fetch[pattern, pos] FROM
'! => bang ← pos;
'. => dot ← pos;
'/ => IF pos = 0 THEN local ← FALSE ELSE dir ← pos;
'> => dir ← pos;
'* => star ← pos;
'[ => IF pos = 0 THEN local ← FALSE;
ENDCASE;
ENDLOOP;
IF dir # len AND dot < dir THEN dot ← len;
IF star = len AND NOT requireExact AND bang = len THEN {
There is no star in the pattern, yet we can allow inexact matches, so we append a star (provided that there is no version info provided)
pattern ← Rope.Concat[pattern, "*"];
};
IF Rope.Length[defaultExtension] # 0 THEN {
There is an extension given, so we append it if not already in the name
IF bang = dot THEN {
Need to append the extension to the pattern
pattern ← Rope.Concat[pattern, defaultExtension];
};
};
IF bang = len THEN {
Need to limit the match to the highest versions
pattern ← Rope.Concat[pattern, "!H"];
};
IF local AND searchRules # NIL
THEN {
For a "local" name we have to search all of the directories in the searchRules. If the current workingDirectory is not in the list, then we put it into the list.
searchRules ← MaybeAddWorkingDir[searchRules];
[] ← DoWithRules[searchRules, eachRule];
}
ELSE [] ← eachRule[pattern];
IF star = len AND requireExact AND paths # NIL THEN
We are looking for an exact match, and there is no pattern, so the very first file found in the search path is the right one.
paths.rest ← NIL;
};
FindMatchingCommands: PUBLIC PROC [root: ROPE, requireExact: BOOL, searchRules: REF] RETURNS [paths: LORNIL, data: Commander.CommandProcHandle ← NIL] = {
eachRule: WithRulesProc = {
[rule: ROPE] RETURNS [stop: BOOLFALSE]
eachCommand: Commander.EnumerateAction = {
[key: ROPE, procData: Commander.CommandProcHandle] RETURNS [stop: BOOL ← FALSE]
keyLen: INT ← Rope.Length[key];
ruleRun: INT ← Rope.Run[rule, 0, key, 0, FALSE];
IF rule = NIL OR (ruleRun = ruleLen AND Rope.SkipTo[key, ruleRun, "/"] = keyLen) THEN {
rootRun: INT ← Rope.Run[root, 0, key, ruleRun, FALSE];
keyLen: INT ← Rope.Length[key];
SELECT TRUE FROM
rootRun = rootLen AND rootRun = keyLen-ruleRun => {
Found an exact match, so we can stop here
paths ← pathsTail ← LIST[key];
data ← procData;
RETURN [TRUE];
};
requireExact => {
We are looking for an exact match, so don't try any fancy matching
};
starPos = rootRun => {
At this point we have matched as far as the first star.
IF starPos+1 >= rootLen
THEN
The star is trailing, so we can check for a match cheaply
addName[key, procData]
ELSE {
The star is embedded, so we have to check expensively
short: ROPE ← Rope.Substr[key, ruleRun];
IF Rope.Match[root, short, FALSE] THEN addName[key, procData];
};
};
ENDCASE;
};
};
ruleLen: INT ← Rope.Length[rule];
IF requireExact THEN {
Do this lookup the fast way to avoid the enumeration
fullName: ROPE ← Rope.Concat[rule, root];
data ← Commander.Lookup[fullName];
IF data # NIL THEN {paths ← LIST[fullName]; RETURN [stop: TRUE]};
RETURN [FALSE];
};
IF Commander.Enumerate[eachCommand].key # NIL THEN RETURN [TRUE];
};
rootLen: INT ← Rope.Length[root];
starPos: INT ← Rope.SkipTo[root, 0, "*"];
addName: PROC [name: ROPE, procData: Commander.CommandProcHandle] = {
new: LORLIST[name];
IF pathsTail = NIL
THEN {paths ← new; data ← procData}
ELSE {pathsTail.rest ← new; data ← NIL};
pathsTail ← new;
};
pathsTail: LORNIL;
IF Rope.Match["/*", root] OR searchRules = NIL
THEN [] ← eachRule[NIL]
ELSE {
searchRules ← MaybeAddWorkingDir[searchRules];
[] ← DoWithRules[searchRules, eachRule];
};
paths ← RopeList.Sort[paths, RopeList.IgnoreCase];
};
DoWithRules: PUBLIC PROC [rules: REF, inner: WithRulesProc] RETURNS [stop: BOOLFALSE] = {
WITH rules SELECT FROM
lora: LORA =>
WHILE lora # NIL DO
IF DoWithRules[lora.first, inner] THEN RETURN [TRUE];
lora ← lora.rest;
ENDLOOP;
lor: LOR =>
WHILE lor # NIL DO
IF inner[lor.first] THEN RETURN;
lor ← lor.rest;
ENDLOOP;
rope: ROPE =>
RETURN [inner[rope]];
ENDCASE;
};
ShowAmbiguous: PUBLIC PROC [out: STREAM, list: LOR] = {
IO.PutRope[out, "{Ambiguous:\n"];
FOR each: LOR ← list, each.rest WHILE each # NIL DO
IO.PutF1[out, " %g\n", [rope[each.first]] ];
ENDLOOP;
IO.PutRope[out, " }\n"];
};
MaybeAddWorkingDir: PROC [rules: REF] RETURNS [REF] = {
For a "local" name we have to search all of the directories in the searchRules. If the current workingDirectory is not in the list, then we put it into the list.
wDir: ROPE ← CommandTool.CurrentWorkingDirectory[];
checkRule: WithRulesProc = {
[rule: ROPE] RETURNS [stop: BOOLFALSE]
IF Rope.Equal[rule, wDir, FALSE] THEN RETURN [TRUE];
};
IF rules = NIL THEN RETURN [LIST[wDir]];
IF NOT DoWithRules[rules, checkRule] THEN {
The working directory is not in the current rules, so add it as the first one to search
newList: LIST OF REF ANYLIST[wDir, rules];
RETURN [newList];
};
RETURN [rules];
};
RemoveDuplicateShorts: PROC [list: LOR, tail: LOR] RETURNS [LOR] = {
list ← RopeList.Append[list, tail];
FOR each: LOR ← list, each.rest WHILE each # NIL DO
lag: LOR ← each;
eachStart, eachLen: INT;
IF each.rest = NIL THEN EXIT;
[eachStart, eachLen] ← FindShortPart[lag.first];
FOR other: LOR ← each.rest, other.rest WHILE other # NIL DO
otherStart, otherLen: INT;
[otherStart, otherLen] ← FindShortPart[other.first];
IF eachLen = otherLen THEN
IF Rope.Run[each.first, eachStart, other.first, otherStart, FALSE] >= otherLen THEN {
A duplicate short name to remove
lag.rest ← other.rest;
LOOP;
};
lag ← other;
ENDLOOP;
ENDLOOP;
RETURN [list];
};
FindShortPart: PROC [name: ROPE] RETURNS [start: INT ← 0, len: INT ← 0] = {
end: INT ← Rope.Length[name];
start ← end;
WHILE start > 0 DO
SELECT Rope.Fetch[name, start ← start - 1] FROM
'/, '>, '] => {start ← start + 1; EXIT};
'. => end ← start;
ENDCASE;
ENDLOOP;
len ← end - start;
};
CountAngles: PROC [name: ROPE] RETURNS [count: NAT ← 0] = {
inner: Rope.ActionType = {
[c: CHAR] RETURNS [quit: BOOL ← FALSE]
IF c = '> THEN count ← count + 1;
};
[] ← Rope.Map[base: name, action: inner]
};
TestLookupProc: Commander.CommandProc = {
[cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...]
out: STREAM = cmd.out;
ProcessArgument: PROC [arg: ROPE] = {
paths: LOR ← DoLookup[cmd, arg].paths;
SELECT TRUE FROM
paths = NIL => IO.PutF1[out, "Not found: %g\n", [rope[arg]] ];
paths.rest = NIL => IO.PutF1[out, "Found: %g\n", [rope[paths.first]] ];
ENDCASE => ShowAmbiguous[out, paths];
};
argsProcessed: NAT ← 0;
# of arguments processed
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd, starExpand: FALSE
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
When parsing the command line, be prepared for failure. The error is reported to the user
FOR i: NAT IN [1..argv.argc) DO
Each argument can either be a switch specification or a genuine argument to be processed. The first argument (argv[0]) is not examined, because by convention it is the name of the command as given by the user.
arg: ROPE = argv[i];
Process.CheckForAbort[];
It is a good idea to periodically check for a process abort request.
IF Rope.Length[arg] = 0 THEN LOOP;
Ignore null arguments (it is not easy to generate them, even).
ProcessArgument[arg];
Perform whatever processing is necessary for a normal argument.
ENDLOOP;
EXITS
failed => {result ← $Failure};
};
doc: ROPE = "Tests command lookup.";
Commander.Register[
key: "///Commands/Lookup",
proc: TestLookupProc,
doc: doc,
clientData: NIL,
interpreted: TRUE
];
END.