DIRECTORY
Basics USING [DIVMOD],
FS USING [ComponentPositions, ComponentRopes, maxFNameLength, Position],
FSBackdoor USING [highestVersion, lowestVersion, ProduceError, Version],
FSFileOps USING [GetVolumeDesc, VolumeDesc],
FSName USING [ParsedFName, VersionInfo],
List USING [Assoc],
ProcessProps USING [GetPropList],
Rope USING [Cat, Concat, Fetch, Flatten, Index, Length, NewText, Replace, Substr, ROPE, Text];
Exported to FS
SetDefaultWDir:
PUBLIC
PROC [dir: Rope.
ROPE] =
BEGIN
Inner:
ENTRY
PROC =
{ defaultWDir ← dir };
places: Places;
IF Rope.Length[dir] = 0
THEN dir ← initialDefaultWDir
ELSE
BEGIN
[dir, places] ← ExpandAndFindPlaces[NIL, dir, directory];
IF places.serverEnd # 1 THEN DirNotLocal[dir];
IF places.dirEnd = places.serverEnd THEN NoVolumePart[dir];
dir ← Rope.Flatten[dir];
END;
Inner[];
END;
GetDefaultWDir:
PUBLIC
ENTRY
PROC
RETURNS [Rope.
ROPE] =
{ RETURN [defaultWDir] };
ExpandName:
PUBLIC
PROC[name: Rope.
ROPE, wDir: Rope.
ROPE]
RETURNS [fullFName: Rope.
ROPE, cp:
FS.ComponentPositions, dirOmitted:
BOOLEAN ←
FALSE] =
BEGIN
places: Places;
fNLen: INT;
[fullFName, places] ← ExpandAndFindPlaces[name, wDir, fName];
fNLen ← Rope.Length[fullFName];
cp.server ← [1, places.serverEnd-1];
IF places.dirEnd >= places.serverEnd+2
THEN cp.dir ← [places.serverEnd+2, places.dirEnd-places.serverEnd-2]
ELSE { dirOmitted ← TRUE; cp.dir ← [places.serverEnd+1, 0] };
cp.subDirs ←
IF places.subDirEnd > places.dirEnd
THEN [places.dirEnd+1, places.subDirEnd-places.dirEnd-1]
ELSE [places.dirEnd+1, 0];
cp.base ← [places.subDirEnd+1, places.rootEnd-places.subDirEnd-1];
cp.ext ←
IF places.bang > places.rootEnd
THEN [places.rootEnd+1, places.bang-places.rootEnd-1]
ELSE [places.rootEnd, 0];
IF places.bang < fNLen
THEN
BEGIN
[] ← ParseVersion[fullFName, places.bang+1, FALSE];
cp.ver ← [places.bang+1, fNLen-places.bang-1];
END
ELSE cp.ver ← [fNLen, 0];
END;
ConstructFName:
PUBLIC
PROC [cr:
FS.ComponentRopes, omitDir:
BOOLEAN]
RETURNS [fName: Rope.
ROPE] =
BEGIN
fName ← Rope.Cat[ "[", cr.server, "]" ];
IF NOT omitDir THEN fName ← Rope.Cat[ fName, "<", cr.dir, ">" ];
IF Rope.Length[cr.subDirs] > 0 THEN fName ← Rope.Cat[ fName, cr.subDirs, ">" ];
fName ← Rope.Cat[ fName, cr.base ];
IF Rope.Length[cr.ext] > 0 THEN fName ← Rope.Cat[ fName, ".", cr.ext ];
IF Rope.Length[cr.ver] > 0 THEN fName ← Rope.Cat[ fName, "!", cr.ver ];
END;
Exported to FSName
ParseClientName:
PUBLIC
PROC [clientName, wDir: Rope.
ROPE, defaultVersionHigh, pattern:
BOOLEAN]
RETURNS [pn: FSName.ParsedFName, vI: FSName.VersionInfo] =
BEGIN
vName: Rope.ROPE ← NIL;
places: Places;
[pn.fullName, places] ← ExpandAndFindPlaces[clientName, wDir, IF pattern THEN pattern ELSE fName];
IF places.bang < Rope.Length[pn.fullName]
THEN [pn.version, vI] ← ParseVersion[pn.fullName, places.bang+1, pattern]
ELSE
BEGIN
vI ← missing;
pn.version ← IF defaultVersionHigh THEN FSBackdoor.highestVersion ELSE FSBackdoor.lowestVersion;
END;
IF IsLocal[pn.fullName]
THEN
BEGIN
vL: CARDINAL ← places.dirEnd - places.serverEnd - 2;
IF vL # 0 THEN vName ← Rope.Substr[pn.fullName, places.serverEnd + 2, vL];
pn.nameBody ← Rope.Flatten[ Rope.Substr[pn.fullName, places.dirEnd + 1, places.bang - places.dirEnd - 1] ];
END
ELSE pn.nameBody ← Rope.Flatten[ Rope.Substr[pn.fullName, 0, places.bang] ];
pn.volDesc ← FSFileOps.GetVolumeDesc[vName];
END;
ParseCacheName:
PUBLIC
PROC [volName, cacheName: Rope.
ROPE, pattern:
BOOLEAN]
RETURNS [pn: FSName.ParsedFName, vI: FSName.VersionInfo] =
BEGIN
firstChar: CHARACTER;
length, bangIndex: CARDINAL;
nameUse: NameUse = IF pattern THEN pattern ELSE fName;
pn ← [NIL, NIL, FSBackdoor.highestVersion, FSFileOps.GetVolumeDesc[volName]];
vI ← missing;
IF Rope.Length[cacheName] = 0
THEN
BEGIN
IF pattern
THEN { pn.nameBody ← "[*"; RETURN }
ELSE IllegalName[cacheName, nameUse];
END;
FOR i:
INT
IN [0 .. Rope.Length[cacheName])
DO
-- determine syntax convention
SELECT Rope.Fetch[cacheName, i]
FROM
'[, '], '<, '> => EXIT;
'/ => { cacheName ← ConvertSlashName[cacheName]; EXIT };
ENDCASE;
ENDLOOP;
firstChar ← Rope.Fetch[cacheName, 0];
IF firstChar # '[
THEN
BEGIN
IF firstChar = '*
THEN cacheName ← Rope.Concat["[", cacheName]
ELSE IllegalName[cacheName, nameUse];
END;
length ← Rope.Length[cacheName];
bangIndex ← Rope.Index[cacheName, IF length > 6 THEN length - 6 ELSE 0, "!"];
IF bangIndex # length AND NOT (bangIndex = length-2 AND Rope.Fetch[cacheName, length-1] = '*)
THEN [pn.version, vI] ← ParseVersion[cacheName, bangIndex+1, pattern];
pn.nameBody ← Rope.Flatten[ Rope.Substr[cacheName, 0, bangIndex] ];
END;
ParseName:
PUBLIC
PROC [volName, fName: Rope.
ROPE]
RETURNS [p: FSName.ParsedFName] =
BEGIN
length: CARDINAL = Rope.Length[fName];
bangIndex: CARDINAL = Rope.Index[fName, IF length > 6 THEN length - 6 ELSE 0, "!"];
IF bangIndex = length
THEN p.version ← FSBackdoor.highestVersion
ELSE [p.version, ] ← ParseVersion[fName, bangIndex+1, FALSE];
p.nameBody ← Rope.Flatten[ Rope.Substr[fName, 0, bangIndex] ];
p.fullName ← fName;
p.volDesc ← FSFileOps.GetVolumeDesc[volName];
END;
IsLocal:
PUBLIC
PROC [name: Rope.
ROPE]
RETURNS [
BOOLEAN] =
{RETURN[ Rope.Length[name]=0 OR Rope.Fetch[name, 0]#'[ OR Rope.Fetch[name, 1]='] ]};
ServerAndFileRopes:
PUBLIC PROC [gName: Rope.
ROPE]
RETURNS [server, file: Rope.
ROPE] =
BEGIN
closingBracket: CARDINAL = Rope.Index[gName, 0, "]"];
server ← Rope.Substr[gName, 1, closingBracket-1];
file ← Rope.Substr[gName, closingBracket+1];
END;
BangStarFile:
PUBLIC PROC [file: Rope.
ROPE]
RETURNS [Rope.
ROPE] =
BEGIN
length: CARDINAL = Rope.Length[file];
bangIndex: CARDINAL = Rope.Index[file, IF length > 6 THEN length - 6 ELSE 0, "!"];
RETURN [ Rope.Replace [ file, bangIndex, length - bangIndex, "!*" ] ];
END;
BangVersionFile:
PUBLIC
PROC [file: Rope.
ROPE, version: FSBackdoor.Version]
RETURNS [Rope.
ROPE] =
BEGIN
length: CARDINAL = Rope.Length[file];
bangIndex: CARDINAL = Rope.Index[file, IF length > 6 THEN length - 6 ELSE 0, "!"];
RETURN [ Rope.Replace [ file, bangIndex, length - bangIndex, VersionPartFromVersion[version] ] ];
END;
VersionFromRope:
PUBLIC PROC [r: Rope.
ROPE]
RETURNS [v: FSBackdoor.Version] =
BEGIN
IF Rope.Length[r] = 0
THEN v ← FSBackdoor.highestVersion
ELSE [v, ] ← ParseVersion[r, 0, FALSE];
END;
VersionPartFromVersion:
PUBLIC
PROC [version: FSBackdoor.Version]
RETURNS [r: Rope.Text] =
BEGIN
Decimate:
PROC [num:
CARDINAL] =
BEGIN
q, r: CARDINAL;
[q, r] ← Basics.DIVMOD[num, 10];
IF q # 0 THEN Decimate[q];
AppendChar[t, r+'0];
END;
t: REF TEXT;
SELECT version
FROM
FSBackdoor.lowestVersion, FSBackdoor.highestVersion => r ← NIL;
ENDCASE =>
TRUSTED BEGIN
r ← Rope.NewText[6];
t ← LOOPHOLE[r];
t[0] ← '!;
t.length ← 1;
Decimate[version];
END;
END;
Internal procedures
NameUse: TYPE = {fName, pattern, directory};
Places: TYPE = RECORD[serverEnd, dirEnd, subDirEnd, bang, rootEnd, firstStar: CARDINAL ← 0];
ExpandAndFindPlaces:
PROC [name, wDir: Rope.
ROPE, nameUse: NameUse]
RETURNS [fN: Rope.
ROPE, p: Places] =
If an FS.Error is not generated then the full FName is returned as "fN" and the parts of this FName are located by the indexes in "p" according to the following rules (first means left-most, last means right-most, and after means to-the-right-of):
p.serverEnd = index of the "]";
p.dirEnd = IF no ">" THEN p.serverEnd ELSE index of first ">";
p.subDirEnd = IF no ">" THEN p.dirEnd ELSE index of last ">";
p.bang = IF no "!" THEN Rope.Length[fN] ELSE index of first "!";
p.rootEnd = IF no "." after p.subDirEnd THEN p.bang ELSE index of last "." after p.subDirEnd;
p.firstStar = index of the first "*".
Note that all parsing stops when the first "!" is encountered. It is the client's responsibility to verify that the characters following the "!" constitute a valid version part.
BEGIN
i: INT;
leftPoint: BOOLEAN ← FALSE;
fNLen: CARDINAL ← Rope.Length[name];
fN ← name;
IF fNLen # 0
THEN
SELECT Rope.Fetch[fN, 0]
FROM
'[ => NULL;
'/ => { fN ← ConvertSlashName[fN]; fNLen ← Rope.Length[fN] };
ENDCASE =>
BEGIN -- need a working directory
IF Rope.Length[wDir] # 0
THEN wDir ← ConvertWDir[wDir] -- use working directory argument
ELSE
BEGIN
wDir ← NARROW [ List.Assoc[key: $WorkingDirectory, aList: ProcessProps.GetPropList[]] ];
IF Rope.Length[wDir] # 0
THEN wDir ← ConvertWDir[wDir] -- use process working directory
ELSE wDir ← GetDefaultWDir[]; -- use default working directory
END;
FOR i
IN [1 .. fNLen)
DO
-- convert the name part if necessary
SELECT Rope.Fetch[fN, i]
FROM
'], '<, '>, '! => NULL;
'/ => fN ← ConvertSlashName[fN];
ENDCASE => LOOP;
EXIT;
ENDLOOP;
fN ← Rope.Cat[wDir, fN];
fNLen ← Rope.Length[fN];
END; -- of need a working directory
i ← 0;
DO
i ← i + 1;
IF i >= fNLen THEN EXIT;
SELECT Rope.Fetch[fN, i]
FROM
IN ['a .. 'z],
IN ['A .. 'Z],
IN ['0 .. '9], '$, '-, '+ =>
NULL;
'] =>
IF p.serverEnd = 0
THEN p.subDirEnd ← p.dirEnd ← p.serverEnd ← i
ELSE IllegalName[fN, nameUse]; -- this is the second ']
'< =>
IF p.serverEnd = i - 1
THEN leftPoint ← TRUE
ELSE IllegalName[fN, nameUse]; -- '< does not immediately follow ']
'> =>
IF leftPoint AND ( i > p.subDirEnd + 1 )
THEN
BEGIN
IF p.dirEnd = p.serverEnd
THEN p.dirEnd ← i; -- end of directory part
p.subDirEnd ← i; -- end of subdirectory part
END
ELSE IllegalName[fN, nameUse]; -- '> in the wrong place
'! =>
{p.bang ← i; EXIT};
'# =>
IF p.serverEnd # 0
THEN
BEGIN
-- not in server part
IF p.serverEnd = 1 AND leftPoint AND i = 3
THEN
DO
-- LName with a max 20 digit hex number as a volume id
i ← i + 1;
IF i >= fNLen THEN EXIT;
IF i > 23 THEN IllegalName[fN, nameUse];
SELECT Rope.Fetch[fN, i]
FROM
IN ['0 .. '9], IN ['A .. 'H] => NULL;
ENDCASE => IllegalName[fN, nameUse];
ENDLOOP
ELSE IllegalName[fN, nameUse]; -- sharpSign in the wrong place
END;
'* =>
IF nameUse = pattern
THEN { IF p.firstStar = 0 THEN p.firstStar ← i }
ELSE NoPatterns[fN]; -- star and we're not parsing a pattern
ENDCASE => IllegalCharacter[fN]; -- illegal character
ENDLOOP;
IF p.bang = 0 THEN p.bang ← fNLen;
IF p.bang > FS.maxFNameLength - 6
THEN TooLong[fN];
IF p.firstStar = 0
THEN
BEGIN
-- not a pattern
IF nameUse # directory AND p.subDirEnd+1 >= p.bang
THEN IllegalName[fN, nameUse]; -- no simple name
END
ELSE
BEGIN
-- have a pattern
IF (p.serverEnd = 1 AND p.firstStar < p.dirEnd) OR (p.firstStar < p.serverEnd)
THEN IllegalName[fN, nameUse]; -- first star is in volume part of LName or in server part
END;
IF p.rootEnd <= p.subDirEnd THEN p.rootEnd ← p.bang;
END;
ParseVersion:
PROC [name: Rope.
ROPE, index:
CARDINAL, pattern:
BOOLEAN ←
FALSE]
RETURNS [value: FSBackdoor.Version, vI: FSName.VersionInfo] =
BEGIN
c: CHAR;
lastIndex: CARDINAL = Rope.Length[name] - 1;
IF lastIndex IN [index .. index+4]
THEN
SELECT c ← Rope.Fetch[name, index]
FROM
IN ['0 .. '9] =>
BEGIN
num: LONG CARDINAL ← 0;
DO
num ← num*10 + (c - '0);
IF index = lastIndex
THEN
BEGIN
IF num NOT IN (FSBackdoor.lowestVersion .. FSBackdoor.highestVersion)
THEN EXIT;
value ← [num];
vI ← number;
RETURN;
END;
index ← index + 1;
c ← Rope.Fetch[name, index];
IF c NOT IN ['0 .. '9]
THEN EXIT;
ENDLOOP;
END;
'H, 'h =>
IF index = lastIndex
THEN { value ← FSBackdoor.highestVersion; vI ← bangH; RETURN };
'L, 'l =>
IF index = lastIndex
THEN { value ← FSBackdoor.lowestVersion; vI ← bangL; RETURN };
'* =>
IF index = lastIndex AND pattern
THEN { value ← FSBackdoor.highestVersion; vI ← bangStar; RETURN };
ENDCASE;
IllegalVersion[name];
END;
QuotedName:
PROC [n: Rope.
ROPE]
RETURNS [Rope.
ROPE] =
BEGIN
quoteRope: Rope.ROPE = "\"";
RETURN [ Rope.Cat[quoteRope, n, quoteRope] ];
END;
DirNotLocal:
PROC [n: Rope.
ROPE] =
{ FSBackdoor.ProduceError[badWorkingDir,
Rope.Concat[QuotedName[n], " is not a local directory."] ] };
NoVolumePart:
PROC [n: Rope.
ROPE] =
{ FSBackdoor.ProduceError[badWorkingDir,
Rope.Concat[QuotedName[n], " needs a volume part."] ] };
TooLong:
PROC [n: Rope.
ROPE] =
{ FSBackdoor.ProduceError[ illegalName,
Rope.Concat[QuotedName[n], " has more than 120 characters."] ] };
IllegalCharacter:
PROC [n: Rope.
ROPE] =
{ FSBackdoor.ProduceError[ illegalName,
Rope.Concat[QuotedName[n], " contains an illegal character."] ] };
IllegalVersion:
PROC [n: Rope.
ROPE] =
{ FSBackdoor.ProduceError[ illegalName,
Rope.Concat[QuotedName[n], " has an illegal version part."] ] };
IllegalName:
PROC [n: Rope.
ROPE, nameUse: NameUse] =
BEGIN
e: Rope.
ROPE =
SELECT nameUse
FROM
fName => " is not a legal FName.",
pattern => " is not a legal pattern.",
directory => " is not a legal directory name",
ENDCASE => ERROR;
FSBackdoor.ProduceError [illegalName, Rope.Concat[QuotedName[n], e] ];
END;
NoPatterns:
PROC [n: Rope.
ROPE] =
{ FSBackdoor.ProduceError[ patternNotAllowed,
Rope.Concat[QuotedName[n], " contains a \"*\", but patterns are not allow for this operation."] ] };
AppendChar:
PROC [text:
REF
TEXT, c:
CHAR] =
INLINE
BEGIN
text[text.length] ← c;
text.length ← text.length + 1;
END;
ConvertWDir:
PROC [wDir: Rope.
ROPE]
RETURNS [w: Rope.
ROPE] =
BEGIN
SELECT Rope.Fetch[wDir, 0]
FROM
'[ =>
BEGIN
c: CHAR = Rope.Fetch[wDir, Rope.Length[wDir] - 1];
IF c # '> AND c # '] THEN wDir ← Rope.Cat[wDir, ">"];
w ← wDir;
END;
'/ =>
BEGIN
c: CHAR = Rope.Fetch[wDir, Rope.Length[wDir] - 1];
IF c # '/ THEN wDir ← Rope.Cat[wDir, "/"];
w ← ConvertSlashName[wDir];
END;
ENDCASE => IllegalName[wDir, directory];
END;
ConvertSlashName:
PROC [r: Rope.
ROPE]
RETURNS [t: Rope.Text] =
TRUSTED
BEGIN
rL: INT = Rope.Length[r];
text: REF TEXT;
slashCount: CARDINAL ← IF Rope.Fetch[r, 0] = '/ THEN 0 ELSE 2;
t ← Rope.NewText[rL+1];
text ← LOOPHOLE[t];
text.length ← 0;
FOR i:
INT
IN [0 .. rL)
DO
c: CHAR = Rope.Fetch[r, i];
IF c = '/
THEN
BEGIN
slashCount ← slashCount + 1;
SELECT slashCount
FROM
1 => AppendChar[text, '[];
2 =>
BEGIN
AppendChar[text, ']];
FOR j:
INT
IN [i+1 .. rL)
DO
-- scan ahead for another slash
IF Rope.Fetch[r, j] = '/
THEN {AppendChar[text, '<]; EXIT};
ENDLOOP;
END;
ENDCASE => AppendChar[text, '>];
END
ELSE AppendChar[text, c];
ENDLOOP;
END;