FileNamesOnPFSImpl.mesa
Copyright Ó 1985, 1987, 1989, 1991 by Xerox Corporation. All rights reserved.
L. Stewart, January 16, 1984 1:03 pm
Russ Atkinson (RRA) February 16, 1987 7:46:03 pm PST
Doug Wyatt, January 24, 1987 11:50:39 pm PST
JKF December 22, 1988 2:35:42 pm PST
Bill Jackson (bj) October 24, 1988 12:42:06 pm PDT
Carl Hauser, November 30, 1988 10:45:59 am PST
Pier, February 2, 1989 2:20:16 pm PST
Michael Plass, November 29, 1989 1:00:07 pm PST
Clients of this should be converted to use PFS directly.
Willie-s, August 20, 1991 5:46 pm PDT
DIRECTORY
PFS,
FileNames,
RefText,
Rope,
SystemNames;
FileNamesOnPFSImpl:
CEDAR
MONITOR
IMPORTS PFS, RefText, Rope, SystemNames
EXPORTS FileNames
= BEGIN
LOR: TYPE = LIST OF ROPE;
LORA: TYPE = LIST OF REF ANY;
ROPE: TYPE = Rope.ROPE;
A few small caches
lastPreSlash1: ROPE ¬ NIL;
lastPostSlash1: ROPE ¬ NIL;
lastPreSlash2: ROPE ¬ NIL;
lastPostSlash2: ROPE ¬ NIL;
lastUserName: ROPE ¬ NIL;
lastHomeDirectory: ROPE ¬ NIL;
FileWithSearchRules:
PUBLIC
PROC
[root:
ROPE, defaultExtension:
ROPE,
requireExtension:
BOOL ¬
TRUE, requireExact:
BOOL ¬
TRUE, searchRules:
REF
ANY]
RETURNS [fullPath:
ROPE ¬
NIL, ambiguous:
BOOL ¬
FALSE] = {
FileWithSearchRules uses the working directory and the search rules to try to translate the short name of a file into a full path name. Calls ResolveRelativePath first. NIL is returned if the file cannot be found.
searchRules should either be a LIST OF REF ANY or a LIST OF Rope.ROPE.
If requireExtension, then if root has an extension, it must be the same as defaultExtension.
FileWithSearchRules adds !H to all pattern matches
IF requireExact = TRUE, then FileWithSearchRules will not try for a unique pattern match, but will only check exact names.
FileWithSearchRules returns NIL and ambiguous = TRUE if it finds more than one matching file.
FileNames returns not-NIL and ambiguous = TRUE if the result was found as a consequence of an exact pattern match.
FileWithSearchRules uses the following rules:
IF root is a full path name
THEN {
tries root
tries Concat[root, defaultExtension]
tries for a unique match for Cat[root, *, defaultExtension]
}
ELSE {
tries root (automatically used $WorkingDirectory)
FOR wdir ← each element of search rules {
tries Concat[wdir, root]
}
tries Concat[root, defaultExtension] (automatically used $WorkingDirectory)
FOR wdir ← each element of search rules {
tries Cat[wdir, root, defaultExtension]
}
IF NOT requireExact THEN FOR wdir ← each element of search rules {
tries for a unique match for Cat[wdir, root, *, defaultExtension]
}
}
Returns NIL if can't find it.
fullPathName: BOOL ¬ FALSE;
withExt: ROPE ¬ NIL;
withStarExt: ROPE ¬ NIL;
rules: LORA;
ropeRules: LOR;
list: LORA;
ropeList: LOR;
hasExtension: BOOL ¬ FALSE;
hasVersion: BOOL ¬ FALSE;
doRoot, doWithExt, doWithStarExt: BOOL ¬ FALSE;
defaultExtLen: INT = Rope.Length[defaultExtension];
rootLen: INT ¬ Rope.Length[root ¬ ResolveRelativePath[root]];
Try:
PROC [name:
ROPE, wDir:
ROPE ¬
NIL]
RETURNS [found:
BOOL] = {
IF
NOT Rope.IsEmpty[name]
THEN {
fullPathName: PFS.PATH ¬ NIL;
Inner:
PROC ~ {
fullPathName ¬
PFS.FileInfo[name:
PFS.PathFromRope[name]
! PFS.Error => IF error.group = user THEN CONTINUE].fullFName;
};
IF wDir = NIL THEN Inner[] ELSE PFS.DoInWDir[PFS.PathFromRope[wDir], Inner];
fullPath ¬ PFS.RopeFromPath[fullPathName];
RETURN [fullPathName # NIL];
};
RETURN [FALSE];
};
UniqueMatch:
PROC [name:
ROPE, wDir:
ROPE ¬
NIL]
RETURNS [found:
BOOL] = {
lst: LOR ¬ NIL;
p:
PFS.NameProc = {
continue ¬ lst = NIL; -- give up after this name if it is the second one
IF lst # NIL THEN ambiguous ¬ TRUE;
lst ¬ CONS[PFS.RopeFromPath[name], lst];
};
IF name.IsEmpty[]
THEN {
fullPath ¬ NIL;
RETURN [TRUE];
};
BEGIN
Inner:
PROC ~ {
PFS.EnumerateForNames[pattern:
PFS.PathFromRope[name], proc: p
! PFS.Error => IF error.group = user THEN CONTINUE];
};
IF wDir = NIL THEN Inner[] ELSE PFS.DoInWDir[PFS.PathFromRope[wDir], Inner];
END;
IF lst = NIL THEN RETURN [FALSE];
IF ambiguous
THEN {
fullPath ¬ NIL;
RETURN [TRUE];
};
Exactly one match
ambiguous ¬ TRUE;
fullPath ¬ ConvertToSlashFormat[lst.first];
RETURN [TRUE];
};
IF rootLen = 0 THEN RETURN [NIL];
SELECT Rope.Fetch[root, 0]
FROM
'/, '[ => fullPathName ¬ TRUE;
ENDCASE;
FOR i:
INT
DECREASING
IN [0..rootLen)
DO
SELECT Rope.Fetch[root, i]
FROM
'>, '], '/ => EXIT;
'. => {hasExtension ¬ TRUE; EXIT};
'! => hasVersion ¬ TRUE;
ENDCASE;
ENDLOOP;
IF requireExtension AND NOT hasExtension AND hasVersion THEN RETURN [NIL];
IF requireExtension
AND hasExtension
AND defaultExtLen # 0
THEN
IF Rope.Find[s1: root, s2: defaultExtension, pos1: 0, case:
FALSE]
# (Rope.Index[s1: root, s2: "!", pos1: 0, case:
TRUE] - defaultExtLen)
THEN
RETURN [NIL];
IF
NOT hasExtension
THEN {
withExt ¬ Rope.Concat[root, defaultExtension];
withStarExt ¬ Rope.Cat[root, "*", defaultExtension];
IF
NOT hasVersion
THEN {
withExt ¬ Rope.Concat[withExt, "!H"];
withStarExt ¬ Rope.Concat[withStarExt, "!H"];
};
};
doRoot ¬ NOT requireExtension OR hasExtension;
doWithExt ¬ NOT hasExtension;
doWithStarExt ¬ (NOT requireExact) AND (NOT hasExtension);
IF fullPathName
THEN {
try the exact matches
IF doRoot AND Try[name: root] THEN RETURN;
IF doWithExt AND Try[name: withExt] THEN RETURN;
try for a unique match
IF doWithStarExt AND UniqueMatch[name: withStarExt] THEN RETURN;
}
ELSE {
IF doRoot AND Try[name: root] THEN RETURN;
WITH searchRules
SELECT
FROM
lra: LORA => rules ¬ lra;
lr: LOR => ropeRules ¬ lr;
ENDCASE;
list ¬ rules;
ropeList ¬ ropeRules;
IF doRoot
THEN {
WHILE list #
NIL
DO
IF Try[name: root, wDir: NARROW[list.first, ROPE]] THEN RETURN;
list ¬ list.rest;
ENDLOOP;
WHILE ropeList #
NIL
DO
IF Try[name: root, wDir: ropeList.first] THEN RETURN;
ropeList ¬ ropeList.rest;
ENDLOOP;
};
IF doWithExt AND Try[name: withExt] THEN RETURN;
IF doWithExt
THEN {
list ¬ rules;
ropeList ¬ ropeRules;
WHILE list #
NIL
DO
IF Try[name: withExt, wDir: NARROW[list.first, ROPE]] THEN RETURN;
list ¬ list.rest;
ENDLOOP;
WHILE ropeList #
NIL
DO
IF Try[name: withExt, wDir: ropeList.first] THEN RETURN;
ropeList ¬ ropeList.rest;
ENDLOOP;
};
IF doWithStarExt
THEN {
IF UniqueMatch[name: withStarExt] THEN RETURN;
list ¬ rules;
ropeList ¬ ropeRules;
WHILE list #
NIL
DO
IF UniqueMatch[name: withStarExt, wDir: NARROW[list.first, ROPE]] THEN RETURN;
list ¬ list.rest;
ENDLOOP;
WHILE ropeList #
NIL
DO
IF UniqueMatch[name: withStarExt, wDir: ropeList.first] THEN RETURN;
ropeList ¬ ropeList.rest;
ENDLOOP;
};
};
};
ParseState:
TYPE = {idle, slash, oneDot, twoDots};
CurrentWorkingDirectory:
PUBLIC
PROC
RETURNS [
ROPE] = {
RETURN [PFS.RopeFromPath[PFS.GetWDir[]]]
};
ResolveRelativePath:
PUBLIC
PROC [path:
ROPE]
RETURNS [
ROPE] = {
If path starts with or contains ./ or ../, ResolveRelativePath converts it into the equivalent full path name using the $WorkingDirectory property on the process properties list.
If path is exactly . or .., ResolveRelativePath converts it to the current or parent directory.
i: INT ¬ 0;
state: ParseState ¬ slash;
pathLength: INT ¬ Rope.Length[path ¬ ConvertToSlashFormat[path]];
out: REF TEXT ¬ NIL;
char: CHAR;
usedCWD: BOOL ¬ FALSE;
HandleDot is called with pos pointing to the location of the trailing '/ is the "/./" construct, it works if either the leading or trailing slash aren't there, as in "./xxx" or "xxx/."
After execution of HandleDot, out holds the path tp to and including the leading / while path holds the path after the trailing slash.
If the leading slash doesn't exist, then out will be allocated but empty.
If the trailing slash doesn't exist (or was the last character in path), then path will be empty
HandleDot:
PROC [pos:
INT] = {
IF out = NIL THEN out ¬ RefText.New[pathLength];
IF pos - 1 > 0 THEN out ¬ RefText.AppendRope[to: out, from: path, start: 0, len: pos - 1];
out now includes up to and including the leading slash.
IF pos >= pathLength THEN path ¬ NIL ELSE path ¬ path.Substr[pos + 1];
path now starts at the character after the trailing slash
pathLength ¬ path.Length[]; -- recompute pathLength
};
HandleDotDot:
PROC [pos:
INT] = {
HandleDotDot is called with pos pointing to the location of the trailing '/ is the "/../" construct. It worksif either the leading or trailing slash aren't there, as in "../xxx" or "xxx/.."
After execution of HandleDotDot, out holds the path tp to and including the / terminating the parent directory while path holds the path after the trailing slash.
If the leading slash doesn't exist, then out will contain the parent of CWD.
If the trailing slash doesn't exist (or was the last character in path), then path will be empty
first: BOOL ¬ TRUE;
pos is the location of the trailing '/
IF out = NIL THEN out ¬ RefText.New[pathLength];
IF pos - 2 > 0 THEN out ¬ RefText.AppendRope[to: out, from: path, start: 0, len: pos - 2];
out now contains the path up to and including the leading slash
IF pos >= pathLength THEN path ¬ NIL ELSE path ¬ path.Substr[pos + 1];
path now starts at the character after the trailing slash
pathLength ¬ path.Length[]; -- recompute pathLength
Now strip back out to the last slash, provided it is not the first character examined
pos ¬ out.length - 1; -- index of last character in out
DO
IF pos < 0
THEN {
out.length ¬ 0;
IF usedCWD THEN EXIT;
usedCWD ¬ TRUE;
out ¬ RefText.AppendRope[to: out, from: PFS.RopeFromPath[PFS.GetWDir[]]];
pos ¬ out.length - 1; -- continue scanning at end of CWD
LOOP;
};
IF out[pos] = '/
AND
NOT first
THEN {
out.length ¬ pos + 1;
EXIT;
};
pos ¬ pos - 1;
first ¬ FALSE;
ENDLOOP;
};
IF pathLength = 0 THEN RETURN [path];
IF path.Fetch[0] = '~
THEN {
Put in ///Users/name/, then pull back on the trailing /
usedCWD ¬ TRUE;
out ¬ RefText.AppendRope[to: RefText.New[pathLength], from: HomeDirectory[]];
out.length ¬ out.length - 1;
state ¬ idle;
pathLength ¬ Rope.Length[path ¬ Rope.Substr[path, 1]];
};
DO
IF i >= pathLength THEN EXIT;
char ¬ path.Fetch[i];
SELECT state
FROM
idle => IF char = '/ THEN state ¬ slash;
slash => {
SELECT char
FROM
'. => state ¬ oneDot;
'/ => state ¬ slash;
ENDCASE => state ¬ idle;
};
oneDot => {
SELECT char
FROM
'. => state ¬ twoDots;
'/ => {
delete the "./" construct
HandleDot[i];
i ¬ 0;
continue scanning from beginning (since HandleDot has re-written path)
state ¬ slash;
LOOP;
};
ENDCASE => state ¬ idle;
};
twoDots => {
IF char = '/
THEN {
handle the "../" construct
HandleDotDot[i];
i ¬ 0;
continue scanning from beginning (since HandleDot has re-written path)
state ¬ slash;
The next character we get should be the one after the slash
LOOP;
}
ELSE state ¬ idle;
};
ENDCASE => ERROR;
i ¬ i + 1;
ENDLOOP;
handle state at end of path
IF state = oneDot
OR state = twoDots
THEN {
The next lines call HandleDot (or DotDot) with a pointer to the '/ which is off the end of the path.
IF state = oneDot THEN HandleDot[pathLength]
ELSE HandleDotDot[pathLength];
path ¬ Rope.FromRefText[out];
}
ELSE
IF out # NIL THEN path ¬ Rope.Concat[Rope.FromRefText[out], path];
IF path.IsEmpty[] AND NOT usedCWD THEN path ¬ PFS.RopeFromPath[PFS.GetWDir[]];
RETURN [path];
};
ConvertToSlashFormat:
PUBLIC
ENTRY
PROC [path:
ROPE]
RETURNS [
ROPE] = {
ENABLE UNWIND => NULL;
SELECT
TRUE
FROM
Rope.Equal[path, lastPreSlash1] => RETURN [lastPostSlash1];
Rope.Equal[path, lastPreSlash2] => RETURN [lastPostSlash2];
ENDCASE => {
ToSlash: Rope.TranslatorType = {
TYPE = PROC [old: CHAR] RETURNS [CHAR]
gnu:
CHAR ¬
SELECT old
FROM
'[, '], '<, '> => '/
ENDCASE => old;
RETURN[gnu];
};
Convert to slashes
pos: INT ¬ path.Find["]<"];
lastPreSlash1 ¬ lastPreSlash2;
lastPostSlash1 ¬ lastPostSlash2;
lastPreSlash2 ¬ path;
IF pos # -1 THEN path ¬ Rope.Replace[base: path, start: pos, len: 2, with: "/"];
IF Rope.SkipTo[s: path, pos: 0, skip: "[]<>"] # path.Length[]
THEN
path ¬ Rope.Translate[base: path, translator: ToSlash];
lastPostSlash2 ¬ path;
RETURN [path];
};
};
HomeDirectory:
PUBLIC
ENTRY
PROC
RETURNS [
ROPE] = {
ENABLE UNWIND => NULL;
user: ROPE ¬ SystemNames.UserName[]; --UserCredentials.Get[].name;
IF (
NOT Rope.Equal[lastUserName, user] )
THEN {
lastHomeDirectory ¬ SystemNames.SimpleHomeDirectory[];
lastUserName ¬ user;
};
RETURN [lastHomeDirectory];
};
IsADirectory:
PUBLIC
PROC [path:
ROPE]
RETURNS [
BOOL] = {
length: INT ¬ Rope.Length[path];
IF length # 0
THEN
SELECT Rope.Fetch[path, length-1]
FROM
'/, '], '> => RETURN [TRUE];
ENDCASE;
RETURN [FALSE];
};
IsAPattern:
PUBLIC
PROC [path:
ROPE]
RETURNS [
BOOL] = {
RETURN [Rope.SkipTo[path, 0, "*"] # Rope.Length[path]];
};
GetShortName:
PUBLIC
PROC
[path:
ROPE, stripOffVersionNumber:
BOOL ¬
TRUE]
RETURNS [
ROPE] = {
len: INT ¬ Rope.Length[path];
bang: INT ¬ len;
pos: INT ¬ len;
WHILE pos # 0
DO
np: INT ¬ pos - 1;
c: CHAR ¬ Rope.Fetch[path, np];
SELECT c
FROM
'! => IF stripOffVersionNumber THEN bang ¬ np;
'>, '], '/ => RETURN [Rope.Substr[path, pos, bang-pos]];
ENDCASE;
pos ¬ np;
ENDLOOP;
RETURN [Rope.Substr[path, 0, bang]];
};
Tail:
PUBLIC
PROC [s:
ROPE, char:
CHAR]
RETURNS [
ROPE] = {
Tail returns the part of a rope after the last instance of char.
pos: INT ¬ s.Length[];
WHILE pos # 0
DO
IF Rope.Fetch[s, pos-1] = char THEN RETURN [Rope.Substr[s, pos]];
pos ¬ pos - 1;
ENDLOOP;
RETURN [s];
};
StripVersionNumber:
PUBLIC
PROC [path:
ROPE]
RETURNS [
ROPE] = {
pos: INT ¬ path.Find["!"];
IF pos = -1 THEN RETURN [path];
RETURN [path.Substr[0, pos]];
};
IsRemote:
PUBLIC
PROC [path:
ROPE]
RETURNS [
BOOL] = {
first, second: CHAR;
IF path.Length[] < 2 THEN RETURN [FALSE];
first ¬ path.Fetch[0];
second ¬ path.Fetch[1];
SELECT first
FROM
'/ => RETURN [second # '/];
'[ => RETURN [second # ']];
ENDCASE;
RETURN [FALSE];
};
InASubdirectory:
PUBLIC
PROC [parent:
ROPE, path:
ROPE]
RETURNS [
BOOL] = {
pos: INT;
path ¬ ConvertToSlashFormat[path];
parent ¬ ConvertToSlashFormat[parent];
pos ¬ Rope.Find[s1: path, s2: parent, case: FALSE];
IF pos = -1 THEN RETURN [FALSE];
RETURN [Rope.Find[s1: path, s2: "/", pos1: parent.Length[]] # -1];
};
FirstSubdirectory:
PUBLIC
PROC [parent:
ROPE, path:
ROPE]
RETURNS [
ROPE] = {
pos: INT;
end: INT;
path ¬ ConvertToSlashFormat[path];
parent ¬ ConvertToSlashFormat[parent];
pos ¬ Rope.Find[s1: path, s2: parent, case: FALSE];
IF pos # 0 THEN RETURN [NIL];
pos ¬ parent.Length[];
IF pos = 0 THEN RETURN [NIL];
end ¬ Rope.Find[s1: path, s2: "/", pos1: pos];
IF end = -1 THEN RETURN [NIL];
RETURN [Rope.Substr[base: path, start: 0, len: end + 1]];
};
DirectoryContaining:
PUBLIC
PROC [path:
ROPE, pos:
INT]
RETURNS [
ROPE] = {
Return the prefix of the part of name before pos which is the name of a directory.
WHILE pos > 0
DO
c: CHAR ¬ Rope.Fetch[path, pos ¬ pos - 1];
SELECT c
FROM
'>, '], '/ => RETURN [Rope.Substr[path, 0, pos+1]];
ENDCASE;
ENDLOOP;
RETURN [NIL];
};
Directory:
PUBLIC
PROC [path:
ROPE]
RETURNS [
ROPE] = {
Return the directory part of a name.
RETURN [DirectoryContaining[path, Rope.Length[path]]];
};
Parent:
PUBLIC
PROC [path:
ROPE]
RETURNS [
ROPE] = {
If path is a directory, then returns the parent directory. If path is a file, then returns the current directory. In either case, this means that Parent returns the directory which contains the current object.
len: INT ¬ Rope.Length[path];
pos: INT ¬ len;
first: BOOL ¬ TRUE;
WHILE pos # 0
DO
c: CHAR ¬ Rope.Fetch[path, pos - 1];
SELECT c
FROM
'>, '], '/ => IF pos # len THEN RETURN [Rope.Substr[path, 0, pos]];
ENDCASE;
pos ¬ pos - 1;
ENDLOOP;
RETURN [NIL];
};
Extras
RelativizePath:
PUBLIC
PROC [path:
ROPE]
RETURNS [
ROPE] = {
homeDir: ROPE ¬ HomeDirectory[];
homeDirLen: INT ¬ Rope.Length[homeDir];
slashName: ROPE ¬ ConvertToSlashFormat[path];
IF Rope.Run[homeDir, 0, slashName, 0,
FALSE] = homeDirLen
THEN
path ¬ Rope.Replace[path, 0, homeDirLen, "~/"];
RETURN [path];
};
END.