<> <> <> <> DIRECTORY Basics USING [bytesPerWord], BasicTime USING [GMT, Now, nullGMT, ToPupTime], BcdDefs USING [BCD, NullVersion, VersionStamp], CedarProcess, Commander USING [CommandProc, Register], CommandTool, DefaultRemoteNames USING [Get], DFUtilities USING [Date, RemoveVersionNumber], FS USING [Error, FileInfo, minimumStreamBufferParms, Open, OpenFile, StreamFromOpenFile, StreamOpen], GenerateDFClosure USING [ActionProc, ClosureInfo, GenerateClosureToProc], IO USING [Close, GetIndex, PutChar, PutF, PutFR, PutRope, SetIndex, SetLength, STREAM, UnsafeGetBlock], MessagesOut USING [PutRopes], PriorityQueue USING [Insert, Predict, Ref, Remove, Size, SortPred], Process USING [CheckForAbort, Pause, SecondsToTicks], Rope USING [Concat, Equal, Fetch, Find, Flatten, FromProc, Index, Length, Match, ROPE, Run, Size, SkipTo, Substr], VersionMap, ViewerIO USING [CreateViewerStreams]; VersionMapFromDFImpl: CEDAR MONITOR LOCKS myData USING myData: MyData IMPORTS BasicTime, CedarProcess, Commander, CommandTool, DefaultRemoteNames, DFUtilities, FS, GenerateDFClosure, IO, MessagesOut, PriorityQueue, Process, Rope, VersionMap, ViewerIO SHARES IO, VersionMap = BEGIN <> BCD: TYPE = BcdDefs.BCD; bytesPerWord: NAT = Basics.bytesPerWord; Date: TYPE = DFUtilities.Date; LORA: TYPE = LIST OF REF ANY; PQ: TYPE = PriorityQueue.Ref; GMT: TYPE = BasicTime.GMT; Map: TYPE = VersionMap.Map; MapList: TYPE = VersionMap.MapList; MapEntry: TYPE = VersionMap.MapEntry; MapRep: TYPE = VersionMap.MapRep; MyStamp: TYPE = VersionMap.MyStamp; NullStamp: MyStamp = LOOPHOLE[NullVersion]; MyStampAsHex: TYPE = PACKED ARRAY [0..12) OF [0..16); ROPE: TYPE = Rope.ROPE; RopeList: TYPE = LIST OF ROPE; STREAM: TYPE = IO.STREAM; VersionStamp: TYPE = BcdDefs.VersionStamp; NullVersion: VersionStamp = BcdDefs.NullVersion; FileEntry: TYPE = REF FileEntryRep; FileEntryRep: TYPE = RECORD [ name: ROPE, date: Date, version: VersionStamp, from: ROPE]; NameSeq: TYPE = REF NameSeqRep; NameSeqRep: TYPE = RECORD [ SEQUENCE len: NAT OF NameEntry]; NameEntry: TYPE = RECORD [hasPrefix: BOOL, name: ROPE]; MyData: TYPE = REF MyDataRep; MyDataRep: TYPE = MONITORED RECORD [ sourceQueue: PQ _ NIL, objectQueue: PQ _ NIL, objectFiles,sourceFiles: INT _ 0, oldObjectMap: MapList _ NIL, oldSourceMap: MapList _ NIL, prefixList: RopeList _ NIL, warningMatch: ROPE _ NIL, errs: STREAM _ NIL, noIFSFile: BOOL _ FALSE, <> <> <> which: {source, object, both} _ both, checkRemote: BOOL _ TRUE, <> abortRequested: BOOL _ FALSE ]; <> pauseForServerGlitch: [0..3600] _ 90; -- seconds to pause if server busy triesForServerGlitch: NAT _ 12; -- times to retry if server busy forkers: NAT _ 2; -- # of processes to fork <> <<>> EachFile: GenerateDFClosure.ActionProc = { <<[data: REF, kind: {file, notFound, syntaxError}, name: ROPE, date: Date, from: ROPE]>> myData: MyData _ NARROW[data]; CheckAbort[myData]; SELECT kind FROM file => { needsVersion: BOOL _ FALSE; isBcd: BOOL _ FALSE; entry: FileEntry _ NEW[FileEntryRep _ [ name: name, date: date, version: NullVersion, from: from]]; triesLeft: NAT _ triesForServerGlitch; IF Rope.Match[myData.warningMatch, name, FALSE] THEN MessagesOut.PutRopes[myData.errs, "Warning match\n for ", name, "\n from ", from]; IF Rope.Match["*!*", name] THEN isBcd _ Rope.Match["*.symbols!*", name, FALSE] OR Rope.Match["*.bcd!*", name, FALSE] ELSE isBcd _ Rope.Match["*.symbols", name, FALSE] OR Rope.Match["*.bcd", name, FALSE]; IF NOT Rope.Match["[]*", name] THEN { <> pos: INT _ Rope.Find[name, ">"]; prefix: ROPE _ Rope.Flatten[name, 0, pos+1]; lag: RopeList _ NIL; FOR each: RopeList _ myData.prefixList, each.rest WHILE each # NIL DO IF Rope.Equal[prefix, each.first, FALSE] THEN GO TO found; lag _ each; ENDLOOP; IF lag = NIL THEN myData.prefixList _ LIST[prefix] ELSE lag.rest _ LIST[prefix]; EXITS found => {}; }; DO ENABLE FS.Error => { SELECT error.code FROM $serverInaccessible => { MessagesOut.PutRopes[myData.errs, "Server glitch: ", name]; Process.Pause[Process.SecondsToTicks[pauseForServerGlitch]]; IF (triesLeft _ triesLeft - 1) # 0 THEN LOOP; }; ENDCASE; MessagesOut.PutRopes[ myData.errs, "File not found: ", name, IO.PutFR["\n from: %g\n reason: %g\n", [rope[from]], [rope[error.explanation]]]]; IF error.group # bug THEN EXIT ELSE REJECT; }; IF isBcd THEN SELECT myData.which FROM object,both => FillInObjectVersion[myData, entry, myData.checkRemote]; ENDCASE ELSE SELECT myData.which FROM source,both => FillInSourceVersion[myData, entry, myData.checkRemote]; ENDCASE; EXIT; ENDLOOP; }; ENDCASE; }; FillInSourceVersion: PROC [myData: MyData, entry: FileEntry, checkRemote: BOOL _ TRUE] = { created: BasicTime.GMT _ entry.date.gmt; initName: ROPE _ entry.name; full: ROPE _ initName; attached: ROPE _ NIL; version: VersionStamp; useFileSystem: BOOL _ FALSE; IF entry.date.format = explicit AND created # BasicTime.nullGMT THEN { <> version _ [0, 0, BasicTime.ToPupTime[created]]; IF checkRemote THEN { <> IF myData.oldSourceMap = NIL THEN useFileSystem _ TRUE ELSE { <> fromMap: ROPE _ VersionMap.VersionToName[myData.oldSourceMap, version].name; len: INT _ fromMap.Length[]; bang: INT _ ScanName[fromMap].bang; SELECT Rope.Run[fromMap, 0, full, 0, FALSE] FROM 0, < bang => useFileSystem _ TRUE; ENDCASE => full _ fromMap; }; }; } ELSE { <> useFileSystem _ TRUE; created _ BasicTime.nullGMT; checkRemote _ TRUE; }; <<>> <> IF useFileSystem THEN { IF created = BasicTime.nullGMT THEN full _ DFUtilities.RemoveVersionNumber[full]; [created: created, fullFName: full, attachedTo: attached] _ FS.FileInfo[ name: full, wantedCreatedTime: created, remoteCheck: checkRemote]; version _ [0, 0, BasicTime.ToPupTime[created]]; }; <<>> <> entry.date _ [format: explicit, gmt: created]; entry.version _ version; IF attached # NIL THEN entry.name _ attached ELSE entry.name _ full; IF Rope.Length[entry.name] = 0 THEN { <> MessagesOut.PutRopes[ myData.errs, "Warning, failed to get source version for ", initName]; } ELSE { <> PriorityQueue.Insert[myData.sourceQueue, entry]; myData.sourceFiles _ myData.sourceFiles + 1; }; }; FillInObjectVersion: PROC [myData: MyData, entry: FileEntry, checkRemote: BOOL _ TRUE] = { <> outerCreated: BasicTime.GMT _ entry.date.gmt; full: ROPE _ entry.name; attached: ROPE _ NIL; valid: BOOL _ FALSE; -- indicates validity of version stamp SELECT TRUE FROM Rope.SkipTo[full, 0, "!"] = Rope.Length[full] => { <> valid _ FALSE; }; entry.date.format # explicit OR outerCreated = BasicTime.nullGMT => { <> valid _ FALSE; }; myData.oldObjectMap # NIL => { <> shortName: ROPE _ VersionMap.ShortName[full]; rangeList: VersionMap.RangeList _ VersionMap.ShortNameToRanges[myData.oldObjectMap, shortName]; IF rangeList # NIL THEN { FOR each: VersionMap.RangeList _ rangeList, each.rest WHILE each # NIL DO <> short: VersionMap.ShortNameSeq _ each.first.map.shortNameSeq; FOR i: CARDINAL IN [each.first.first..each.first.first+each.first.len) DO IF VersionMap.FetchCreated[each.first.map, short[i]] = outerCreated THEN { <> entry.version _ VersionMap.FetchStamp[each.first.map, short[i]]; GO TO enter; }; ENDLOOP; ENDLOOP; }; valid _ FALSE; }; ENDCASE => { valid _ FALSE; }; outerCreated _ BasicTime.nullGMT; full _ DFUtilities.RemoveVersionNumber[full]; <> [created: outerCreated, fullFName: full, attachedTo: attached] _ FS.FileInfo[ name: full, wantedCreatedTime: outerCreated, remoteCheck: checkRemote]; entry.date _ [format: explicit, gmt: outerCreated]; IF attached # NIL THEN full _ attached; entry.name _ full; [valid, entry.version] _ FindShortObjectName[myData, full]; IF NOT valid THEN { <> <> <<[fullGName: ROPE, created: BasicTime.GMT, bytes: INT, keep: CARDINAL]>> <> < 0 THEN {>> <> <> <> <<}; >> <> <<};>> <<>> <> UseFS: PROC [name: ROPE] = { file: FS.OpenFile _ FS.Open[ name: full, lock: read, wantedCreatedTime: outerCreated, remoteCheck: FALSE]; stream: STREAM _ FS.StreamFromOpenFile[ openFile: file, streamBufferParms: FS.minimumStreamBufferParms]; TRUSTED { ENABLE UNWIND => IO.Close[stream]; versionOffset: NAT = bytesPerWord*SIZE[CARDINAL]; versionBytes: NAT = bytesPerWord*SIZE[VersionStamp]; IO.SetIndex[stream, versionOffset]; [] _ IO.UnsafeGetBlock[ stream, [LOOPHOLE[@entry.version], 0, versionBytes]]; }; IO.Close[stream]; }; UseFS[full]; <> <> <> <> <> <> <> <> <> <> <> <<{ENABLE UNWIND => ReleaseRemoteHandle[myData];>> <<[valid, entry.version] _ VersionStampFromRemoteHandle[>> <> <<};>> <> <> <> <> <> <<};>> <<}>> <> <> <> <> <<};>> <<};>> }; GO TO enter; EXITS enter => { PriorityQueue.Insert[myData.objectQueue, entry]; myData.objectFiles _ myData.objectFiles + 1; }; }; EntrySortPred: PriorityQueue.SortPred = TRUSTED { <<[x: Item, y: Item, data: REF _ NIL] RETURNS [BOOL]>> xx: MyStamp = LOOPHOLE[LOOPHOLE[x, FileEntry].version]; yy: MyStamp = LOOPHOLE[LOOPHOLE[y, FileEntry].version]; IF xx.num # yy.num THEN RETURN [xx.num < yy.num]; IF xx.hi # yy.hi THEN RETURN [xx.hi < yy.hi]; RETURN [xx.lo < yy.lo]; }; VersionSortPred: PROC [xx,yy: MyStamp] RETURNS [BOOL] = TRUSTED { IF xx.num # yy.num THEN RETURN [xx.num < yy.num]; IF xx.hi # yy.hi THEN RETURN [xx.hi < yy.hi]; RETURN [xx.lo < yy.lo]; }; VersionMapFromPQ: PROC [pq: PQ, prefix: ROPE, myData: MyData] RETURNS [map: Map] = { currentName: ROPE _ prefix _ Rope.Flatten[prefix]; prefixLen: NAT = Rope.Size[prefix]; pos: INT _ prefixLen+1; entries: NAT = PriorityQueue.Size[pq]; seq: NameSeq _ NEW[NameSeqRep[entries]]; entryIndex: NAT _ 0; subPos: NAT _ 0; subLim: NAT _ prefixLen; lastPos: INT _ 0; lag: FileEntry _ NIL; getChar: PROC [] RETURNS [c: CHAR] = { IF subPos < subLim THEN { c _ currentName.Fetch[subPos]; subPos _ subPos + 1; RETURN; }; c _ '\n; IF entryIndex < entries THEN { nameEntry: NameEntry _ seq[entryIndex]; currentName _ nameEntry.name; subLim _ Rope.Length[currentName]; IF nameEntry.hasPrefix THEN subPos _ prefixLen ELSE subPos _ 0; entryIndex _ entryIndex + 1; }; }; Process.CheckForAbort[]; map _ NEW[MapRep[entries]]; FOR i: NAT IN [0..entries) DO entry: FileEntry = NARROW[PriorityQueue.Remove[pq]]; name: ROPE = entry.name; hasPrefix: BOOL = Rope.Run[name, 0, prefix, 0, FALSE] = prefixLen; seq[i] _ [hasPrefix, name]; map[i] _ [stamp: LOOPHOLE[entry.version], created: entry.date.gmt, index: pos]; pos _ pos + Rope.Length[name] + 1; IF hasPrefix THEN pos _ pos - prefixLen; IF lag # NIL AND lag.version = entry.version THEN { <> MessagesOut.PutRopes[myData.errs, "Warning, duplicate versions for", IO.PutFR["\n %g\n from: %g", [rope[lag.name]], [rope[lag.from]]], IO.PutFR["\n %g\n from: %g", [rope[entry.name]], [rope[entry.from]]] ]; }; lag _ entry; ENDLOOP; map.names _ Rope.FromProc[pos, getChar, 4040]; VersionMap.FillInShortNames[map]; }; WriteMapToFile: PROC [map: Map, name: ROPE] = { prefix: ROPE _ NIL; st: IO.STREAM _ FS.StreamOpen[name, create]; count: NAT _ map.len; names: ROPE _ map.names; firstCR: INT _ names.Index[0, "\n"]; Process.CheckForAbort[]; prefix _ names.Flatten[0, firstCR]; IO.PutRope[st, prefix]; IO.PutRope[st, "\n"]; FOR i: NAT IN [0..map.len) DO <> entry: MapEntry _ map[i]; index: INT _ entry.index; stamp: MyStamp _ entry.stamp; PutStampAsHex[st, stamp]; IO.PutRope[st, " "]; DO c: CHAR _ names.Fetch[index]; index _ index + 1; IO.PutChar[st, c]; IF c = '\n THEN EXIT; ENDLOOP; ENDLOOP; IO.PutRope[st, "\n"]; IO.SetLength[st, IO.GetIndex[st]]; -- force goddamm truncation already!!! IO.Close[st]; }; IndexToShortName: PROC [map: Map, index: INT] RETURNS [ROPE] = { <> names: ROPE _ map.names; end: INT _ names.SkipTo[index, "!\n"]; pos: INT _ end; WHILE pos > index DO SELECT names.Fetch[pos] FROM '/, '], '> => EXIT; ENDCASE; pos _ pos - 1; ENDLOOP; RETURN [names.Substr[pos, end-pos]]; }; PutStampAsHex: PROC [st: IO.STREAM, stamp: MyStamp] = { hex: MyStampAsHex _ LOOPHOLE[stamp]; FOR index: CARDINAL IN [0..12) DO x: CARDINAL [0..15] _ hex[index]; c: CHAR _ IF x IN [0..9] THEN '0 + x ELSE 'A + (x-10); st.PutChar[c]; ENDLOOP; }; ScanName: PROC [name: ROPE] RETURNS [pos,bang,dot: INT] = { len: INT = Rope.Length[name]; pos _ bang _ dot _ len; WHILE pos > 0 DO posM: INT = pos-1; SELECT Rope.Fetch[name, posM] FROM '! => bang _ dot _ posM; '. => dot _ posM; '>, '/, '] => RETURN; ENDCASE; pos _ posM; ENDLOOP; }; CheckAbort: ENTRY PROC [myData: MyData] = { <<... puts out the given ropes to the MessageWindow. This is an entry procedure to keep the message in the MessageWindow consistent.>> ENABLE UNWIND => myData.abortRequested _ TRUE; IF myData.abortRequested THEN RETURN WITH ERROR ABORTED; Process.CheckForAbort[]; }; CheckAbortInternal: INTERNAL PROC [myData: MyData] = { <<... puts out the given ropes to the MessageWindow. This is an entry procedure to keep the message in the MessageWindow consistent.>> ENABLE UNWIND => myData.abortRequested _ TRUE; IF myData.abortRequested THEN RETURN WITH ERROR ABORTED; Process.CheckForAbort[]; }; <> <> < myData.abortRequested _ TRUE;>> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<};>> <<>> <> <> <> <<[userName,password] _ UserCredentials.Get[];>> <> < {>> <> <> < GO TO badNews;>> < {problem _ other; CONTINUE}>> <<];>> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<};>> < {valid _ FALSE};>> <<};>> <<>> <<>> <> ShortName: PROC [full: ROPE] RETURNS [ROPE] = { pos: INT _ Rope.Length[full]; lim: INT _ pos; WHILE pos > 0 DO c: CHAR = Rope.Fetch[full, pos_pos-1]; SELECT c FROM '! => lim _ pos; '>, '] => {pos _ pos + 1; EXIT}; ENDCASE; ENDLOOP; RETURN [Rope.Flatten[full, pos, lim-pos]]; }; FindShortObjectName: PROC [myData: MyData, full: ROPE] RETURNS [valid: BOOL _ FALSE, stamp: VersionStamp] = { IF myData.oldObjectMap # NIL THEN { short: ROPE = ShortName[full]; rangeList: VersionMap.RangeList _ VersionMap.ShortNameToRanges[myData.oldObjectMap, short]; WHILE rangeList # NIL DO range: VersionMap.Range _ rangeList.first; rangeList _ rangeList.rest; WHILE range.len # 0 DO name: ROPE; stamp: VersionStamp; created: BasicTime.GMT; [name, stamp, created, range] _ VersionMap.RangeToEntry[range]; IF Rope.Equal[full, name, FALSE] THEN RETURN [TRUE, stamp]; ENDLOOP; ENDLOOP; <> valid _ FALSE; }; }; <
> Quit: ERROR = CODE; GenMap: Commander.CommandProc = { <<[cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL]>> dump: BOOL _ TRUE; dumpDebug: BOOL _ TRUE; namesOnly: BOOL _ FALSE; remoteOpened: BOOL _ FALSE; pos,bang,dot: INT _ 0; inName,sourceName,objectName,outPrefix: ROPE _ NIL; command: ROPE _ cmd.command; results: GenerateDFClosure.ClosureInfo; myData: MyData; sourceMap: Map _ NIL; objectMap: Map _ NIL; checkRemote: BOOL _ TRUE; inLog,outLog: STREAM _ NIL; genMaps: BOOL _ FALSE; -- gen regular version maps if TRUE trustOld: BOOL _ FALSE; -- trust old version maps if TRUE prefix: ROPE _ NIL; -- the first prefix seen oldPath: ROPE _ DefaultRemoteNames.Get[].previous; -- the previous release switches: PACKED ARRAY CHAR['a..'z] OF BOOL _ ALL[FALSE]; ProcessArgument: PROC = { [pos,bang,dot] _ ScanName[inName]; IF bang = Rope.Size[inName] -- no version stuff AND (bang-dot # 3 OR Rope.Run[inName, dot, ".df", 0, FALSE] # 3) -- no DF extension THEN inName _ Rope.Concat[inName, ".df"]; inName _ FS.FileInfo[inName ! FS.Error => IF error.group # bug THEN { IO.PutF[ cmd.out, "\nCan't open %g\n %g\n", [rope[inName]], [rope[error.explanation]]]; ERROR Quit; }; ].fullFName; [inLog,outLog] _ ViewerIO.CreateViewerStreams[ "VersionMapBuilder.log", NIL, "VersionMapBuilder.log", FALSE]; [pos,bang,dot] _ ScanName[inName]; outPrefix _ Rope.Flatten[inName, pos, dot-pos]; IO.PutF[outLog, "\nVersion Map Builder started at %g", [time[BasicTime.Now[]]]]; IO.PutF[outLog, "\n\nInput from: %g\n", [rope[inName]]]; sourceName _ Rope.Concat[outPrefix, ".sourceMap"]; objectName _ Rope.Concat[outPrefix, ".symbolsMap"]; <> { <> < { FinalizeRemoteHandle[myData] };>> SELECT cmd.procData.clientData FROM $GenMap => { genMaps _ TRUE; }; $MergeMap => { trustOld _ genMaps _ TRUE; }; $GenCedarMap => { genMaps _ TRUE; outPrefix _ "Cedar"; }; $MergeCedarMap => { trustOld _ genMaps _ TRUE; outPrefix _ "Cedar"; }; ENDCASE; myData _ NEW[MyDataRep _ [ sourceQueue: PriorityQueue.Predict[2000, EntrySortPred], objectQueue: PriorityQueue.Predict[1500, EntrySortPred], errs: outLog, checkRemote: checkRemote ]]; SELECT cmd.procData.clientData FROM $GenCedarMap, $MergeCedarMap => myData.warningMatch _ Rope.Concat[oldPath, "*.df*"]; ENDCASE; IF genMaps THEN { sourceName _ Rope.Concat[outPrefix, "Source.VersionMap"]; objectName _ Rope.Concat[outPrefix, "Symbols.VersionMap"]; }; IF trustOld THEN { <> IO.PutRope[outLog, "\nReading old maps."]; myData.oldSourceMap _ LIST[ VersionMap.RestoreMapFromFile[sourceName ! FS.Error => { IO.PutRope[outLog, "\nWarning, can't read "]; IO.PutRope[outLog, sourceName]; CONTINUE}] ]; myData.oldObjectMap _ LIST[ VersionMap.RestoreMapFromFile[objectName ! FS.Error => { IO.PutRope[outLog, "\nWarning, can't read "]; IO.PutRope[outLog, objectName]; GO TO exit}] ]; EXITS exit => {}; }; CheckAbort[myData]; myData.which _ both; IO.PutRope[outLog, "\nGenerating DF closure."]; results _ GenerateDFClosure.GenerateClosureToProc[ inName, outLog, EachFile, myData, [toFork: forkers, followImports: NOT switches['s]]]; }; { <> IO.PutF[outLog, "\n\n%g DF files, %g source files, %g object files.\n", [integer[results.dfFiles]], [integer[myData.sourceFiles]], [integer[myData.objectFiles]]]; IO.PutRope[outLog, "\nPrefixes seen:"]; FOR each: RopeList _ myData.prefixList, each.rest WHILE each # NIL DO IF prefix = NIL THEN prefix _ each.first; IO.PutF[outLog, "\n %g", [rope[each.first]]]; ENDLOOP; IO.PutRope[outLog, "\n\nMaking short name index for source map."]; sourceMap _ VersionMapFromPQ[myData.sourceQueue, prefix, myData]; CheckAbort[myData]; IO.PutRope[outLog, "\n\nWriting "]; IO.PutRope[outLog, sourceName]; IO.PutRope[outLog, "\n"]; IF genMaps THEN VersionMap.SaveMapToFile[sourceMap, sourceName] ELSE WriteMapToFile[sourceMap, sourceName]; IO.PutRope[outLog, "\nMaking short name index for object map."]; objectMap _ VersionMapFromPQ[myData.objectQueue, prefix, myData]; CheckAbort[myData]; IO.PutRope[outLog, "\n\nWriting "]; IO.PutRope[outLog, objectName]; IO.PutRope[outLog, "\n"]; IF genMaps THEN VersionMap.SaveMapToFile[objectMap, objectName] ELSE WriteMapToFile[objectMap, objectName]; IO.PutF[outLog, "\n\nVersion maps built at %g\n\n", [time[BasicTime.Now[]]]]; IO.Close[inLog]; IO.Close[outLog]; }; }; 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}; IN ['a..'z] => switches[char] _ sense; IN ['A..'Z] => switches[char + ('a-'A)] _ sense; ENDCASE; sense _ TRUE; ENDLOOP; }; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd: cmd, starExpand: FALSE ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; <> FOR i: NAT IN [1..argv.argc) DO <> arg: ROPE = argv[i]; IF Rope.Length[arg] = 0 THEN LOOP; <> IF Rope.Fetch[arg, 0] = '- THEN { <> ProcessSwitches[arg]; LOOP; }; inName _ arg; CedarProcess.DoWithPriority[background, ProcessArgument ! Quit, ABORTED => GO TO quit]; ENDLOOP; IF inName = NIL THEN { inName _ "CurrentCedar.df"; CedarProcess.DoWithPriority[background, ProcessArgument ! Quit, ABORTED => GO TO quit]; }; EXITS quit => {}; failed => result _ $Failed; }; Commander.Register[ "GenMap", GenMap, "Generate version maps", $GenMap]; <> Commander.Register[ "MergeMap", GenMap, "Merge version maps", $MergeMap]; <> Commander.Register[ "GenCedarMap", GenMap, "Generate Cedar version maps", $GenCedarMap]; <> Commander.Register[ "MergeCedarMap", GenMap, "Merge Cedar version maps", $MergeCedarMap]; <> END.