<<>> <> <> <> <> <> <> <<>> DIRECTORY Commander, CommanderOps, CommanderRegistry, Convert, IO, IOClasses, List, PFS, PFSNames, Process, ProcessProps, RefTab, Rope, RopeList, SymTab, SystemNames; CommanderFileCommandsImpl: CEDAR MONITOR IMPORTS Commander, CommanderOps, CommanderRegistry, Convert, IO, IOClasses, List, PFS, PFSNames, Process, ProcessProps, RefTab, Rope, RopeList, SymTab, SystemNames ~ BEGIN PATH: TYPE ~ PFS.PATH; ROPE: TYPE ~ Rope.ROPE; searchRules: LIST OF REF ¬ LIST[Rope.Flatten["/Cedar/Commands/"]]; ChangeSearchRules: ENTRY PROC [from, to: LIST OF REF] RETURNS [ok: BOOL] ~ { <> IF searchRules # from THEN RETURN [FALSE]; searchRules ¬ to; RETURN [TRUE] }; searchRuleCache: RefTab.Ref ~ RefTab.Create[]; RemPath: PROC [a: PATH, b: LIST OF PATH] RETURNS [LIST OF PATH] ~ { SELECT TRUE FROM (b = NIL) => RETURN [b]; PFSNames.Equal[a, b.first] => RETURN [b.rest]; ENDCASE => { rest: LIST OF PATH ~ RemPath[a, b.rest]; RETURN [IF b.rest = rest THEN b ELSE CONS[b.first, rest]] }; }; MergePaths: PROC [a, b: LIST OF PATH] RETURNS [LIST OF PATH] ~ { IF a = NIL THEN RETURN [b] ELSE { rest: LIST OF PATH ~ RemPath[a.first, MergePaths[a.rest, b]]; RETURN [IF rest = a.rest THEN a ELSE CONS[a.first, rest]] }; }; ComputeSearchRules: PROC [rules: REF] RETURNS [paths: LIST OF PATH ¬ NIL] ~ { <> found: BOOL; val: REF; IF rules = NIL THEN RETURN; [found: found, val: val] ¬ RefTab.Fetch[searchRuleCache, rules]; IF found THEN RETURN [paths: NARROW[val]]; IF RefTab.GetSize[searchRuleCache] > 50 THEN RefTab.Erase[searchRuleCache]; WITH rules SELECT FROM rope: ROPE => { paths ¬ LIST[PFS.AbsoluteName[PFS.PathFromRope[rope]]]; }; < {>> <> <<};>> list: LIST OF REF => { paths ¬ MergePaths[ComputeSearchRules[list.first], ComputeSearchRules[list.rest]]; }; ENDCASE => NULL; }; GetSearchRules: PROC [cmd: Commander.Handle] RETURNS [LIST OF PATH] ~ { <> RETURN [ComputeSearchRules[searchRules]]; }; PrintSearchRules: Commander.CommandProc = { Inner: PROC [rules: REF] = { WITH rules SELECT FROM rope: ROPE => {IO.PutRope[out, rope]}; list: LIST OF REF => { <> FOR each: LIST OF REF ¬ list, each.rest WHILE each # NIL DO Inner[each.first]; IO.PutChar[out, ' ]; ENDLOOP; <> }; ENDCASE => IF rules = NIL THEN IO.PutRope[out, "( )"] ELSE IO.PutRope[out, "??"]; Process.CheckForAbort[]; }; out: IO.STREAM = cmd.out; <> Inner[searchRules]; IO.PutRope[out, "\n"]; }; SetSearchRules: Commander.CommandProc = { argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd]; data: REF ¬ cmd.procData.clientData; DO oldRules: LIST OF REF ¬ searchRules; newRules: LIST OF REF ¬ NIL; <> < oldRules _ list;>> <> SELECT data FROM $Pop => IF oldRules # NIL THEN { newRules ¬ oldRules.rest; IF newRules # NIL AND newRules.rest = NIL THEN WITH newRules.first SELECT FROM list: LIST OF REF => newRules ¬ list; <> ENDCASE; }; ENDCASE => { tail: LIST OF REF ¬ NIL; FOR i: NAT DECREASING IN [1..argv.argc) DO <> <> <> dir: ROPE ¬ argv[i]; IF NOT Rope.Match["/*", dir] THEN { ERROR CommanderOps.Failed["Please use absolute directories"]; }; IF NOT Rope.Match["*/", dir] THEN dir ¬ Rope.Concat[dir, "/"]; newRules ¬ CONS[dir, newRules]; IF tail = NIL THEN tail ¬ newRules; Process.CheckForAbort[]; ENDLOOP; <> SELECT data FROM $Add => IF tail = NIL THEN newRules ¬ oldRules ELSE tail.rest ¬ oldRules; $Push => newRules ¬ LIST[newRules, oldRules]; ENDCASE; }; IF ChangeSearchRules[from: oldRules, to: newRules] THEN EXIT; ENDLOOP; }; StreamName: PROC [stream: IO.STREAM] RETURNS [ROPE] ~ { RETURN [PFS.RopeFromPath[PFS.GetName[PFS.OpenFileFromStream[stream]].fullFName]] }; PreRegisterSimple: PROC [commandName: ROPE, cmd: Commander.Handle] RETURNS [foundOne: BOOL ¬ FALSE] ~ { commandSearchRules: LIST OF PATH ~ GetSearchRules[cmd]; shortName: PATH ~ PFS.PathFromRope[Rope.Concat[commandName, ".command"]]; Inner: PROC ~ { in: IO.STREAM ¬ PFS.StreamOpen[shortName ! PFS.Error => GOTO NoDice]; streamName: ROPE ~ StreamName[in]; fail: BOOL ¬ FALSE; foundOne ¬ TRUE; BEGIN ENABLE UNWIND => {IO.Close[in]}; subCmd: Commander.Handle ~ CommanderOps.CreateFromStreams[in: in, parentCommander: cmd]; fail ¬ CommanderOps.ReadEvalPrintLoop[subCmd]; IF Commander.Lookup[commandName] = NIL THEN ERROR CommanderOps.Failed[Rope.Concat[streamName, " failed to register command"]]; IO.Close[in]; END; IF fail THEN ERROR CommanderOps.Failed[Rope.Concat[streamName, " failed."]] EXITS NoDice => NULL; }; Inner[]; FOR tail: LIST OF PATH ¬ commandSearchRules, tail.rest UNTIL foundOne OR tail = NIL DO < PFS.DoInWDir[PATH[%g], Inner]; (shortName = %g)\n", [rope[PFS.RopeFromPath[tail.first]]], [rope[PFS.RopeFromPath[shortName]]]];>> PFS.DoInWDir[wDir: tail.first, inner: Inner]; ENDLOOP; }; GetBase: PROC [shortName: PFSNames.Component] RETURNS [ROPE] ~ { dot: INT ¬ Rope.FindBackward[s1: shortName.name.base, s2: ".", pos1: shortName.name.start+shortName.name.len]; len: INT ¬ IF dot < shortName.name.start THEN shortName.name.len ELSE dot-shortName.name.start; RETURN[Rope.Substr[base: shortName.name.base, start: shortName.name.start, len: len]]; }; AbbreviationCommand: Commander.CommandProc ~ { [result: result, msg: msg] ¬ CommanderOps.ExecuteCommand[cmd, Rope.Cat[NARROW[cmd.procData.clientData], " ", cmd.commandLine]]; }; RegisterAbbreviation: PROC [cmd: Commander.Handle, new, old: ROPE] ~ { <> IF Commander.Lookup[new] # NIL THEN RETURN; -- avoid self-reference if names differ only by case. Commander.Register[key: new, proc: AbbreviationCommand, doc: Rope.Cat["(short for ", old,")"], clientData: old, interpreted: FALSE]; }; commandPattern: ROPE ~ "*.command"; cmPattern: ROPE ~ "*.cm"; PreRegister: PROC [commandName: ROPE, cmd: Commander.Handle] RETURNS [fullname: ROPE ¬ NIL] ~ { ENABLE { PFS.Error => { ERROR CommanderOps.Failed[error.explanation] }; }; IF NOT (Commander.Lookup[commandName] # NIL OR PreRegisterSimple[commandName, cmd]) THEN { pathPattern: PATH ~ PFS.PathFromRope[Rope.Concat[commandName, "*!H"]]; pattern: ROPE ~ Rope.Concat[commandName, "*"]; matches: LIST OF ROPE ¬ NIL; EachRegisteredCommand: Commander.EnumerateAction ~ { IF procData.proc # AbbreviationCommand AND Rope.Match[pattern: pattern, object: key, case: FALSE] THEN { matches ¬ CONS[key, matches]; }; }; [] ¬ CommanderRegistry.EnumeratePattern[pattern, EachRegisteredCommand]; IF matches # NIL THEN { fullname ¬ matches.first } ELSE { cm: BOOL ¬ FALSE; foundCM: ROPE ¬ NIL; EachFileCommand: PFS.NameProc ~ { shortName: PFSNames.Component ¬ PFSNames.ShortName[name]; shortNameRope: ROPE ¬ Rope.Substr[base: shortName.name.base, start: shortName.name.start, len: shortName.name.len]; key: ROPE ~ GetBase[shortName]; IF Rope.Match[pattern: commandPattern, object: shortNameRope, case: FALSE] THEN { matches ¬ CONS[key, matches] } ELSE IF Rope.Match[pattern: cmPattern, object: shortNameRope, case: FALSE] THEN { foundCM ¬ key; matches ¬ CONS[Rope.Concat["Source ", PFS.RopeFromPath[PFSNames.StripVersionNumber[name]]], matches]; }; }; Inner: PROC ~ { PFS.EnumerateForNames[pattern: pathPattern, proc: EachFileCommand]; IF matches # NIL AND matches.rest = NIL THEN { <> SELECT TRUE FROM (foundCM # NIL) => { RegisterAbbreviation[cmd, foundCM, matches.first]; fullname ¬ foundCM; }; (PreRegisterSimple[matches.first, cmd]) => { fullname ¬ matches.first; }; ENDCASE => NULL; }; }; Inner[]; FOR tail: LIST OF PATH ¬ GetSearchRules[cmd], tail.rest UNTIL matches # NIL OR tail = NIL DO PFS.DoInWDir[wDir: tail.first, inner: Inner]; ENDLOOP; }; SELECT TRUE FROM (matches = NIL) => ERROR CommanderOps.Failed[Rope.Concat["No .command or .cm file found for ", commandName]]; (matches.rest # NIL) => { matches ¬ RopeList.Sort[matches, RopeList.IgnoreCase]; IO.PutRope[cmd.err, commandName]; IO.PutRope[cmd.err, " ambiguous: "]; FOR tail: LIST OF ROPE ¬ matches, tail.rest UNTIL tail = NIL DO IO.PutRope[cmd.err, " "]; IO.PutRope[cmd.err, IF Rope.Match["Source *", tail.first] THEN tail.first.Substr[7] ELSE tail.first]; ENDLOOP; IO.PutRope[cmd.err, "\n"]; CommanderOps.Failed["Ambiguous command name"]; }; ENDCASE => NULL; }; }; PreRegisterCommand: Commander.CommandProc ~ { fullName: ROPE ¬ NIL; n: INT ¬ 0; DO token: ROPE ~ CommanderOps.NextArgument[cmd]; IF token = NIL THEN EXIT; fullName ¬ PreRegister[token, cmd]; n ¬ n + 1; ENDLOOP; IF n = 1 THEN result ¬ fullName; }; SourceCommand: Commander.CommandProc ~ { ENABLE PFS.Error => { ERROR CommanderOps.Failed[error.explanation] }; argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd]; fileName: ROPE ~ IF argv.argc > 1 THEN argv[1] ELSE NIL; path: PATH ~ PFS.AbsoluteName[PFS.PathFromRope[fileName]]; fileStream: IO.STREAM ~ PFS.StreamOpen[path]; BEGIN ENABLE UNWIND => { IO.Close[fileStream, TRUE] }; child: Commander.Handle ~ CommanderOps.CreateFromStreams[in: fileStream, parentCommander: cmd]; child.propertyList ¬ CONS[List.DotCons[$CommandFileArgumentVector, argv], child.propertyList]; child.propertyList ¬ CONS[List.DotCons[$CommandFileDirectory, PFS.RopeFromPath[PFSNames.Directory[path]]], child.propertyList]; IF CommanderOps.ReadEvalPrintLoop[child].hadFailure THEN result ¬ $Failure; END; IO.Close[fileStream]; }; RedirectIOCommand: Commander.CommandProc ~ { ENABLE PFS.Error => { ERROR CommanderOps.Failed[error.explanation] }; cmds: IO.STREAM ~ IO.RIS[cmd.commandLine]; index: INT ¬ 0; in: IO.STREAM ¬ NIL; out: IO.STREAM ¬ NIL; pipe: IO.STREAM ¬ NIL; IF cmd.procData.clientData = $ReceivePipe THEN { in ¬ WITH CommanderOps.GetProp[cmd, $PipedOutput] SELECT FROM rope: ROPE => IO.RIS[rope], ENDCASE => IO.noInputStream; CommanderOps.PutProp[cmd, $PipedOutput, NIL]; }; DO token: CommanderOps.Token ~ CommanderOps.GetCmdToken[cmds]; SELECT TRUE FROM Rope.Equal[token.value, "-from", FALSE] => { fName: ROPE ~ CommanderOps.GetCmdToken[cmds].value; in ¬ PFS.StreamOpen[PFS.PathFromRope[fName]]; }; Rope.Equal[token.value, "-to", FALSE] => { fName: ROPE ~ CommanderOps.GetCmdToken[cmds].value; out ¬ PFS.StreamOpen[PFS.PathFromRope[fName], create]; }; Rope.Equal[token.value, "-append", FALSE] => { fName: ROPE ~ CommanderOps.GetCmdToken[cmds].value; out ¬ PFS.StreamOpen[PFS.PathFromRope[fName], append]; }; Rope.Equal[token.value, "-pipe", FALSE] => { pipe ¬ out ¬ IO.ROS[]; }; ENDCASE => { index ¬ token.start; EXIT }; ENDLOOP; [result, msg] ¬ CommanderOps.ExecuteCommand[ cmd: CommanderOps.CreateFromStreams[in: in, out: out, parentCommander: cmd], wholeCommandLine: Rope.Substr[cmd.commandLine, index] ]; IF pipe # NIL THEN { CommanderOps.PutProp[cmd, $PipedOutput, IO.RopeFromROS[pipe]]; }; IF out # NIL THEN { IO.Close[out] }; IF in # NIL THEN { IO.Close[in] }; }; <> NullSeparatorProc: PFSNames.SeparatorProc ~ {}; PFSFail: PROC [msg: ROPE, path: PATH] ~ { CommanderOps.Failed[Rope.Concat[msg, PFS.RopeFromPath[path]]]; }; ForceDirectory: PROC [path: PATH, mustExist: BOOL] RETURNS [PATH] ~ { ENABLE PFS.Error => { <<-- FileInfo will raise an error if it doesn't like the path>> SELECT error.code FROM $unknownFile => PFSFail["No such directory: ", path]; $invalidNameSyntax => PFSFail["Invalid name: ", path]; $fileTypeMismatch => PFSFail["Not a directory: ", path]; ENDCASE => ERROR CommanderOps.Failed[error.explanation]; }; path ¬ PFS.AbsoluteName[path]; IF mustExist THEN { nMatches: INT ¬ 0; match: PATH ¬ NIL; EachMatch: PFS.InfoProc ~ { IF fileType = PFS.tDirectory THEN { nMatches ¬ nMatches + 1; match ¬ fullFName; }; }; PFS.EnumerateForInfo[pattern: PFSNames.SubName[name: path, absolute: TRUE, directory: FALSE], proc: EachMatch]; IF nMatches = 0 THEN { <> OneMatch: PFS.InfoProc ~ { <> RETURN[FALSE] }; PFS.EnumerateForInfo[pattern: PFSNames.SubName[name: path, absolute: TRUE, directory: TRUE], proc: OneMatch]; PFS.EnumerateForInfo[pattern: PFSNames.SubName[name: path, absolute: TRUE, directory: FALSE], proc: EachMatch]; IF nMatches = 0 THEN PFSFail["Not a directory: ", path]; }; IF nMatches # 1 THEN PFSFail["Ambiguous pattern: ", path]; path ¬ match; }; IF NOT PFSNames.IsADirectory[path] THEN { path ¬ PFSNames.SubName[name: path, absolute: TRUE, directory: TRUE]; }; RETURN [path]; }; SetWDirRope: PROC [rope: ROPE, mustExist: BOOL] ~ { SetWDir[PFS.PathFromRope[rope], mustExist] }; SetWDir: PROC [wDir: PATH, mustExist: BOOL] ~ { wDirRope: ROPE ~ PFS.RopeFromPath[ForceDirectory[wDir, mustExist]]; old: List.AList ~ ProcessProps.GetPropList[]; new: List.AList ~ List.PutAssoc[key: $WorkingDirectory, val: wDirRope, aList: old]; IF old # new THEN ERROR CommanderOps.Failed["Could not add $WorkingDirectory property"]; [] ¬ List.PutAssoc[key: $WorkingDirectory, val: wDirRope, aList: ProcessProps.GetPropList[]]; }; UserCedarDir: PROC RETURNS [PATH] ~ { localDir: PATH ¬ PFS.PathFromRope[SystemNames.UserCedarDir[NIL, $releaseDir]]; RETURN [localDir] }; HomeDir: PROC [cmd: Commander.Handle] RETURNS [ROPE] ~ { WITH CommanderOps.GetProp[cmd, $HOME] SELECT FROM rope: ROPE => RETURN [rope]; ENDCASE; RETURN [NIL] }; CDCommand: Commander.CommandProc ~ { <<-- Three variants of this procedure. CDV moves relative to UserCedarDir[] and is indicated by the $V clientData. CDF moves to a directory regardless of its existence ($F). The default CD checks if the directory exists. Note that this assumes that the UserCedarDir and the user's home directory exist (it doesn't check). See ForceDirectory for checking.>> ENABLE PFS.Error => IF error.group = user THEN ERROR CommanderOps.Failed[error.explanation]; mustExist: BOOL ~ SELECT cmd.procData.clientData FROM $N, $H => TRUE ENDCASE => FALSE; arg: ROPE ¬ CommanderOps.NextArgument[cmd]; IF CommanderOps.NextArgument[cmd] # NIL THEN ERROR CommanderOps.Failed["Too many arguments"]; SELECT TRUE FROM (cmd.procData.clientData = $V) => { -- CDV or PUSHV commands SetWDir[wDir: UserCedarDir[], mustExist: FALSE]; }; (arg = NIL) => { arg ¬ HomeDir[cmd] }; (cmd.procData.clientData = $H) => { SetWDirRope[rope: HomeDir[cmd], mustExist: FALSE]; }; ENDCASE; SetWDirRope[rope: arg, mustExist: mustExist]; RETURN PWDCommand[cmd]; }; PushCommand: Commander.CommandProc ~ { old: LIST OF PATH ~ WITH CommanderOps.GetProp[cmd, $WorkingDirectoryStack] SELECT FROM list: LIST OF PATH => list, ENDCASE => NIL; new: LIST OF PATH ~ CONS[PFS.GetWDir[], old]; [result: result, msg: msg] ¬ CDCommand[cmd]; CommanderOps.PutProp[cmd, $WorkingDirectoryStack, new]; }; PopCommand: Commander.CommandProc ~ { WITH CommanderOps.GetProp[cmd, $WorkingDirectoryStack] SELECT FROM list: LIST OF PATH => { SetWDir[list.first, FALSE]; CommanderOps.PutProp[cmd, $WorkingDirectoryStack, list.rest]; RETURN PWDCommand[cmd]; }; ENDCASE => RETURN [result: $Failure, msg: "working directory stack is empty"]; }; PWDCommand: Commander.CommandProc ~ { IO.PutRope[cmd.out, PFS.RopeFromPath[PFS.GetWDir[]]]; IO.PutRope[cmd.out, "\n"]; }; FromCommand: Commander.CommandProc ~ { ENABLE { PFS.Error => IF error.group = user THEN ERROR CommanderOps.Failed[error.explanation]; }; ris: IO.STREAM ~ IO.RIS[cmd.commandLine]; directory: ROPE ~ CommanderOps.GetCmdToken[ris].value; path: PATH ~ IF directory = NIL THEN NIL ELSE PFS.PathFromRope[directory]; IF path = NIL THEN ERROR CommanderOps.Failed["Usage: From command ..."] ELSE { Inner: PROC = { child: Commander.Handle ~ CommanderOps.CreateFromStreams[in: ris, parentCommander: cmd]; IF CommanderOps.ReadEvalPrintLoop[child].hadFailure THEN result ¬ $Failure; }; PFS.DoInWDir[wDir: ForceDirectory[path, FALSE], inner: Inner]; IO.Close[ris]; }; }; <> MyFileType: TYPE ~ {datafile, realdir, symdir}; QuickIsItADirectory: PROC [path: PATH] RETURNS [MyFileType] ~ { <> short: PFSNames.Component ~ PFSNames.ShortName[path]; xx: INT ~ short.name.start+short.name.len; end: INT ~ MIN[xx, Rope.Size[short.name.base]]; -- to keep compiler happier xlen: INT ¬ 0; GetMyFileType: PROC [fullFName, attachedTo: PFS.PATH, uniqueID: PFS.UniqueID, bytes: INT, mutability: PFS.Mutability, fileType: PFS.FileType] RETURNS [MyFileType] ~ { RETURN[IF (fileType = PFS.tDirectory) THEN IF attachedTo=NIL THEN realdir ELSE symdir ELSE datafile]; }; IF short.version.versionKind = numeric THEN RETURN [datafile]; -- vux does not have versioned directories; this could be wrong for XNS. FOR i: INT DECREASING IN [short.name.start..end) DO SELECT Rope.Fetch[short.name.base, i] FROM '. => IF xlen > 0 THEN RETURN [datafile]; -- looks like it has an extension; call it a file. IN ['a..'z] => xlen ¬ xlen + 1; ENDCASE => EXIT; ENDLOOP; RETURN [APPLY [GetMyFileType, PFS.FileInfo[path]]]; }; FetchEnd: PROC [path: PATH, begin: BOOL] RETURNS [CHAR] ~ { short: PFSNames.Component ~ PFSNames.ShortName[path]; xx: INT ~ short.name.start+short.name.len; end: INT ~ MIN[xx, Rope.Size[short.name.base]]; i: INT ~ IF begin THEN short.name.start ELSE end-1; IF i >= 0 THEN RETURN [Rope.Fetch[short.name.base, i]]; RETURN ['?] }; Hidden: PROC [path, pattern: PATH] RETURNS [BOOL] ~ { c: CHAR ¬ FetchEnd[path, TRUE]; IF c = '. AND c # FetchEnd[pattern, TRUE] THEN RETURN [TRUE]; c ¬ FetchEnd[path, FALSE]; IF c = '~ AND c # FetchEnd[pattern, FALSE] THEN RETURN [TRUE]; RETURN [FALSE] }; Dotty: PROC [path: PATH] RETURNS [BOOL] ~ { short: PFSNames.Component ~ PFSNames.ShortName[path]; xx: INT ~ short.name.start+short.name.len; end: INT ~ MIN[xx, Rope.Size[short.name.base]]; len: INT ~ end-short.name.start; IF len NOT IN [1..2] THEN RETURN [FALSE]; FOR i: INT IN [short.name.start..end) DO IF Rope.Fetch[short.name.base, i] # '. THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE]; }; bangH: PATH ~ PFS.PathFromRope["*!H"]; PatternFromDirectory: PROC [path: PATH] RETURNS [PATH] ~ { RETURN [PFSNames.Cat[PFSNames.SetVersionNumber[path, [none]], bangH]] }; fileArgumentSwitches: ROPE ~ " switches: -a include files like .* and *~ -f full name always (otherwise strips working directory) -r recursive - delve into subdirectories -s slow - don't use heuristics to guess which might be subdirectories -n never follow symbolic links to directories "; FileArguments: PROC [cmd: Commander.Handle, nullCheck: BOOL, recursive: BOOL, stripPrefix: BOOL, quick: BOOL, all: BOOL, dirs: BOOL, files: BOOL, action: PROC[PATH]] RETURNS [n: INT ¬ 0] ~ { wd: PATH ~ PFS.GetWDir[]; stripVersion: BOOL ¬ FALSE; followDirLinks: BOOL ¬ TRUE; MaybeStrip: PROC [name: PATH] RETURNS [PATH] ~ { Abbrev: PROC [isa: BOOL, suffix: PATH] ~ { IF isa THEN name ¬ suffix }; IF stripPrefix THEN [] ¬ APPLY [Abbrev, PFSNames.IsAPrefix[wd, name]]; IF stripVersion THEN name ¬ PFSNames.SetVersionNumber[name, [none]]; RETURN [name] }; Emit: PROC [name, pattern: PATH] ~ { IF all OR NOT Hidden[name, pattern] THEN { n ¬ n + 1; action[MaybeStrip[name]] }; }; argi: INT ¬ 0; arg: ROPE; [] ¬ CommanderOps.ArgN[cmd, argi]; WHILE Rope.Match["-*", arg ¬ CommanderOps.NextArgument[cmd, leaveQuotes]] DO sense: BOOL ¬ TRUE; FOR i: INT IN (0..Rope.Size[arg]) DO c: CHAR ~ Rope.Fetch[arg, i]; SELECT c FROM '~ => sense ¬ NOT sense; 'a, 'A => { all ¬ sense; sense ¬ TRUE }; 'f, 'F => { stripPrefix ¬ NOT sense; sense ¬ TRUE }; 'r, 'R => { recursive ¬ sense; sense ¬ TRUE }; 's, 'S => { quick ¬ NOT sense; sense ¬ TRUE }; 'n, 'N => { followDirLinks ¬ NOT sense; sense ¬ TRUE }; ENDCASE => CommanderOps.Failed[cmd.procData.doc]; ENDLOOP; argi ¬ argi + 1; ENDLOOP; [] ¬ CommanderOps.ArgN[cmd, argi]; WHILE (arg ¬ CommanderOps.NextArgument[cmd]) # NIL DO before: INT ¬ n; pattern0: PATH ¬ PFS.PathFromRope[arg]; Enum: PROC [pattern: PATH] ~ IF quick THEN EnumNames ELSE EnumInfo; EnumInfo: PROC [pattern: PATH] ~ { InfoMatch: PFS.InfoProc ~ { Match[fullFName, IF (fileType = PFS.tDirectory) THEN IF attachedTo=NIL THEN realdir ELSE symdir ELSE datafile, pattern] }; PFS.EnumerateForInfo[pattern: pattern, proc: InfoMatch]; }; EnumNames: PROC [pattern: PATH] ~ { NameMatch: PFS.NameProc ~ {Match[name, QuickIsItADirectory[name], pattern]}; PFS.EnumerateForNames[pattern: pattern, proc: NameMatch] }; Match: PROC [name: PATH, type: MyFileType, pattern: PATH] ~ { IF (type=realdir) OR (type=symdir) THEN { IF dirs THEN Emit[name, pattern]; IF recursive AND (followDirLinks OR type=realdir) AND NOT Dotty[name] AND (all OR NOT Hidden[name, pattern]) THEN { Enum[pattern: pattern ¬ PatternFromDirectory[name] ! PFS.Error => IF error.code = $accessDenied THEN { cmd.err.PutRope[" -- "]; cmd.err.PutRope[PFS.RopeFromPath[name]]; cmd.err.PutRope[" - accessDenied --\n"]; CONTINUE; }; ]; }; } ELSE { IF NOT dirs THEN Emit[name, pattern] }; }; stripVersion ¬ PFSNames.ShortName[pattern0].version.versionKind = none; IF stripVersion THEN { pattern0 ¬ PFSNames.SetVersionNumber[pattern0, [highest]] }; Enum[pattern0]; IF nullCheck AND before = n THEN { PFSFail["No files from enumeration of pattern ", pattern0]; }; ENDLOOP; }; FilesCommand: Commander.CommandProc = { ENABLE PFS.Error => IF error.group = user THEN ERROR CommanderOps.Failed[error.explanation]; dirs: BOOL ~ cmd.procData.clientData = $Dirs; n: INT ~ FileArguments[cmd: cmd, nullCheck: FALSE, recursive: FALSE, stripPrefix: TRUE, quick: TRUE, all: FALSE, dirs: dirs, files: NOT dirs, action: Inner]; Inner: PROC [name: PATH] ~ { IO.PutRope[cmd.out, PFS.RopeFromPath[name]]; IO.PutRope[cmd.out, "\n"]; }; }; TypeCommand: Commander.CommandProc = { ENABLE PFS.Error => IF error.group = user THEN ERROR CommanderOps.Failed[error.explanation]; n: INT ~ FileArguments[cmd: cmd, nullCheck: TRUE, recursive: FALSE, stripPrefix: FALSE, quick: FALSE, all: FALSE, dirs: FALSE, files: TRUE, action: Inner]; Inner: PROC [name: PATH] ~ { fileStream: IO.STREAM ~ PFS.StreamOpen[name]; IOClasses.Copy[from: fileStream, to: cmd.out, closeFrom: TRUE, closeTo: FALSE]; }; IF n = 0 THEN CommanderOps.Failed["Usage: Type pattern*"]; }; PrintFilePropertiesCommand: Commander.CommandProc ~ { ENABLE PFS.Error => CommanderOps.Failed[error.explanation]; arg0: ROPE _ CommanderOps.NextArgument[cmd]; IF arg0 = NIL THEN CommanderOps.Failed[cmd.procData.doc]; FOR arg: ROPE _ arg0, CommanderOps.NextArgument[cmd] UNTIL arg = NIL DO EachProp: PROC [propertyName: ROPE, propertyValue: ROPE] RETURNS [continue: BOOL ¬ TRUE] ~ { IO.PutRope[cmd.out, " "]; IO.PutRope[cmd.out, propertyName]; IO.PutRope[cmd.out, ": "]; IO.PutRope[cmd.out, Convert.RopeFromRope[propertyValue, TRUE]]; IO.PutRope[cmd.out, "\n"]; }; file: PFS.OpenFile ~ PFS.Open[PFS.PathFromRope[arg]]; IO.PutRope[cmd.out, PFS.RopeFromPath[PFS.GetName[file].fullFName]]; IO.PutRope[cmd.out, "\n"]; PFS.EnumerateClientProperties[file, EachProp]; PFS.Close[file]; ENDLOOP; }; SetFilePropertiesCommand: Commander.CommandProc ~ { ENABLE PFS.Error => CommanderOps.Failed[error.explanation]; changes: SymTab.Ref ~ SymTab.Create[]; arg0: ROPE _ CommanderOps.NextArgument[cmd]; IF arg0 = NIL THEN CommanderOps.Failed[cmd.procData.doc]; FOR arg: ROPE _ arg0, CommanderOps.NextArgument[cmd] UNTIL arg = NIL DO IF Rope.Match["-*", arg] THEN { [] ¬ changes.Store[key: Rope.Substr[arg, 1], val: CommanderOps.NextArgument[cmd]]; } ELSE { file: PFS.OpenFile ~ PFS.Open[PFS.PathFromRope[arg]]; EachChange: SymTab.EachPairAction ~ { value: ROPE ~ NARROW[val]; PFS.SetClientProperty[file, key, value ! PFS.Error => { cmd.err.PutRope[error.explanation]; cmd.err.PutRope["\n"]; CONTINUE; }]; }; [] ¬ changes.Pairs[EachChange]; PFS.Close[file]; }; ENDLOOP; }; IfFilesDifferCommand: Commander.CommandProc ~ { ris: IO.STREAM ~ IO.RIS[cmd.commandLine]; filename1: ROPE ~ CommanderOps.GetCmdToken[ris].value; filename2: ROPE ~ CommanderOps.GetCmdToken[ris].value; same: BOOL ¬ TRUE; IF filename2 = NIL THEN CommanderOps.Failed[cmd.procData.doc]; BEGIN ENABLE PFS.Error => CommanderOps.Failed[error.explanation]; stream1: IO.STREAM ~ PFS.StreamOpen[PFS.PathFromRope[filename1]]; stream2: IO.STREAM ~ PFS.StreamOpen[PFS.PathFromRope[filename2]]; UNTIL (NOT same) OR IO.EndOf[stream1] OR IO.EndOf[stream2] DO same ¬ IO.GetChar[stream1] = IO.GetChar[stream2]; ENDLOOP; IF same THEN same ¬ IO.EndOf[stream1] AND IO.EndOf[stream2]; IO.Close[stream1]; IO.Close[stream2]; END; IF cmd.procData.clientData=$NOT THEN same ¬ NOT same; IF NOT same THEN RETURN CommanderOps.ExecuteCommand[cmd, IO.GetRope[ris]]; }; YankCommand: Commander.CommandProc ~ { bytesMoved: INT ¬ 0; bytesWouldMove: INT ¬ 0; verbose: BOOL ¬ FALSE; FinalReport: PROC = { IF verbose THEN { IF bytesMoved + bytesWouldMove = 0 THEN cmd.out.PutRope["Moved 0 bytes\n"]; IF bytesMoved # 0 THEN cmd.out.PutF1["Moved %g bytes\n", [integer[bytesMoved]]]; IF bytesWouldMove # 0 THEN cmd.out.PutF1["Would have moved %g bytes\n", [integer[bytesWouldMove]]]; }; }; BEGIN ENABLE { Convert.Error => CommanderOps.Failed[cmd.procData.doc]; PFS.Error => CommanderOps.Failed[error.explanation]; UNWIND => { cmd.err.PutRope[" *** "]; FinalReport[] }; }; infoOnly: BOOL ¬ FALSE; arg0: ROPE _ CommanderOps.NextArgument[cmd]; IF arg0 = NIL THEN CommanderOps.Failed[cmd.procData.doc]; FOR arg: ROPE _ arg0, CommanderOps.NextArgument[cmd] UNTIL arg = NIL DO IF Rope.Match["-*", arg] THEN { sense: BOOL ¬ TRUE; FOR i: INT IN (0..Rope.Size[arg]) DO SELECT Rope.Lower[Rope.Fetch[arg, i]] FROM 'v => { verbose ¬ sense; sense ¬ TRUE }; 'n => { infoOnly ¬ sense; sense ¬ TRUE }; '~ => { sense ¬ NOT sense }; ENDCASE => CommanderOps.Failed[cmd.procData.doc]; ENDLOOP; } ELSE { pattern: PFS.PATH ~ PFS.PathFromRope[arg]; matches: INT ¬ 0; EachMatch: PFS.InfoProc = { <> Report: PROC [s: IO.STREAM, fmt: ROPE] = { s.PutF[fmt, [rope[PFS.RopeFromPath[fullFName]]], [rope[PFS.RopeFromPath[attachedTo]]]] }; matches ¬ matches + 1; IF fileType # PFS.tDirectory AND attachedTo # NIL THEN { IF bytes < 0 THEN { result ¬ $Failure; Report[cmd.err, "Bad attachment: %q --> %q\n"]; } ELSE { IF verbose THEN { Report[cmd.out, "%q := %q"]; cmd.out.PutF1[" (%g bytes)\n", [integer[bytes]]]; }; IF infoOnly THEN { bytesWouldMove ¬ bytesWouldMove + bytes; } ELSE { PFS.Delete[fullFName, uniqueID]; PFS.Copy[from: attachedTo, to: fullFName, wantedUniqueID: uniqueID, confirmProc: NIL ! UNWIND => { Report[cmd.err, " *** So sorry, the link from %q to %q has been lost\n"]; }]; bytesMoved ¬ bytesMoved + bytes; }; }; }; }; PFS.EnumerateForInfo[pattern: pattern, proc: EachMatch]; IF matches = 0 THEN {result ¬ $Failure; cmd.err.PutF1["No files matching %q\n", [rope[arg]]]}; }; ENDLOOP; END; FinalReport[]; }; <> Commander.Register[key: "AddSearchRules", proc: SetSearchRules, doc: "Add search rules", clientData: $Add, interpreted: TRUE]; Commander.Register[key: "Cat", proc: TypeCommand, doc: "Write the contents of the named file(s) onto stdout (switches and patterns like the files command)\n", interpreted: TRUE]; Commander.Register[key: "CD", proc: CDCommand, doc: "Change Working Directory", interpreted: TRUE, clientData: $N]; Commander.Register[key: "CDF", proc: CDCommand, doc: "Change Working Directory regardless of its existence", interpreted: TRUE, clientData: $F]; Commander.Register[key: "CDH", proc: CDCommand, doc: "Change Working Directory (home relative)", interpreted: TRUE, clientData: $H]; Commander.Register[key: "CDV", proc: CDCommand, doc: "Change Working Directory (version relative)", interpreted: TRUE, clientData: $V]; Commander.Register[key: "Dirs", proc: FilesCommand, doc: Rope.Concat["List directories matching given pattern(s)", fileArgumentSwitches], clientData: $Dirs]; Commander.Register[key: "Files", proc: FilesCommand, doc: Rope.Concat["List files matching given pattern(s)", fileArgumentSwitches]]; Commander.Register[key: "From", proc: FromCommand, doc: "Execute a command in a remote directory", interpreted: FALSE]; Commander.Register[key: "Pop", proc: PopCommand, doc: "Pop Working Directory", interpreted: TRUE]; <> <> Commander.Register[key: "PreRegister", proc: PreRegisterCommand, doc: "commandName ... - Ensure the registration of one or more commands.", interpreted: TRUE]; Commander.Register[key: "PrintSearchRules", proc: PrintSearchRules, doc: "Print command search rules", interpreted: TRUE]; Commander.Register[key: "Push", proc: PushCommand, doc: "Push Working Directory", interpreted: TRUE, clientData: $N]; Commander.Register[key: "PushH", proc: PushCommand, doc: "Push Working Directory (home relative)", interpreted: TRUE, clientData: $H]; <> <> Commander.Register[key: "PushV", proc: PushCommand, doc: "Push Working Directory (version relative)", interpreted: TRUE, clientData: $V]; Commander.Register[key: "PWD", proc: PWDCommand, doc: "Print Working Directory", interpreted: TRUE]; Commander.Register[key: "RedirectIO", proc: RedirectIOCommand, doc: "Execute a command with IO redirection", interpreted: FALSE]; Commander.Register[key: "SetSearchRules", proc: SetSearchRules, doc: "Set command search rules: SetSearchRules directory*", clientData: $Set, interpreted: TRUE]; Commander.Register[key: "Source", proc: SourceCommand, doc: "Execute commands from a file", interpreted: TRUE]; Commander.Register[key: "Type", proc: TypeCommand, doc: Rope.Concat["Type file contents\n", fileArgumentSwitches], interpreted: TRUE]; Commander.Register[key: "PrintFileProperties", proc: PrintFilePropertiesCommand, doc: "Show the PFS client properties of files", interpreted: TRUE]; Commander.Register[key: "SetFileProperties", proc: SetFilePropertiesCommand, doc: "Set PFS client properties of files\nsyntax: -propname \"propval\" filename1 filename2 ...", interpreted: TRUE]; Commander.Register[key: "|", proc: RedirectIOCommand, doc: "Receive piped output from previous command", clientData: $ReceivePipe, interpreted: FALSE]; Commander.Register["IfFilesDiffer", IfFilesDifferCommand, "do the commandLine if the named files differ\nargs: filename1 filename2 commandLine"]; Commander.Register["IfFilesEqual", IfFilesDifferCommand, "do the commandLine if the named files are equal in contents\nargs: filename1 filename2 commandLine", $NOT]; Commander.Register["Yank", YankCommand, "Makes files local (eliminating attachments), by copying bits if required.\nargs: pattern1 pattern2 ...\nswitches: -v (verbose) -n (don't really do it)"]; END.