DoIt: 
PROC [dfNameRope: 
ROPE, table: RedBlackTree.Table, out: 
STREAM, verbose, isFile: 
BOOL] = {
EachEntry: RedBlackTree.EachNode = {
[data: RedBlackTree.UserData] RETURNS [stop: BOOL ← FALSE]
called for each item in the table to do the comparing. 
WITH data 
SELECT 
FROM
entry: FileEntry => {
IF entry.status # both 
THEN {
explain the status of the file
SELECT entry.status 
FROM
dfOnly => 
IF verbose 
THEN {
out.PutF["--*****dfOnly: %g (%g)\n", [rope[entry.fullSrcName]],
IF entry.uid.egmt.gmt = BasicTime.nullGMT THEN [rope["nullGMT"]] ELSE [time[entry.uid.egmt.gmt]] ];
IF isFile THEN out.Flush[];
};
dirOnly => {
IF verbose 
THEN
out.PutF["Delete %g\n\t-- (%g)\n", [rope[entry.fullSrcName]], [time[entry.uid.egmt.gmt]] ]
ELSE out.PutF1["Delete %g\n", [rope[entry.fullSrcName]] ];
IF isFile THEN out.Flush[];
};
 
differentDates => {
out.PutF1["-- ## different dates for %g\n", [rope[entry.fullSrcName]] ];
IF isFile THEN out.Flush[];
};
ENDCASE;
 
};
 
fileCount[entry.status] ¬ fileCount[entry.status].SUCC;
Process.CheckForAbort[];
RETURN;
};
ENDCASE => ERROR;
 
};
 
VisitEntry: 
PROC [name: 
PATH, uid: 
PFS.UniqueID] = {
This procedure is used to visit each file in a simple DF closure, where the imports are NOT followed, but the inclusions ARE followed.
new: FileEntry ¬ NIL;
bytes: INT ¬ 0;
suffix: PATH ¬ NIL;
ropeName: ROPE ¬ PFS.RopeFromPath[name];
fileInfoName: PATH;
status: FileEntryStatus ¬ both; -- assume things are ok
Process.CheckForAbort[];
WITH RedBlackTree.Lookup[table, ropeName] 
SELECT 
FROM
entry: FileEntry => IF EqualUIDs[entry.uid, uid] THEN RETURN;
ENDCASE;
 
[fullFName: fileInfoName, uniqueID: uid, bytes: bytes] ¬ 
PFS.FileInfo[name: name, wantedUniqueID: uid ! 
PFS.Error => 
IF ( error.code = $unknownFile ) 
OR ( error.code = $unknownUniqueID ) 
OR ( error.code = $unknownCreatedTime ) 
OR ( error.code = $unknownServer ) 
THEN {
out.PutF["\n-- *PFS.Error[%g]\n\tasking for %g(%g)\n", [rope[error.explanation]], [rope[PFS.RopeFromPath[fileInfoName]]], IF uid.egmt.gmt = BasicTime.nullGMT THEN [rope["nullGMT"]] ELSE [time[uid.egmt.gmt]] ];
IF isFile THEN out.Flush[];
status ¬ dfOnly;
needsComment ¬ TRUE;
CONTINUE;
}
ELSE REJECT];
 
WITH RedBlackTree.Lookup[table, ropeName] 
SELECT 
FROM
entry: FileEntry => {
IF EqualUIDs[entry.uid, uid] THEN RETURN;
fileCount[differentDates] ¬ fileCount[differentDates].SUCC;
IF verbose 
THEN {
out.PutF["-- $Deleting %g (%g) from table; new date is %g\n", [rope[ropeName]],
[time[entry.uid.egmt.gmt]], [time[uid.egmt.gmt]] ];
IF isFile THEN out.Flush[];
needsComment ¬ TRUE;
};
 
[] ¬ RedBlackTree.Delete[table, ropeName];
};
ENDCASE;
 
new ¬ 
NEW[FileEntryRep ¬ [
fullSrcName: ropeName,
uid: uid,
status: status]];
RedBlackTree.Insert[table, new, ropeName];
};
 
VisitClosure: 
PROC [dfName: 
PATH, uid: 
PFS.UniqueID,
visitor: 
PROC [name: 
PATH, uid: 
PFS.UniqueID],
usingList: 
REF DFUtilities.UsingList ¬ 
NIL] = {
ENABLE 
PFS.Error => 
IF ( error.code = $unknownServer ) 
OR
( error.code = $unknownFile ) 
THEN {
out.PutF["\n-- ****PFS.Error[%g], in dfFile: %g\n", [rope[error.explanation]], [rope[PFS.RopeFromPath[dfName]]] ];
IF isFile THEN out.Flush[];
needsComment ¬ TRUE;
};
 
EachItem: DFUtilities.ProcessItemProc = {
WITH item 
SELECT 
FROM
dir: 
REF DFUtilities.DirectoryItem => 
prefix ¬ IF dir.readOnly THEN NIL ELSE PFS.PathFromRope[dir.path1];
file: 
REF DFUtilities.FileItem => 
IF prefix # 
NIL 
THEN {
name: PATH = PFSNames.ExpandName[PFS.PathFromRope[file.name], prefix];
UpdateDirList[name];
visitor[name, UIDFromGMT[file.date.gmt]];
};
incl: 
REF DFUtilities.IncludeItem => {
thisUID: PFS.UniqueID = UIDFromGMT[incl.date.gmt];
path1: PATH ¬ PFS.PathFromRope[incl.path1];
CheckCount[];
visitor[path1, thisUID];
VisitClosure[path1, thisUID, visitor];
};
ENDCASE => { i: INT ¬ 0; }; -- handy for setting breakpoints - (bj)
 
};
prefix: PATH ¬ NIL;
in: STREAM;
in ¬ 
PFS.StreamOpen[
fileName: dfName,
wantedUniqueID: uid
! 
PFS.Error => 
IF error.code = $unknownFile 
THEN {
out.PutF["-- PFS.Error[%g] - from dfFile %g\n", [rope[error.explanation]],
[rope[PFS.RopeFromPath[dfName]]] ];
IF isFile THEN out.Flush[];
needsComment ¬ TRUE;
in ¬ NIL;
CONTINUE;
}
ELSE REJECT
];
IF in#
NIL 
THEN {
DFUtilities.ParseFromStream[in, EachItem 
-- global variable, hack for now! - (bj)
! UNWIND => in.Close[]];
in.Close[]
};
 
};
 
UpdateDirList: 
PROC[path: 
PATH] = {
thisDIR: PATH ¬ PFSNames.Directory[path];
lastsubDir: ROPE ¬ PFSNames.ComponentRope[
PFSNames.Fetch[thisDIR, PFSNames.ComponentCount[thisDIR]-1]];
IF lastsubDir.Equal["Top", FALSE] THEN RETURN;
IF lastsubDir.Equal["Documentation", FALSE] THEN RETURN;
IF lastsubDir.Equal["Commands", FALSE] THEN RETURN;
IF lastsubDir.Equal["FamousFiles", FALSE] THEN RETURN;
IF lastsubDir.Equal["Derived", FALSE] THEN RETURN;
FOR pL: 
LIST 
OF 
PATH ¬ dirList, pL.rest 
UNTIL pL=
NIL 
DO
IF PFSNames.Compare[pL.first, thisDIR, FALSE] = equal THEN RETURN; 
ENDLOOP;
 
IF verbose 
THEN {
out.PutF1["\n-- &&Adding %g to dirList\n", [rope[PFS.RopeFromPath[thisDIR]]] ];
IF isFile THEN out.Flush[];
needsComment ¬ TRUE;
};
 
dirList ¬ CONS[thisDIR, dirList];
};
 
CheckCount: 
PROC ~ {
IF (count ¬ count + 1) MOD 10 # 0 THEN RETURN;
IF needsComment THEN { out.PutRope["-- "]; needsComment ¬ FALSE };
IF count 
MOD 100 = 0 
THEN {
out.PutF1["(%g) ", [integer[count]] ];
IF isFile THEN out.Flush[];
}
ELSE out.PutChar['.]
 
};
 
 
The mainline of DoIt
count: INT ¬ 0;
needsComment: BOOL ¬ TRUE;
dirList: LIST OF PATH ¬ NIL;
RedBlackTree.DestroyTable[table]; -- clear the table from the last run
Phase1, build up data base.
out.PutF1["\n-- ***** Building table from DF at %g\n", [time[BasicTime.Now[]]] ];
VisitClosure[PFS.PathFromRope[dfNameRope.Concat[bangH]], PFS.nullUniqueID, VisitEntry];
Phase2, scan directory.
ExamineDirectories[dirList, table, out, isFile];
Phase3, dump differences.
out.PutF1["\n-- ***** Examining tree at %g\n", [time[BasicTime.Now[]]] ];
RedBlackTree.EnumerateIncreasing[table, EachEntry];
out.PutF1["\n-- {Done at %g}\n", [time[BasicTime.Now[]]] ];
};
 
ExamineDirectories: 
PROC[dirList: 
LIST 
OF 
PATH, table: RedBlackTree.Table, out: 
STREAM, isFile: 
BOOL] = {
NameInfoProc: 
PFS.InfoProc = {
new: FileEntry;
ropeName: ROPE;
IF fileType = PFS.tDirectory THEN RETURN;
WITH RedBlackTree.Lookup[table, ropeName ¬ 
PFS.RopeFromPath[fullFName]] 
SELECT 
FROM
entry: FileEntry => 
{
IF EqualUIDs[entry.uid, uniqueID] 
THEN
entry.status ¬ both ELSE entry.status ¬ differentDates;
 
RETURN;
};
ENDCASE;
 
new ¬ 
NEW[FileEntryRep ¬ [
fullSrcName: ropeName,
uid: uniqueID,
status: dirOnly]];
RedBlackTree.Insert[table, new, ropeName];
};
 
out.PutF1["\n-- ***** Enumerating directories %g\n", [time[BasicTime.Now[]]] ];
IF isFile THEN out.Flush[];
FOR pL: 
LIST 
OF 
PATH ¬ dirList, pL.rest 
UNTIL pL=
NIL 
DO
toSearch: PATH ¬ PFSNames.Cat[pL.first, bangHPath];
out.PutF1["--\t(%g)\N", [rope[PFS.RopeFromPath[toSearch]]] ];
IF isFile THEN out.Flush[];
PFS.EnumerateForInfo[toSearch, NameInfoProc];
ENDLOOP;
 
};
 
ShowTable: 
PROC [out: 
STREAM, table: RedBlackTree.Table] = {
EachEntry: RedBlackTree.EachNode = {
[data: RedBlackTree.UserData] RETURNS [stop: BOOL ← FALSE]
WITH data 
SELECT 
FROM
entry: FileEntry => ShowEntry[out, entry];
ENDCASE => ERROR;
 
};
RedBlackTree.EnumerateIncreasing[table, EachEntry];
};
 
ShowEntry: 
PROC [out: 
STREAM, entry: FileEntry] = {
IO.PutF[out, "[name: %g, date: %g, state: ",
[rope[entry.fullSrcName]], [time[entry.uid.egmt.gmt]] ];
SELECT entry.status 
FROM
dfOnly => out.PutRope["dfOnly]\n"];
dirOnly => out.PutRope["dirOnly]\n"];
both => out.PutRope["both]\n"];
differentDates => out.PutRope["differentDates]\n"];
ENDCASE;
 
};
 
Compare: RedBlackTree.Compare = {
[k: RedBlackTree.Key, data: RedBlackTree.UserData] RETURNS [Basics.Comparison]
key: ROPE ¬ NIL;
WITH k 
SELECT 
FROM
ent: FileEntry => key ¬ ent.fullSrcName;
rope: ROPE => key ¬ rope;
ENDCASE => ERROR;
 
WITH data 
SELECT 
FROM
ent: FileEntry => RETURN [Rope.Compare[key, ent.fullSrcName, FALSE]];
ENDCASE;
 
ERROR;
};
 
CompareDFwithServer: Commander.CommandProc ~ {
ProcessSwitches: 
PROC [arg: 
ROPE] ~ {
sense: BOOL ¬ TRUE;
FOR index: 
INT 
IN [0..Rope.Length[arg]) 
DO
char: CHAR ¬ Rope.Fetch[arg, index];
SELECT char 
FROM
'- => LOOP;
'~ => {sense ¬ NOT sense; LOOP};
'a, 'A => { outFileName ¬ CommanderOps.NextArgument[cmd]; switches[char] ¬ sense };
'o, 'O => { outFileName ¬ CommanderOps.NextArgument[cmd]; switches[char] ¬ sense };
IN ['a..'z] => switches[char] ¬ sense;
IN ['A..'Z] => switches[char + ('a-'A)] ¬ sense;
ENDCASE;
 
sense ¬ TRUE;
ENDLOOP;
 
};
 
out: STREAM ¬ cmd.out;
dfName: ROPE;
outFileName: ROPE;
isFile: BOOL ¬ FALSE;
table: RedBlackTree.Table ¬ RedBlackTree.Create[getKey: GetKey, compare: Compare];
switches: Switches ¬ 
ALL[
FALSE];
DO
arg: 
ROPE ¬ CommanderOps.NextArgument[cmd ! CommanderOps.Failed => { msg ¬ errorMsg; 
GO 
TO failed } ];
When parsing the command line, be prepared for failure.  The error is reported to the user
ch: CHAR;
Process.CheckForAbort[];
IF arg = NIL THEN EXIT;
ch ¬ Rope.Fetch[arg, 0];
SELECT 
TRUE 
FROM
( ch = '- ) AND ( arg.Length[] = 2 ) => ProcessSwitches[arg]; -- switch
( ch = '{ ) => LOOP; -- ignore
( ch = '} ) => LOOP; -- ignore
( ch = '$ ) => LOOP; -- ignore
ENDCASE => dfName ¬ arg -- translations or other things
ENDLOOP;
 
 
IF outFileName # 
NIL 
THEN {
outPath: PFS.PATH ~ PFS.PathFromRope[outFileName];
outStream: STREAM;
outStream ¬ PFS.StreamOpen[outPath, IF switches['a] THEN $append ELSE $create ! PFS.Error => {
out.PutRope[error.explanation]; CONTINUE} ];
IF (isFile ¬ ( outStream # 
NIL )) 
THEN {
out.PutF1["Messages will be written on %g\n", [rope[outFileName]] ];
out ¬ outStream;
};
 
};
 
DoIt[dfName, table, out, switches['v] , isFile
! 
PFS.Error => {
out.PutF["-- PFS.Error[%g] - quitting.\n\t\t(at %g)\n\n", [rope[error.explanation]], [time[BasicTime.Now[]]] ];
CONTINUE;
};
];
IF outFileName # NIL THEN out.Close[];
EXITS
failed => {result ¬ $Failure};
 
};