FileSet Operations:
NewFileSet:
PUBLIC
PROC [ids: IdentificationScheme ← []]
RETURNS [fs: FileSet] = {
fs ←
NEW [FileSetRep ← [
elts: RedBlackTree.Create[Id, IF ids.askFS OR ids.create THEN CompareFullFileNotes ELSE CompareFileNotesByName],
ids: ids,
summary: NIL]];
};
Id:
PROC [data:
REF
ANY]
RETURNS [key:
REF
ANY]
--RedBlackTree.GetKey-- =
{key ← data};
CompareFileNotesByName:
PROC [k, data:
REF
ANY]
RETURNS [c: Basics.Comparison]
--RedBlackTree.Compare-- = {
fn1: FileNote ← NARROW[k];
fn2: FileNote ← NARROW[data];
c ← fn1.id.name.Compare[fn2.id.name, FALSE]};
CompareFullFileNotes:
PROC [k, data:
REF
ANY]
RETURNS [c: Basics.Comparison]
--RedBlackTree.Compare-- = {
fn1: FileNote ← NARROW[k];
fn2: FileNote ← NARROW[data];
IF (c ← fn1.id.name.Compare[fn2.id.name,
FALSE]) = equal
THEN {
c ←
SELECT BasicTime.Period[from: fn1.id.created, to: fn2.id.created]
FROM
<0 => less,
=0 => equal,
>0 => greater,
ENDCASE => ERROR;
};
};
EnumSet:
PUBLIC
PROC [fs: FileSet, to: FileConsumer] = {
FOR fn: FileNote ←
NARROW[fs.elts.LookupSmallest[]],
NARROW[fs.elts.LookupNextLarger[fn]]
WHILE fn #
NIL
DO
Process.CheckForAbort[];
to[fn];
ENDLOOP;
fs ← fs};
First:
PUBLIC
PROC [fs: FileSet]
RETURNS [fn: FileNote] =
{fn ← NARROW[fs.elts.LookupSmallest[]]};
Next:
PUBLIC
PROC [fs: FileSet, fn: FileNote]
RETURNS [next: FileNote] =
{next ← NARROW[fs.elts.LookupNextLarger[fn]]};
Lookup:
PUBLIC
PROC [fs: FileSet, fn: FileNote]
RETURNS [found: FileNote] =
{found ← NARROW[fs.elts.Lookup[fn]]};
Delete:
PUBLIC
PROC [fs: FileSet, fn: FileNote]
RETURNS [found: FileNote] = {
node: RedBlackTree.Node ← fs.elts.Delete[fn];
found ← IF node # NIL THEN NARROW[node.data] ELSE NIL;
};
Insert:
PUBLIC
PROC [fs: FileSet, fn: FileNote]
RETURNS [news:
BOOL] = {
prevFN: FileNote ← Lookup[fs, fn];
SELECT (news ← prevFN =
NIL)
FROM
TRUE => fs.elts.Insert[fn, fn];
FALSE => NULL;
ENDCASE => ERROR;
};
Size:
PUBLIC
PROC [fs: FileSet]
RETURNS [numberOfFiles:
INT] =
{numberOfFiles ← fs.elts.Size[]};
MapNames:
PUBLIC
PROC [fs: FileSet, map: RopeMap, mapIDName, mapFSName:
BOOL ←
TRUE]
RETURNS [mfs: FileSet] = {
TranslateFile:
PROC [fn: FileNote] = {
newFN: FileNote ← NEW [FileNoteRep ← fn^];
IF mapIDName THEN newFN.id.name ← MapName[newFN.id.name, mfs.ids];
IF mapFSName THEN newFN.fsName ← MapName[newFN.fsName, fullID];
IF NOT Insert[mfs, newFN] THEN ERROR;
};
MapName:
PROC [pn:
ROPE, ids: IdentificationScheme]
RETURNS [rn:
ROPE] = {
full: ROPE;
cp: FS.ComponentPositions;
rn ← map.Apply[pn];
[full, cp] ← FS.ExpandName[rn];
rn ← TrimNameByIDS[full, cp, ids];
};
mfs ← NewFileSet[fs.ids];
EnumSet[fs, TranslateFile];
mfs.summary ← Convert.RopeFromInt[Size[mfs]].Cat[":(MapNames ", fs.summary, ")"];
};
fullID: IdentificationScheme = [server: TRUE, directory: TRUE, version: TRUE, create: TRUE, askFS: FALSE];
ReIdentify:
PUBLIC
PROC [fs: FileSet, ids: IdentificationScheme ← []]
RETURNS [mfs: FileSet] = {
Mapit:
PROC [fn: FileNote] = {
newFN: FileNote = CreateNote[fn.id.name, fn.id.created, ids];
IF newFN # NIL THEN [] ← Insert[mfs, newFN];
};
mfs ← NewFileSet[ids];
EnumSet[fs, Mapit];
mfs.summary ← Convert.RopeFromInt[Size[mfs]].Cat[":(ReIdentify ", fs.summary, ")"];
};
MapNameAndLookup:
PUBLIC
PROC [fs: FileSet, map: RopeMap, ids: IdentificationScheme ← []]
RETURNS [mfs: FileSet] = {
TranslateFile:
PROC [fn: FileNote] = {
newName: ROPE = map.Apply[fn.id.name];
newFN: FileNote = CreateNote[newName, noGMT, ids];
IF newFN # NIL THEN [] ← Insert[mfs, newFN];
};
mfs ← NewFileSet[ids];
EnumSet[fs, TranslateFile];
mfs.summary ← Convert.RopeFromInt[Size[mfs]].Cat[":(MapNameAndLookup ", fs.summary, ")"];
};
pseudoServerToRead:
PUBLIC RopeMap ←
NEW [TextReplace.RopeMapRep ← [
MapPseudoServerToRead]];
MapPseudoServerToRead:
PROC [data:
REF
ANY, pn:
ROPE]
RETURNS [rn:
ROPE] = {
full: ROPE;
cp: FS.ComponentPositions;
IF pn.Length[] = 0 THEN RETURN [pn];
[full, cp] ← FS.ExpandName[pn];
rn ←
FS.ConstructFName[[
server: FSPseudoServers.TranslateForRead[full.Substr[cp.server.start, cp.server.length]].first,
dir: full.Substr[cp.dir.start, cp.dir.length],
subDirs: full.Substr[cp.subDirs.start, cp.subDirs.length],
base: full.Substr[cp.base.start, cp.base.length],
ext: full.Substr[cp.ext.start, cp.ext.length],
ver: full.Substr[cp.ver.start, cp.ver.length]
]];
};
Direction: TYPE = {normal, reverse};
HasRelative:
PROC [fn: FileNote, fs: FileSet, fr: FileRelation, direction: Direction]
RETURNS [has:
BOOL] = {
NoteRelative: PROC [rfn: FileNote] = {has ← TRUE; Enuf};
has ← FALSE;
EnumerateRelatives[fn, fs, fr, direction, NoteRelative !Enuf => CONTINUE];
has ← has;
};
Enuf: ERROR = CODE;
EnumerateRelatives:
PROC [fn: FileNote, fs: FileSet, fr: FileRelation, direction: Direction,
NoteRelative:
PROC [FileNote]] = {
fir: FileIDRange =
SELECT direction
FROM
normal => fr.LeftToRight[fr.data, fn.id],
reverse => fr.RightToLeft[fr.data, fn.id],
ENDCASE => ERROR;
Relates:
PROC [fn2: FileNote]
RETURNS [b:
BOOL] = {
b ←
SELECT direction
FROM
normal => fr.Test[fr.data, fn.id, fn2.id],
reverse => fr.Test[fr.data, fn2.id, fn.id],
ENDCASE => ERROR;
};
test: FileNote =
NEW [FileNoteRep ← [
id: LowEnd[fir]
]];
highID: FileID = HighEnd[fir];
found: FileNote ← NARROW[fs.elts.Lookup[test]];
IF found # NIL AND Relates[found] THEN NoteRelative[found];
FOR found ←
NARROW[fs.elts.LookupNextLarger[test]],
NARROW[fs.elts.LookupNextLarger[found]]
WHILE found #
NIL
AND LessOrEqual[found.id, highID]
DO
IF Relates[found] THEN NoteRelative[found];
ENDLOOP;
found ← found;
};
LowEnd:
PROC [fir: FileIDRange]
RETURNS [fid: FileID] = {
fid ← [
name: fir.lowName,
created: IF fir.earlyCreated = noGMT THEN BasicTime.earliestGMT ELSE fir.earlyCreated
];
};
HighEnd:
PROC [fir: FileIDRange]
RETURNS [fid: FileID] = {
fid ← [
name: fir.highName,
created: IF fir.lateCreated = noGMT THEN BasicTime.latestGMT ELSE fir.lateCreated
];
};
LessOrEqual:
PROC [fid1, fid2: FileID]
RETURNS [leq:
BOOL] = {
leq ←
SELECT fid1.name.Compare[fid2.name,
FALSE]
FROM
less => TRUE,
equal => BasicTime.Period[from: fid1.created, to: fid2.created] <= 0,
greater => FALSE,
ENDCASE => ERROR;
};
OpWork:
PROC [fsl: FileSetList, head:
ROPE, combine:
PROC [sofar, this: FileSet]
RETURNS [sum: FileSet]]
RETURNS [fs: FileSet] = {
summary: ROPE ← head;
fs ← NIL;
IF fsl = NIL THEN ERROR Error["NIL FileSetList given --- that's a no-no"];
FOR fsl ← fsl, fsl.rest
WHILE fsl #
NIL
DO
IF fs # NIL AND fs.ids # fsl.first.ids THEN ERROR;
fs ← combine[fs, fsl.first];
summary ← summary.Cat[" ", Convert.RopeFromInt[Size[fsl.first]]];
ENDLOOP;
IF fs = NIL THEN ERROR;
fs.summary ← Convert.RopeFromInt[Size[fs]].Cat[":", summary, ")"];
};
Union:
PUBLIC
PROC [fsl: FileSetList, fr: FileRelation ←
NIL]
RETURNS [fs: FileSet] = {
UnionCombine:
PROC [sofar, this: FileSet]
RETURNS [sum: FileSet] = {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[sum, fn]};
AddIfNotRelated:
PROC [fn: FileNote]
--FileConsumer-- = {
related: BOOL = HasRelative[fn, sum, fr, reverse];
IF NOT related THEN [] ← Insert[sum, fn];
};
sum ← sofar;
IF sum = NIL THEN sum ← NewFileSet[this.ids];
EnumSet[this, IF fr = NIL THEN Add ELSE AddIfNotRelated];
};
fs ← OpWork[fsl, "(union", UnionCombine];
};
Difference:
PUBLIC
PROC [fsl: FileSetList, fr: FileRelation ←
NIL]
RETURNS [fs: FileSet] = {
DiffCombine:
PROC [sofar, this: FileSet]
RETURNS [sum: FileSet] = {
IF sofar =
NIL
THEN {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[sum, fn]};
sum ← NewFileSet[this.ids];
EnumSet[this, Add];
}
ELSE {
Subtract: PROC [fn: FileNote] --FileConsumer-- = {[] ← Delete[sum, fn]};
SubtractRelatives:
PROC [fn: FileNote]
--FileConsumer-- = {
EnumerateRelatives[fn, sum, fr, reverse, Subtract];
};
sum ← sofar;
EnumSet[this, IF fr = NIL THEN Subtract ELSE SubtractRelatives];
};
};
fs ← OpWork[fsl, "(diff", DiffCombine];
};
SymmetricDifference:
PUBLIC
PROC [fsl: FileSetList, fr: FileRelation ←
NIL]
RETURNS [fs: FileSet] = {
SDiffCombine:
PROC [sofar, this: FileSet]
RETURNS [sum: FileSet] = {
IF sofar =
NIL
THEN {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[sum, fn]};
sum ← NewFileSet[this.ids];
EnumSet[this, Add];
}
ELSE {
SimpleSDiffit:
PROC [fn: FileNote]
--FileConsumer-- = {
SELECT Lookup[sum, fn]
FROM
=NIL => [] ← Insert[sum, fn];
#NIL => [] ← Delete[sum, fn];
ENDCASE => ERROR;
};
GeneralSDiffit:
PROC [fn: FileNote]
--FileConsumer-- = {
related: BOOL ← FALSE;
SeeLeftee:
PROC [fn: FileNote]
--FileConsumer-- = {
[] ← Delete[sum, fn];
related ← TRUE;
};
EnumerateRelatives[fn, sum, fr, reverse, SeeLeftee];
IF NOT related THEN [] ← Insert[sum, fn];
};
sum ← sofar;
EnumSet[this, IF fr = NIL THEN SimpleSDiffit ELSE GeneralSDiffit];
};
};
fs ← OpWork[fsl, "(sdiff", SDiffCombine];
};
Intersection:
PUBLIC
PROC [fsl: FileSetList, fr: FileRelation ←
NIL]
RETURNS [fs: FileSet] = {
IntersectionCombine:
PROC [sofar, this: FileSet]
RETURNS [sum: FileSet] = {
IF sofar =
NIL
THEN {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[sum, fn]};
sum ← NewFileSet[this.ids];
EnumSet[this, Add];
}
ELSE {
SimpleIntersect:
PROC [fn: FileNote]
--FileConsumer-- = {
IF Lookup[this, fn] = NIL THEN [] ← Delete[sum, fn];
};
GeneralIntersect:
PROC [fn: FileNote]
--FileConsumer-- = {
IF NOT HasRelative[fn, this, fr, normal] THEN [] ← Delete[sum, fn];
};
sum ← sofar;
EnumSet[sum, IF fr = NIL THEN SimpleIntersect ELSE GeneralIntersect];
};
};
fs ← OpWork[fsl, "(intersect", IntersectionCombine];
};
equality:
PUBLIC FileRelation ←
NEW [FileRelationPrivate ← [
TestEquality, PointRange, PointRange]];
TestEquality:
PROC [data:
REF
ANY, left, right: FileID]
RETURNS [b:
BOOL] = {
b ←
left.name.Equal[right.name, FALSE]
AND left.created = right.created;
};
PointRange:
PROC [data:
REF
ANY, fid: FileID]
RETURNS [fir: FileIDRange] = {
fir ← [
lowName: fid.name,
highName: fid.name,
earlyCreated: fid.created,
lateCreated: fid.created
];
};
ReverseFileRelation:
PUBLIC
PROC [fr: FileRelation]
RETURNS [frr: FileRelation] = {
frr ←
NEW [FileRelationPrivate ← [
TestReverse, ReverseLeftToRight, ReverseRightToLeft, fr]];
};
TestReverse:
PROC [data:
REF
ANY, left, right: FileID]
RETURNS [b:
BOOL] = {
fr: FileRelation = NARROW[data];
b ← fr.Test[fr.data, right, left];
};
ReverseLeftToRight:
PROC [data:
REF
ANY, fid: FileID]
RETURNS [fir: FileIDRange] = {
fr: FileRelation = NARROW[data];
fir ← fr.RightToLeft[fr.data, fid];
};
ReverseRightToLeft:
PROC [data:
REF
ANY, fid: FileID]
RETURNS [fir: FileIDRange] = {
fr: FileRelation = NARROW[data];
fir ← fr.LeftToRight[fr.data, fid];
};
FilterFileSet:
PUBLIC
PROC [subject: FileSet, filter: Filter]
RETURNS [filtered: FileSet] = {
DoFilter:
PROC [fn: FileNote] =
{IF filter.Eval[fn, filter.data] THEN [] ← Insert[filtered, fn]};
filtered ← NewFileSet[subject.ids];
EnumSet[subject, DoFilter];
filtered.summary ← Convert.RopeFromInt[Size[filtered]].Cat[":(filter ", subject.summary, ")"];
};
And:
PUBLIC
PROC [fl: FilterList]
RETURNS [f: Filter] = {
f ← NEW [FilterRep ← [AndFilters, fl]];
};
AndFilters:
PROC [fn: FileNote, data:
REF
ANY]
RETURNS [
BOOL] = {
fl: FilterList ← NARROW[data];
FOR fl ← fl, fl.rest
WHILE fl #
NIL
DO
IF NOT fl.first.Eval[fn, fl.first.data] THEN RETURN [FALSE];
ENDLOOP;
RETURN [TRUE];
};
Or:
PUBLIC
PROC [fl: FilterList]
RETURNS [f: Filter] = {
f ← NEW [FilterRep ← [OrFilters, fl]];
};
OrFilters:
PROC [fn: FileNote, data:
REF
ANY]
RETURNS [
BOOL] = {
fl: FilterList ← NARROW[data];
FOR fl ← fl, fl.rest
WHILE fl #
NIL
DO
IF fl.first.Eval[fn, fl.first.data] THEN RETURN [TRUE];
ENDLOOP;
RETURN [FALSE];
};
Not:
PUBLIC
PROC [f: Filter]
RETURNS [nf: Filter] = {
nf ← NEW [FilterRep ← [NegateFilter, f]]};
NegateFilter:
PROC [fn: FileNote, data:
REF
ANY]
RETURNS [
BOOL] = {
f: Filter ← NARROW[data];
RETURN [NOT f.Eval[fn, f.data]];
};
IfCreated:
PUBLIC
PROC [reln: TimeReln, when:
GMT]
RETURNS [f: Filter] = {
f ← NEW [FilterRep ← [TestTime, NEW [TimeFilterRep ← [created, reln, when]]]];
};
IfRead:
PUBLIC
PROC [reln: TimeReln, when:
GMT]
RETURNS [f: Filter] = {
f ← NEW [FilterRep ← [TestTime, NEW [TimeFilterRep ← [read, reln, when]]]];
};
TimeFilter: TYPE = REF TimeFilterRep;
TimeFilterRep:
TYPE =
RECORD [
which: WhichTime,
reln: TimeReln,
when: GMT];
WhichTime: TYPE = {created, read};
TestTime:
PROC [fn: FileNote, data:
REF
ANY]
RETURNS [
BOOL] = {
tf: TimeFilter ← NARROW[data];
time:
GMT ←
SELECT tf.which
FROM
read => GetAccounting[fn].read,
created => GetCreated[fn],
ENDCASE => ERROR;
IF time = noGMT THEN RETURN [FALSE];
RETURN [BasicTime.Period[from: tf.when, to: time] * (SELECT tf.reln FROM before => -1, after => 1, ENDCASE => ERROR) > 0];
};
NameMatches:
PUBLIC
PROC [pattern:
ROPE, literal, word, ignoreCase, addBounds:
BOOL ←
FALSE]
RETURNS [f: Filter] = {
f ← NEW [FilterRep ← [TestName, TextFind.CreateFromRope[pattern: pattern, literal: literal, word: word, ignoreCase: ignoreCase, addBounds: addBounds] ]];
};
TestName:
PROC [fn: FileNote, data:
REF
ANY]
RETURNS [
BOOL] = {
finder: TextFind.Finder ← NARROW[data];
RETURN [TextFind.SearchRope[finder, fn.fsName].found];
};
isOnline: PUBLIC Filter ← NEW [FilterRep ← [TestOnline, NIL]];
TestOnline:
PROC [fn: FileNote, data:
REF
ANY]
RETURNS [
BOOL] = {
exists: BOOL ← TRUE;
[] ← FS.FileInfo[name: fn.fsName, wantedCreatedTime: GetCreated[fn] !FS.Error => {exists ← FALSE; CONTINUE}];
RETURN [exists];
};
DFContentsFilter: TYPE = REF DFContentsFilterRec;
DFContentsFilterRec:
TYPE =
RECORD [
filter: Filter,
dfFilter: DFUtilities.Filter,
allowableExceptions: NAT,
ids: IdentificationScheme
];
ContentsPass:
PUBLIC
PROC [filter: Filter, dfFilter: DFUtilities.Filter, allowableExceptions:
NAT ← 0, ids: IdentificationScheme]
RETURNS [f: Filter
--applicable only to DF files--] = {
f ← NEW [FilterRep ← [TestDFContents, NEW [DFContentsFilterRec ← [filter, dfFilter, allowableExceptions]] ]];
};
TestDFContents:
PROC [fn: FileNote, data:
REF
ANY]
RETURNS [
BOOL] = {
dfcf: DFContentsFilter ← NARROW[data];
exceptions: NAT ← 0;
TestContent:
PROC [fn: FileNote] = {
IF NOT dfcf.filter.Eval[fn, dfcf.filter.data] THEN exceptions ← exceptions + 1;
};
created: GMT ← GetCreated[fn];
EnumDF[
dfName: fn.fsName,
date: IF created # noGMT THEN [explicit, created] ELSE [],
to: TestContent,
filter: dfcf.dfFilter,
which: remote,
ids: dfcf.ids];
RETURN [exceptions <= dfcf.allowableExceptions];
};
Primitive Enumerations:
CreateNote:
PUBLIC
PROC [fileName:
ROPE, created:
GMT, ids: IdentificationScheme]
RETURNS [fn: FileNote] = {
createTried: BOOL ← FALSE;
fsName: ROPE ← NIL;
realCreated: GMT ← created;
SELECT ids.askFS
FROM
TRUE => {
ok: BOOL ← TRUE;
createTried ← TRUE;
[fullFName: fileName, created: created] ← FS.FileInfo[name: fileName, wantedCreatedTime: created, remoteCheck: FALSE !FS.Error => {ok ← FALSE; CONTINUE}];
IF
NOT ok
THEN {
SIGNAL Miss[fileName, created];
RETURN [NIL]};
fsName ← fileName;
realCreated ← created;
createTried ← TRUE;
};
FALSE => {
cp: FS.ComponentPositions;
needInfo: BOOL ← FALSE;
[fileName, cp] ← FS.ExpandName[fileName];
IF (ids.version
AND cp.ver.length = 0)
OR (ids.create
AND created = noGMT)
THEN {
ok: BOOL ← TRUE;
[fullFName: fileName, created: created] ← FS.FileInfo[name: fileName, wantedCreatedTime: created, remoteCheck: FALSE !FS.Error => {ok ← FALSE; CONTINUE}];
IF
NOT ok
THEN {
SIGNAL Miss[fileName, created];
RETURN [NIL]};
[fileName, cp] ← FS.ExpandName[fileName];
realCreated ← created;
createTried ← TRUE;
};
fsName ← fileName;
fileName ← TrimNameByIDS[fileName, cp, ids];
IF NOT ids.create THEN created ← noGMT;
};
ENDCASE => ERROR;
fn ← NEW [FileNoteRep ← [id: [name: fileName, created: created], fsName: fsName, created: realCreated, createTried: createTried]];
};
TrimNameByIDS:
PROC [full:
ROPE, cp:
FS.ComponentPositions, ids: IdentificationScheme]
RETURNS [fileName:
ROPE] = {
fileName ← full;
IF NOT ids.version THEN fileName ← fileName.Substr[len: cp.ext.start + cp.ext.length];
IF NOT ids.directory THEN fileName ← fileName.Substr[start: cp.base.start]
ELSE IF NOT ids.server THEN fileName ← fileName.Substr[start: cp.dir.start-1];
};
FromFS:
PUBLIC
PROC [pattern:
ROPE, ids: IdentificationScheme ← []]
RETURNS [fs: FileSet] = {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[fs, fn]};
fs ← NewFileSet[ids];
EnumFSMatches[pattern, Add, ids];
fs.summary ← Convert.RopeFromInt[Size[fs]];
};
EnumFSMatches:
PUBLIC
PROC [pattern:
ROPE, to: FileConsumer, ids: IdentificationScheme ← []] = {
PerMatch:
PROC [fullFName:
ROPE, created:
GMT, primaryVolume, backupVolume:
ROPE] = {
fn: FileNote ← CreateNote[fullFName, created, ids];
IF primaryVolume # NIL OR backupVolume # NIL THEN ERROR;
IF fn # NIL THEN to[fn];
};
HashEnumerate[pattern, FSEnumerate, PerMatch];
};
FSEnumerate:
PROC [pattern:
ROPE,
Consume: Consumer] = {
PerFile:
PROC [
fullFName, attachedTo:
ROPE, created: BasicTime.
GMT, bytes:
INT, keep:
CARDINAL]
RETURNS [continue:
BOOL] = {
Consume[fullFName, created, NIL, NIL];
continue ← TRUE};
FS.EnumerateForInfo[pattern: pattern, proc: PerFile];
};
FromVersionMapsByShortName:
PUBLIC
PROC [vml: VersionMapList, pattern:
ROPE, ids: IdentificationScheme ← []]
RETURNS [fs: FileSet] = {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[fs, fn]};
fs ← NewFileSet[ids];
EnumVersionMapsByShortName[vml, pattern, Add, ids];
fs.summary ← Convert.RopeFromInt[Size[fs]];
};
EnumVersionMapsByShortName:
PUBLIC
PROC [vml: VersionMapList, pattern:
ROPE, to: FileConsumer, ids: IdentificationScheme ← []] = {
VMEnumerate: PROC [pattern: ROPE, Consume: Consumer] = {EnumVMsByShort[vml, pattern, Consume]};
PerMatch:
PROC [fullFName:
ROPE, created:
GMT, primaryVolume, backupVolume:
ROPE] = {
fn: FileNote ← CreateNote[fullFName, created, ids];
IF primaryVolume # NIL OR backupVolume # NIL THEN ERROR;
IF fn # NIL THEN to[fn];
};
HashEnumerate[pattern, VMEnumerate, PerMatch];
};
EnumVMsByShort:
PROC [vml: VersionMapList, pattern:
ROPE,
Consume: Consumer] = {
size: INT = Rope.Length[pattern];
starPos: INT = pattern.Index[0, "*"];
match: BOOL = starPos # size;
prefix: ROPE = Rope.Flatten[pattern, 0, starPos];
prefixLen: INT = Rope.Length[prefix];
rangeList: VersionMap.RangeList ← VersionMap.ShortNameToRanges[vml, prefix];
IF size = 0 THEN RETURN;
WHILE rangeList #
NIL
DO
range: VersionMap.Range ← rangeList.first;
map: VersionMap.Map = range.map;
rangeList ← rangeList.rest;
Process.CheckForAbort[];
IF match
THEN {
entries: CARDINAL = VersionMap.Length[map];
IF range.first >= entries THEN LOOP;
range.len ← entries - range.first;
WHILE range.len # 0
DO
fullName: ROPE;
stamp: BcdDefs.VersionStamp;
thisShort: ROPE;
created: BasicTime.GMT;
[fullName, stamp, created, range] ← VersionMap.RangeToEntry[range];
thisShort ← ShortName[fullName];
IF Rope.Run[prefix, 0, thisShort, 0, FALSE] # prefixLen THEN EXIT;
IF Rope.Match[pattern, thisShort,
FALSE]
THEN {
Consume[fullName, created, NIL, NIL];
};
ENDLOOP;
}
ELSE {
WHILE range.len # 0
DO
fullName: ROPE;
stamp: BcdDefs.VersionStamp;
created: BasicTime.GMT;
[fullName, stamp, created, range] ← VersionMap.RangeToEntry[range];
Consume[fullName, created, NIL, NIL];
ENDLOOP;
};
ENDLOOP;
};
ShortName:
PROC [r:
ROPE]
RETURNS [
ROPE] = {
make a long name into a short one
assumes a valid long name, of course
first: INT ← 0;
last: INT ← Rope.Length[r];
FOR i:
INT
DECREASING
IN [0..last)
DO
c: CHAR ← r.Fetch[i];
SELECT c
FROM
'>, '/ => {first ← i+1; EXIT};
'! => last ← i
ENDCASE;
ENDLOOP;
RETURN [r.Substr[first, last - first]]
};
FromVersionMapsByCreateDate:
PUBLIC
PROC [vml: VersionMapList, range: TimeRange, ids: IdentificationScheme ← []]
RETURNS [fs: FileSet] = {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[fs, fn]};
fs ← NewFileSet[ids];
EnumVersionMapsByCreateDate[vml, range, Add, ids];
fs.summary ← IO.PutFR["%g:(versionMapFilesCreated \"%g\" \"%g\")", [integer[Size[fs]]], [time[range.from]], [time[range.to]]];
};
EnumVersionMapsByCreateDate:
PUBLIC
PROC [vml: VersionMapList, range: TimeRange, to: FileConsumer, ids: IdentificationScheme ← []] = {
FOR vml ← vml, vml.rest
WHILE vml #
NIL
DO
map: VersionMap.Map = vml.first;
Process.CheckForAbort[];
FOR i:
INT
IN [0 .. map.Length[])
DO
created: GMT = map.FetchCreated[i];
IF InRange[created, range]
THEN {
fn: FileNote = CreateNote[VersionMap.FetchName[map, i], created, ids];
IF fn # NIL THEN to[fn];
};
ENDLOOP;
ENDLOOP;
};
InRange:
PROC [time:
GMT, range: TimeRange]
RETURNS [in:
BOOL] =
INLINE {
in ← BasicTime.Period[range.from, time] >= 0 AND BasicTime.Period[time, range.to] >= 0;
};
HashEnumerate:
PUBLIC
PROC [pattern:
ROPE, Enumerate: Enumerator, Consume: Consumer] = {
fsPattern: ROPE ← pattern;
matchPattern: ROPE ← pattern;
filteringNeeded: BOOL ← FALSE;
nChecks: INT ← 0;
checks: LOR ← NIL;
finder: TextFind.Finder ← NIL;
NoteFile:
PROC [fullFName:
ROPE, created:
GMT, primaryVolume, backupVolume:
ROPE] = {
IF filteringNeeded
THEN {
found: BOOL ← TextFind.SearchRope[finder, fullFName].found;
rm: TextReplace.RopeMap ← TextReplace.MapNamedSubfieldToMatch[finder, fullFName];
IF
NOT found
THEN {
SIGNAL Warning[IO.PutFR["FileSets Enumeration/directory filter bug: couldn't match %g against %g", IO.refAny[matchPattern], IO.refAny[fullFName]]];
RETURN;
};
FOR cl:
LOR ← checks, cl.rest
WHILE cl #
NIL
DO
IF rm.Apply[cl.first].Find[">"] # -1 THEN RETURN;
ENDLOOP;
};
Consume[fullFName, created, primaryVolume, backupVolume];
};
FOR i:
INT ← fsPattern.Find["#"], fsPattern.Find["#"]
WHILE i >= 0
DO
filteringNeeded ← TRUE;
fsPattern ← fsPattern.Substr[len: i].Cat["*", fsPattern.Substr[start: i+1]];
ENDLOOP;
IF filteringNeeded
THEN {
IF matchPattern.Find["/"] # -1 THEN ERROR Error[IO.PutFR["FileSets can't enumerate a pattern in slashy format with a # in it (%g)", IO.refAny[matchPattern]]];
SELECT matchPattern.Fetch[0]
FROM
'[, '< => NULL;
IN ['A .. 'Z],
IN ['a .. 'z],
IN ['0 .. '9], '., '!, '*, '# => {
cwd: ROPE ← GetCWD[];
matchPattern ← cwd.Cat[matchPattern];
fsPattern ← cwd.Cat[fsPattern];
};
ENDCASE => ERROR Error[IO.PutFR["FileSets thinks file name pattern %g is bogus", IO.refAny[matchPattern]]];
matchPattern ← TextReplace.Apply[quoteMetas, matchPattern];
matchPattern ← matchPattern.Substr[len: Rope.Index[s1: matchPattern, s2: "!"]].Concat["!*"];
FOR i:
INT ← matchPattern.Find["#"], matchPattern.Find["#"]
WHILE i >= 0
DO
checks ← CONS[IO.PutFR["ck%g", IO.int[nChecks ← nChecks + 1]], checks];
matchPattern ← matchPattern.Substr[len: i].Cat["<", checks.first, ":*>", matchPattern.Substr[start: i+1]];
ENDLOOP;
finder ← TextFind.CreateFromRope[pattern: matchPattern, ignoreCase: TRUE];
};
Enumerate[fsPattern, NoteFile];
};
quoteMetas: TextReplace.RopeMap ← TextReplace.RopeMapFromPairs[LIST[["<", "'<", TRUE], [">", "'>", TRUE]]];
GetCWD:
PROC
RETURNS [cwd:
ROPE
--in brackety format--] = {
cwd ← FS.ExpandName["x!3", CommandTool.CurrentWorkingDirectory[]].fullFName;
cwd ← cwd.Substr[len: cwd.Length[]-3];
};
FromDF:
PUBLIC
PROC [dfName:
ROPE, date: DFUtilities.Date ← [], filter: DFUtilities.Filter, which: DFWhich, ids: IdentificationScheme ← []]
RETURNS [fs: FileSet] = {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[fs, fn]};
fs ← NewFileSet[ids];
EnumDF[dfName, date, Add, filter, which, ids];
fs.summary ← Convert.RopeFromInt[Size[fs]];
};
FromDFs:
PUBLIC
PROC [dfs: FileSet, filter: DFUtilities.Filter, which: DFWhich, ids: IdentificationScheme ← []]
RETURNS [fs: FileSet] = {
Add: PROC [fn: FileNote] --FileConsumer-- = {[] ← Insert[fs, fn]};
PerDF:
PROC [fn: FileNote]
--FileConsumer-- = {
created: GMT ← GetCreated[fn];
dfDate: DFUtilities.Date ← IF created # noGMT THEN [explicit, created] ELSE [];
EnumDF[fn.fsName, dfDate, Add, filter, which, ids];
};
fs ← NewFileSet[ids];
EnumSet[dfs, PerDF];
fs.summary ← Convert.RopeFromInt[Size[fs]].Cat[":(fromDFS ", dfs.summary, ")"];
};
EnumDF:
PUBLIC
PROC [dfName:
ROPE, date: DFUtilities.Date ← [], to: FileConsumer, filter: DFUtilities.Filter, which: DFWhich, ids: IdentificationScheme ← []] = {
dir: ROPE ← "?";
readOnly: BOOL ← FALSE;
PerItem:
PROC [item:
REF
ANY]
RETURNS [stop:
BOOL ←
FALSE] = {
Process.CheckForAbort[];
WITH item
SELECT
FROM
di: REF DFUtilities.DirectoryItem => {dir ← di.path1; readOnly ← di.readOnly};
fi:
REF DFUtilities.FileItem => {
useDir: ROPE ← SELECT which FROM remote => dir, local => NIL, ENDCASE => ERROR;
fiName: ROPE;
fiCP: FS.ComponentPositions;
fn: FileNote;
[fiName, fiCP, ] ← FS.ExpandName[fi.name, useDir];
IF (fn ←
SELECT which
FROM
remote => CreateNote[fiName, fi.date.gmt, ids],
local => CreateNote[DFUtilities.RemoveVersionNumber[fiName], noGMT, ids],
ENDCASE => ERROR) # NIL THEN to[fn];
};
ii:
REF DFUtilities.ImportsItem => {
subFilter: DFUtilities.Filter ← [
comments: filter.comments,
filterA: filter.filterA,
filterB: IF ii.form = $exports THEN $public ELSE filter.filterB,
filterC: $all,
list: IF ii.form = $list THEN ii.list ELSE filter.list
];
EnumDF[ii.path1, ii.date, to, subFilter, which, ids];
};
ii: REF DFUtilities.IncludeItem => EnumDF[ii.path1, ii.date, to, filter, which, ids];
ci: REF DFUtilities.CommentItem => NULL;
wi: REF DFUtilities.WhiteSpaceItem => NULL;
ENDCASE => ERROR;
};
file: FS.OpenFile ← FS.nullOpenFile;
from: STREAM;
file ←
FS.Open[
name: dfName,
wantedCreatedTime:
SELECT date.format
FROM
explicit => date.gmt,
omitted, greaterThan, notEqual => BasicTime.nullGMT,
ENDCASE => ERROR
!
FS.Error => {
SIGNAL Warning[IO.PutFR["FS.Error[%g, %g]; assumed empty", IO.atom[error.code], IO.rope[error.explanation]]];
file ← FS.nullOpenFile;
CONTINUE
}];
IF file = FS.nullOpenFile THEN RETURN;
from ← FS.StreamFromOpenFile[file];
DFUtilities.ParseFromStream[in: from, proc: PerItem, filter: filter !DFUtilities.SyntaxError => {SIGNAL Warning[IO.PutFR["Syntax error near [%g] in DF-file %g: %g", IO.int[from.GetIndex[]], IO.rope[dfName], IO.rope[reason]]]; CONTINUE}];
from.Close[];
};
MentionedDFs:
PUBLIC
PROC [dfName:
ROPE, filter: DFUtilities.Filter ← [], ids: IdentificationScheme ← []]
RETURNS [fs: FileSet] = {
Add: PROC [fn: FileNote] = {[] ← Insert[fs, fn]};
fs ← NewFileSet[ids];
EnumMentionedDFs[dfName, filter, Add, ids];
fs.summary ← Convert.RopeFromInt[Size[fs]];
};
EnumMentionedDFs:
PUBLIC
PROC [dfName:
ROPE, filter: DFUtilities.Filter ← [], to: FileConsumer, ids: IdentificationScheme ← []] = {
ProcessDFFile:
PROC [fullDFName:
ROPE, filter: DFUtilities.Filter] = {
in: IO.STREAM = FS.StreamOpen[fullDFName];
ProcessItem:
PROC [item:
REF
ANY]
RETURNS [stop:
BOOL ←
FALSE]
--DFUtilities.ProcessItemProc-- = {
WITH item
SELECT
FROM
di: REF DFUtilities.DirectoryItem => NULL;
fi: REF DFUtilities.FileItem => NULL;
ii:
REF DFUtilities.ImportsItem => {
fn: FileNote = CreateNote[ii.path1, ii.date.gmt, ids];
to[fn];
};
ii:
REF DFUtilities.IncludeItem => {
fn: FileNote = CreateNote[ii.path1, ii.date.gmt, ids];
to[fn];
ProcessDFFile[ii.path1, filter];
};
ci: REF DFUtilities.CommentItem => NULL;
wi: REF DFUtilities.WhiteSpaceItem => NULL;
ENDCASE => ERROR;
};
DFUtilities.ParseFromStream[in, ProcessItem, filter];
in.Close[];
};
ProcessDFFile[dfName, filter];
};
FromRope:
PUBLIC
PROC [rope:
ROPE, ids: IdentificationScheme ← []]
RETURNS [fs: FileSet] = {
s: STREAM ← IO.RIS[rope];
fs ← FromStream[s, ids];
s.Close[];
};
FromFile:
PUBLIC
PROC [fileName:
ROPE, ids: IdentificationScheme ← []]
RETURNS [fs: FileSet] = {
s: STREAM ← FS.StreamOpen[fileName];
fs ← FromStream[s, ids];
s.Close[];
};
CRBreak:
PROC [char:
CHAR]
RETURNS [cc:
IO.CharClass]
--IO.BreakProc-- = {
cc ←
SELECT char
FROM
'\n => break,
IN ['\000 .. ' ] => sepr,
ENDCASE => other;
};
FromStream:
PUBLIC
PROC [from:
STREAM, ids: IdentificationScheme ← []]
RETURNS [fs: FileSet] = {
fs ← NewFileSet[ids];
FOR i:
INT ← from.SkipWhitespace[], from.SkipWhitespace[]
WHILE
NOT from.EndOf[]
DO
fileName: ROPE ← from.GetTokenRope[CRBreak].token;
fn: FileNote;
created: GMT ← noGMT;
GetToke:
PROC
RETURNS [toke:
ROPE] =
{toke ← IF from.EndOf[] THEN "\n" ELSE from.GetTokenRope[CRBreak].token};
FOR toke:
ROPE ← GetToke[], GetToke[]
UNTIL toke.Equal["\n"]
DO
SELECT
TRUE
FROM
toke.Equal["Of",
FALSE] => {
createdRope: ROPE ← from.GetLineRope[];
[created] ← Tempus.Parse[rope: createdRope, search: FALSE];
EXIT;
};
ENDCASE => ERROR;
ENDLOOP;
fn ← CreateNote[fileName, created, ids];
IF fn # NIL THEN [] ← Insert[fs, fn];
ENDLOOP;
fs.summary ← Convert.RopeFromInt[Size[fs]];
};
ToRope:
PUBLIC
PROC [fs: FileSet]
RETURNS [rope:
ROPE] = {
s: IO.STREAM ← IO.ROS[];
ToStream[fs, s];
rope ← s.RopeFromROS[];
};
ToFile:
PUBLIC
PROC [fs: FileSet, fileName:
ROPE] = {
s: IO.STREAM ← FS.StreamOpen[fileName, create];
ToStream[fs, s];
s.Close[];
};
ToStream:
PUBLIC
PROC [fs: FileSet, to:
IO.
STREAM] = {
PerNote:
PROC [fn: FileNote]
--FileConsumer-- = {
created: GMT ← GetCreated[fn];
to.PutRope[fn.fsName];
IF created # noGMT THEN to.PutF[" Of %g", IO.time[created]];
to.PutChar['\n];
};
EnumSet[fs, PerNote];
};
}.