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:
LOR ←
NIL, procData: Commander.CommandProcHandle ←
NIL] = {
DoSearch:
PROC [arg:
ROPE, exact:
BOOL]
RETURNS [merged:
LOR ←
NIL, procData: Commander.CommandProcHandle ←
NIL] = {
cmdPaths: LOR ← NIL;
loadPaths: LOR ← NIL;
cmPaths: LOR ← NIL;
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:
BOOL ←
TRUE, searchRules:
REF
ANY]
RETURNS [paths:
LOR ←
NIL] = {
eachRule: WithRulesProc = {
[rule: ROPE] RETURNS [stop: BOOL ← FALSE]
pat:
ROPE ←
FS.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: LOR ← NIL;
local: BOOL ← TRUE;
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:
LOR ←
NIL, data: Commander.CommandProcHandle ←
NIL] = {
eachRule: WithRulesProc = {
[rule: ROPE] RETURNS [stop: BOOL ← FALSE]
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: LOR ← LIST[name];
IF pathsTail =
NIL
THEN {paths ← new; data ← procData}
ELSE {pathsTail.rest ← new; data ← NIL};
pathsTail ← new;
};
pathsTail: LOR ← NIL;
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:
BOOL ←
FALSE] = {
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: BOOL ← FALSE]
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 ANY ← LIST[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: REF ← NIL, msg: ROPE ← NIL]
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.