MODULE; IMPORT DirEntry, DirEntryList, DirEntryListSort, ErrMsg, FileAttr, FS, GlobTree, OSError, Pathname, RefSeq, SupMisc, Text; TYPE UniIterator = Iterator OBJECT path: TEXT; (* Current directory path. *) follow: GlobTree.T; attic: GlobTree.T; rootLength: CARDINAL; (* Length of root path. *) relPathStart: CARDINAL; (* Just after the slash following the root. *) dirStack: RefSeq.T; (* Stack of "Dir". *) needToReadDir: BOOLEAN; (* True if we need to read a directory. *) dirAttr: FileAttr.T; (* Attributes of directory we need to read. *) pruning := FALSE; OVERRIDES next := SingleNext; prune := SinglePrune; close := SingleClose; END; Dir = REF RECORD mainEntries: DirEntryList.T; (* Entries in main directory. *) atticEntries: DirEntryList.T; (* Entries in Attic subdirectory. *) attr: FileAttr.T; (* Attributes of the directory. *) END; PROCEDURE CVTree Iterate (root: Pathname.T; follow: GlobTree.T := NIL; attic: GlobTree.T := NIL): Iterator RAISES {Error} = VAR rootLength := Text.Length(root); relPathStart: CARDINAL; rootAttr: FileAttr.T; BEGIN IF follow = NIL THEN follow := GlobTree.True END; IF attic = NIL THEN attic := GlobTree.True END; IF rootLength = 0 OR Text.GetChar(root, rootLength-1) = SupMisc.SlashChar THEN relPathStart := rootLength; ELSE relPathStart := rootLength + 1; END; (* Get the attributes of the root directory. *) TRY rootAttr := FileAttr.FromPathname(root, follow := follow.test("")); EXCEPT OSError.E(l) => RAISE Error("Cannot get attributes for \"" & root & "\": " & ErrMsg.StrError(l)); END; RETURN NEW(UniIterator, path := root, follow := follow, attic := attic, dirStack := NEW(RefSeq.T).init(30), rootLength := rootLength, relPathStart := relPathStart, needToReadDir := TRUE, dirAttr := rootAttr); END Iterate; PROCEDURESingleNext (iter: UniIterator; VAR type: FileType; VAR name: Pathname.T; VAR attr: FileAttr.T): BOOLEAN RAISES {Error} = VAR mainEntries, atticEntries: DirEntryList.T := NIL; errMsg: TEXT := NIL; dir: Dir; entry: DirEntry.T; BEGIN IF iter.needToReadDir THEN <* ASSERT iter.dirAttr # NIL *> IF iter.pruning THEN iter.dirStack.addhi(NEW(Dir, mainEntries := NIL, atticEntries := NIL, attr := iter.dirAttr)); ELSE TRY mainEntries := DirEntryListSort.SortD(ReadDir(iter, attic := FALSE)); EXCEPT Error(msg) => errMsg := msg; END; IF iter.attic.test(iter.path) THEN (* Do Attic processing. *) VAR cur, prev: DirEntryList.T; c: [-1..1]; BEGIN cur := mainEntries; prev := NIL; WHILE cur # NIL DO c := Text.Compare(cur.head.name, SupMisc.CVSAttic); IF c >= 0 THEN EXIT END; prev := cur; cur := cur.tail; END; IF cur # NIL AND c = 0 AND cur.head.attr.fileType = FileAttr.FileType.Directory THEN (* There is an attic directory. *) TRY atticEntries := DirEntryListSort.SortD(ReadDir(iter, attic := TRUE)); EXCEPT Error(msg) => errMsg := msg; END; IF prev = NIL THEN mainEntries := cur.tail; ELSE prev.tail := cur.tail; END; END; END; END; iter.dirStack.addhi(NEW(Dir, mainEntries := mainEntries, atticEntries := atticEntries, attr := iter.dirAttr)); END; iter.needToReadDir := FALSE; iter.dirAttr := NIL; END; IF errMsg # NIL THEN (* Either the directory or the Attic was unreadable. Raise an exception to report it. Subsequent calls will continue on as if the unreadable directory were empty. *) RAISE Error(errMsg); END; dir := NARROW(iter.dirStack.gethi(), Dir); IF iter.pruning THEN dir.mainEntries := NIL; dir.atticEntries := NIL; iter.pruning := FALSE; END; REPEAT IF dir.mainEntries # NIL AND dir.atticEntries # NIL THEN CASE Text.Compare(dir.mainEntries.head.name, dir.atticEntries.head.name) OF | -1 => entry := dir.mainEntries.head; type := FileType.File; dir.mainEntries := dir.mainEntries.tail; | 0 => (* The file exists in the Attic and in the main directory. This should not happen in a valid CVS repository. We simply ignore the Attic copy of the file. *) entry := dir.mainEntries.head; type := FileType.File; dir.mainEntries := dir.mainEntries.tail; dir.atticEntries := dir.atticEntries.tail; | 1 => entry := dir.atticEntries.head; type := FileType.AtticFile; dir.atticEntries := dir.atticEntries.tail; END; ELSIF dir.mainEntries # NIL THEN entry := dir.mainEntries.head; type := FileType.File; dir.mainEntries := dir.mainEntries.tail; ELSIF dir.atticEntries # NIL THEN entry := dir.atticEntries.head; type := FileType.AtticFile; dir.atticEntries := dir.atticEntries.tail; ELSE entry := NIL; END; UNTIL entry = NIL OR entry.attr.fileType # FileAttr.FileType.Directory OR type # FileType.AtticFile; (* In the above loop, we skip over any subdirectories of the Attic. There should never be any such subdirectories. *) IF entry # NIL THEN (* We have an entry at the current level. *) WITH path = SupMisc.CatPath(iter.path, entry.name) DO name := Text.Sub(path, iter.relPathStart); attr := entry.attr; IF attr.fileType = FileAttr.FileType.Directory THEN type := FileType.DirDown; iter.path := path; iter.needToReadDir := TRUE; iter.dirAttr := attr; END; RETURN TRUE; END; END; IF Text.Length(iter.path) > iter.rootLength THEN (* Pop up a level. *) type := FileType.DirUp; name := Text.Sub(iter.path, iter.relPathStart); attr := NARROW(iter.dirStack.remhi(), Dir).attr; iter.path := SupMisc.PathPrefix(iter.path); RETURN TRUE; END; RETURN FALSE; END SingleNext; PROCEDURESinglePrune (iter: UniIterator) = BEGIN iter.pruning := TRUE; END SinglePrune; PROCEDURESingleClose (<*UNUSED*> iter: UniIterator) = BEGIN END SingleClose; PROCEDUREReadDir (iter: UniIterator; attic: BOOLEAN): DirEntryList.T RAISES {Error} = VAR entries: DirEntryList.T := NIL; e := NEW(DirEntry.T); fsIter: FS.Iterator; path: Pathname.T; BEGIN IF attic THEN path := SupMisc.CatPath(iter.path, SupMisc.CVSAttic); ELSE path := iter.path; END; TRY fsIter := FS.Iterate(path); EXCEPT OSError.E(l) => RAISE Error("Cannot read directory \"" & path & "\": " & ErrMsg.StrError(l)); END; TRY WHILE fsIter.next(e.name) DO WITH filePath = SupMisc.CatPath(path, e.name), relPath = Text.Sub(filePath, iter.relPathStart), follow = iter.follow.test(relPath) DO TRY IF follow THEN TRY e.attr := FileAttr.FromPathname(filePath, follow := TRUE); EXCEPT OSError.E => (* This may be a dangling or recursive symbolic link. Try again with "follow" set to "FALSE". This will give us the attributes of the link itself, just as "ls" does. *) e.attr := FileAttr.FromPathname(filePath, follow := FALSE); END; ELSE e.attr := FileAttr.FromPathname(filePath, follow := FALSE); END; entries := DirEntryList.Cons(e, entries); e := NEW(DirEntry.T); EXCEPT OSError.E => (* If we can't stat it, then pretend it doesn't exist. *) END; END; END; FINALLY fsIter.close(); END; RETURN entries; END ReadDir; BEGIN END CVTree.