<> <> DIRECTORY Atom USING [GetPName], CIFS USING [GetFC, Open, read], Commander USING [CommandProc, CommandObject, Enumerate, Lookup, Register, Handle], ConvertUnsafe USING [AppendRope], Exec USING [commandLine], File USING [Capability, nullCapability], FileIO USING [Open, OpenFailed], Generator USING [Handle, Produce], IO USING [AppendStreams, ChangeDeliverWhen, Close, CreateFilterCommentsStream, CR, DeliverWhenProc, EndOf, EveryThing, GetOutputStreamRope, LookupData, PeekChar, PutChar, PutF, PutRope, RemoveData, rope, ROPE, RIS, ROS, SetEcho, SetIndex, SP, StoreData, STREAM, BreakProc, CharProc, GetSequence, GetToken, GetRefAny, IDProc, SkipOver, WhiteSpace], List USING [DotCons, Nconc1, Sort], MessageWindow USING [Append], ProcessProps USING [AddPropList], CommandProcOps USING [PutProperty], Rope USING [Cat, Concat, Equal, Fetch, Find, IsEmpty, Length, Replace, Substr], RTFiles USING [GetMapList], Spell USING [GeneratorFromEnumerator, SpellingGenerator, IsAPattern, Modes], TTY USING [Handle], SymTab USING [Create, Fetch, Store, Ref], UnsafeStorage USING [GetSystemUZone], UserExec USING [Confirm, UserAborted, DoIt, AcquireResource, CommandProc, ExecHandle, GetMatchingFileList, GetStreams, GetTheOne, MethodProc, ReleaseResource, ErrorThisEvent, AcquireStreams, ReleaseStreams, TransformProc, HistoryEvent], UserExecExtras USING [CorrectionDisabled], UserExecPrivate USING [CommandRecord, commandString, EventFailed, ExecDeliverWhen, ExecPrivateRecord, GetExecFromStream, HistoryEventPrivateRecord, FileNotFound, methodList, MethodRecord, PrintCommand, RopeFromCMFile, RunBCDFile, StripComments, w, Zone, GetPrivateStuff], UserProfile USING [CallWhenProfileChanges, ProfileChangedProc, Token], VersionMapOps USING [SourceFileList, FindSource] ; UserExecRegCmdsImpl: CEDAR PROGRAM IMPORTS Atom, CIFS, Commander, CommandProcOps, ConvertUnsafe, Exec, FileIO, Generator, IO, List, MessageWindow, ProcessProps, Rope, RTFiles, Spell, SymTab, UnsafeStorage, UserExec, UserExecExtras, UserExecPrivate, UserProfile, VersionMapOps EXPORTS UserExec, UserExecPrivate = BEGIN OPEN IO; <> ExecPrivateRecord: PUBLIC TYPE = UserExecPrivate.ExecPrivateRecord; <> HistoryEventPrivateRecord: PUBLIC TYPE = UserExecPrivate.HistoryEventPrivateRecord; <<>> <> CallRegisteredProc: PUBLIC PROC [command: ROPE, event: UserExec.HistoryEvent, exec: UserExec.ExecHandle] RETURNS[handled: BOOLEAN _ TRUE] = { commandLineStream: STREAM = event.commandLineStream; registration: REF UserExecPrivate.CommandRecord _ GetRegistrationData[command]; commanderProc: Commander.CommandProc = Commander.Lookup[command].proc; nameOfBCD: ROPE; IF registration = NIL THEN RETURN[CallCommanderProc[command, event, exec]]; IF NOT Rope.Equal[registration.name, "_"] THEN SetUpCommandLine[event: event, exec: exec]; IF (nameOfBCD _ registration.nameOfBCD) # NIL AND commanderProc = dummyCommanderProc THEN -- check for commanderProc because the file may have been run and the command registered with commander directl, e.g. Walnut { doc: ROPE = registration.documentation; error: ROPE; notFound: BOOL _ FALSE; error _ UserExecPrivate.RunBCDFile[fileName: nameOfBCD, out: UserExec.GetStreams[exec].out ! UserExecPrivate.FileNotFound => { error _ Rope.Concat[nameOfBCD, " must be on your local disk to execute this command"]; notFound _ TRUE; CONTINUE; } ].error; IF notFound THEN TRUSTED { -- what errors to catch? lst: VersionMapOps.SourceFileList; cap: File.Capability _ File.nullCapability; MessageWindow.Append["Loading Version Map...", TRUE]; lst _ VersionMapOps.FindSource[short: nameOfBCD, mapList: RTFiles.GetMapList[]]; IF lst # NIL THEN { MessageWindow.Append[Rope.Cat["Loading ", lst.first.name, "..."]]; cap _ CIFS.GetFC[CIFS.Open[name: lst.first.name, mode: CIFS.read]]; }; IF cap # File.nullCapability THEN error _ UserExecPrivate.RunBCDFile[fileName: lst.first.name, fileCapability: cap, out: UserExec.GetStreams[exec].out].error; }; IF error # NIL THEN UserExecPrivate.EventFailed[event: event, msg: error, offender: nameOfBCD]; registration _ GetRegistrationData[name: registration.name]; registration.nameOfBCD _ NIL; IF registration.commandProc = NIL AND registration.transformProc = NIL AND registration.oldStyleProc = NIL AND Commander.Lookup[command].proc = dummyCommanderProc THEN { UserExec.GetStreams[exec].out.PutF["*n*m%g was run and did not register any commands*s\n", rope[nameOfBCD]]; RETURN[TRUE]; }; IF registration # NIL AND registration.documentation = NIL THEN registration.briefDocumentation _ registration.documentation _ doc; RETURN[CallRegisteredProc[command, event, exec]]; }; IF registration.commandProc # NIL THEN -- if there is a UserExec.commandProc, use it even though there is a commander CommandProc, because it may (probably) have more functionality { ok: BOOL; msg: ROPE; inner: Commander.CommandProc = { [ok, msg] _ registration.commandProc[event, exec, registration.clientData]; }; PushPropsAndCallProc[inner, command, event, exec]; IF NOT ok THEN ERROR UserExec.ErrorThisEvent[event, msg]; -- msg should be stored in history, even on success. } ELSE IF registration.oldStyleProc # NIL THEN { Done: PROC = {[] _ UserExec.ReleaseResource[$Exec]}; [] _ UserExec.AcquireResource[resource: $Exec, owner: registration.name, exec: exec]; TRUSTED { ENABLE UNWIND => Done[]; private: REF ExecPrivateRecord = exec.privateStuff; inner: Commander.CommandProc = TRUSTED { registration.oldStyleProc[] }; UserExecPrivate.w _ private.execDotW; UserExecPrivate.commandString.length _ 0; IF Rope.Length[event.commandLine] > UserExecPrivate.commandString.maxlength THEN -- long command file. make string larger. Exec.commandLine.s _ UserExecPrivate.commandString _ UnsafeStorage.GetSystemUZone[].NEW[StringBody[Rope.Length[event.commandLine]]]; ConvertUnsafe.AppendRope[to: UserExecPrivate.commandString, from: event.commandLine]; Exec.commandLine.i _ 0; PushPropsAndCallProc[inner, command, event, exec]; Done[]; }; } ELSE IF registration.transformProc # NIL THEN { input: ROPE = registration.transformProc[event, exec, registration.clientData]; privateEvent: REF UserExecPrivate.HistoryEventPrivateRecord = event.privateStuff; IF Rope.Equal[input, event.input] THEN ERROR UserExec.ErrorThisEvent[event: event, msg: "You are looping"]; privateEvent.showInput _ TRUE; UserExec.DoIt[input, exec, event]; } ELSE IF commanderProc # dummyCommanderProc THEN RETURN[CallCommanderProc[command, event, exec]] -- command had registrationData, but nothing to do. Occurs for commands obtained from catalogue which when run, register themselves with commander only, e.g. smodel ELSE RETURN[FALSE]; RETURN[TRUE]; }; SetUpCommandLine: PROC [event: UserExec.HistoryEvent, exec: UserExec.ExecHandle] = { commandLine: ROPE _ event.commandLine; pos: INT; commandLine _ ExpandCommandLine[line: commandLine, event: event, exec: exec]; <> WHILE (pos _ Rope.Find[s1: commandLine, s2: "\\"]) # -1 DO commandLine _ Rope.Replace[base: commandLine, start: pos, len: 1]; ENDLOOP; event.commandLine _ commandLine; [] _ RIS[rope: commandLine, oldStream: event.commandLineStream]; -- for commands registered by UserExec.RegisterCommand }; -- of SetUpCommandLine ExpandCommandLine: PUBLIC PROC [line: ROPE, modes: Spell.Modes _ NIL, event: UserExec.HistoryEvent, exec: UserExec.ExecHandle] RETURNS [ROPE] = { inRopeStream: STREAM; pos: INT; <> WHILE (pos _ Rope.Find[s1: line, s2: "@"]) # -1 AND (pos = 0 OR Rope.Fetch[line, pos - 1] # '\\) DO fileName: ROPE; inRopeStream _ RIS[rope: line, oldStream: inRopeStream]; inRopeStream.SetIndex[pos]; fileName _ IO.GetToken[inRopeStream, IO.IDProc]; line _ Rope.Replace[base: line, start: pos, len: Rope.Length[fileName], with: UserExecPrivate.RopeFromCMFile[file: fileName, event: event, exec: exec].contents]; ENDLOOP; line _ UserExecPrivate.StripComments[event, line]; <> <> <> <> <> <> <> IF Spell.IsAPattern[line] THEN { r: ROPE; asFile: BOOLEAN _ FALSE; inRopeStream _ RIS[rope: line, oldStream: inRopeStream]; WHILE Rope.Length[r _ IO.GetToken[inRopeStream, IO.IDProc]] # 0 DO IF Spell.IsAPattern[r] THEN {outRopeStream: STREAM = IO.ROS[]; FOR l: LIST OF ROPE _ -- IF ~asFile THEN LookupCommands[r, exec] ELSE-- UserExec.GetMatchingFileList[file: r, event: event, exec: exec, modes: modes], l.rest UNTIL l = NIL DO outRopeStream.PutRope[l.first]; IF l.rest # NIL THEN outRopeStream.PutChar[SP]; ENDLOOP; line _ Rope.Replace[base: line, start: Rope.Find[line, r], len: Rope.Length[r], with: outRopeStream.GetOutputStreamRope[]]; }; asFile _ TRUE; ENDLOOP; }; RETURN[line]; }; -- of ExpandCommandLine GetRestOfStream: PUBLIC PROC [in: STREAM, includeLast: BOOL _ TRUE] RETURNS[value: ROPE] = { value _ IO.GetSequence[in, IO.EveryThing]; IF NOT includeLast THEN value _ Rope.Substr[base: value, len: Rope.Length[value] - 1]; }; <> <> <> <> <> <<};>> DoesUserMean: PUBLIC PROC [rope: ROPE, exec: UserExec.ExecHandle, intro: ROPE _ NIL] = { out: STREAM = UserExec.GetStreams[exec: exec].out; out.PutRope[IF intro = NIL THEN "perhaps you mean:\n" ELSE intro]; IF UserExec.Confirm[msg: rope, exec: exec] THEN UserExec.DoIt[rope, exec] ELSE UserExec.UserAborted[exec]; }; <> ReadQuietly: PUBLIC PROC [rope: ROPE, exec: UserExec.ExecHandle] = { in: STREAM; private: REF ExecPrivateRecord = UserExecPrivate.GetPrivateStuff[exec]; private.continuation _ TRUE; -- tells exec not to increment event number and to reuse event. private.dontPrompt _ TRUE; IF NOT rope.IsEmpty[] THEN { in _ UserExec.AcquireStreams[exec: exec].in; [] _ in.SetEcho[NIL]; -- turn off echoing. in.StoreData[key: $Count, data: NEW[INT _ rope.Length[]]]; [] _ IO.ChangeDeliverWhen[in, StuffBufferDeliverWhen]; -- turns echoing back on as soon as all characters consumed. AppendStreams[in, RIS[rope]]; }; }; <> StuffBufferDeliverWhen: IO.DeliverWhenProc -- PROC[char: CHAR, stream: STREAM] RETURNS[BOOL] -- = { exec: UserExec.ExecHandle = UserExecPrivate.GetExecFromStream[stream]; count: REF INT; private: REF UserExecPrivate.ExecPrivateRecord = UserExecPrivate.GetPrivateStuff[exec]; in, out: STREAM; [in, out] _ UserExec.GetStreams[exec: exec]; count _ NARROW[in.LookupData[$Count]]; count^ _ count^ - 1; IF count^ <= 0 THEN { [] _ IO.ChangeDeliverWhen[in, UserExecPrivate.ExecDeliverWhen]; [] _ in.SetEcho[out]; in.RemoveData[key: $Count]; UserExec.ReleaseStreams[exec: exec]; }; RETURN[FALSE]; }; <> RegisterCommand: PUBLIC PROC [name: ROPE, proc: UserExec.CommandProc, briefDoc, doc: ROPE _ NIL, clientData: REF ANY _ NIL] = { Register[name: name, proc: proc, briefDoc: briefDoc, doc: doc, clientData: clientData]; }; RegisterTransformation: PUBLIC PROC [name: ROPE, proc: UserExec.TransformProc, briefDoc, doc: ROPE _ NIL, clientData: REF ANY _ NIL]= { Register[name: name, transformProc: proc, briefDoc: briefDoc, doc: doc, clientData: clientData]; }; RegisterMethod: PUBLIC PROC [name: ROPE, proc: UserExec.MethodProc, doc: ROPE _ NIL, clientData: REF ANY _ NIL] = { r: REF UserExecPrivate.MethodRecord _ NIL; IF name # NIL THEN FOR lst: LIST OF REF UserExecPrivate.MethodRecord _ UserExecPrivate.methodList, lst.rest UNTIL lst = NIL DO IF Rope.Equal[lst.first.name, name] THEN {r _ lst.first; EXIT} ENDLOOP; IF r = NIL THEN TRUSTED { -- Loophole for polymorphism r _ UserExecPrivate.Zone.NEW[UserExecPrivate.MethodRecord _ []]; UserExecPrivate.methodList _ LOOPHOLE[List.Nconc1[LOOPHOLE[UserExecPrivate.methodList, LIST OF REF ANY], r]]; }; -- important that methods be executed in order defined r^ _ [proc: proc, name: name, doc: doc, clientData: clientData]; }; Register: PUBLIC PROC [ name: ROPE _ NIL, proc: UserExec.CommandProc _ NIL, oldStyleProc: UNSAFE PROCEDURE _ NIL, transformProc: UserExec.TransformProc _ NIL, briefDoc, doc: ROPE _ NIL, nameOfBCD: ROPE _ NIL, clientData: REF ANY _ NIL, fromCatalogue: BOOL _ FALSE ] = { <> <> <> <> <> <<};>> i: INT _ Rope.Find[s1: name, s2: "."]; r: REF UserExecPrivate.CommandRecord; commanderProc: Commander.CommandProc; IF i # -1 THEN name _ Rope.Substr[name, 0, i]; -- old style registration was to register mumble.~ commanderProc _ Commander.Lookup[key: name].proc; Commander.Register[key: name, proc: IF commanderProc = NIL THEN dummyCommanderProc ELSE commanderProc, doc: briefDoc]; -- if happen to be registering a command with same name as one already registered in commander, leave the commander one alone so when run from command tool, it will continue to work the same as before r _ GetRegistrationData[name]; IF r = NIL THEN { r _ UserExecPrivate.Zone.NEW[UserExecPrivate.CommandRecord _ []]; RegisterData[name, r]; } ELSE IF (r.commandProc # NIL OR r.oldStyleProc # NIL) AND nameOfBCD # NIL THEN { -- command already registered with procedure is being reregistered indirectly with nameOfBCD. In this case, user should have to explicitly run the bcd. IF doc # NIL THEN r.documentation _ doc; RETURN; }; r^ _ [name: name, commandProc: proc, transformProc: transformProc, briefDocumentation: IF briefDoc = NIL THEN doc ELSE briefDoc, documentation: IF doc = NIL THEN briefDoc ELSE doc, oldStyleProc: oldStyleProc, nameOfBCD: nameOfBCD, clientData: clientData, fromCatalogue: fromCatalogue]; <<{>> <> < NULL;>> <> <<};>> <> <<};>> }; -- Register registrationTable: SymTab.Ref _ SymTab.Create[30, FALSE]; GetRegistrationData: PUBLIC PROC [name: ROPE] RETURNS[REF UserExecPrivate.CommandRecord] = { RETURN[NARROW[SymTab.Fetch[registrationTable, name].val, REF UserExecPrivate.CommandRecord]]; }; RegisterData: PUBLIC PROC [command: ROPE, val: REF ANY] = { [] _ SymTab.Store[x: registrationTable, key: command, val: val]; }; <> LookupCommand: PUBLIC PROC [name: ROPE, event: UserExec.HistoryEvent, exec: UserExec.ExecHandle _ NIL] RETURNS[fullName: ROPE] = { command: ROPE = name; out: STREAM; matches: LIST OF ROPE; matches _ LookupCommands[name, event, exec]; IF matches # NIL THEN TRUSTED { -- LOOPHOLE for polymorphism ENABLE UNWIND => IF out # NIL THEN UserExec.ReleaseStreams[exec]; IF matches.rest = NIL THEN RETURN[matches.first] ; out _ UserExec.AcquireStreams[exec].out; out.PutF["*n%g is ambiguous. It matches with:\n", rope[name]]; FOR l: LIST OF ROPE _ LOOPHOLE[List.Sort[LOOPHOLE[matches]]], l.rest UNTIL l = NIL DO [] _ UserExecPrivate.PrintCommand[name: l.first, event: event, exec: exec]; ENDLOOP; UserExec.ReleaseStreams[exec]; RETURN[NIL]; }; }; LookupCommands: PROC [command: ROPE, event: UserExec.HistoryEvent, exec: UserExec.ExecHandle _ NIL] RETURNS[val: LIST OF ROPE _ NIL] = { r: ROPE; search: PROC[name: ROPE, proc: Commander.CommandProc, doc: ROPE] RETURNS[stop: BOOL] = { IF Rope.Equal[s1: command, s2: name, case: FALSE] THEN { val _ LIST[name]; RETURN[TRUE]; } ELSE IF Rope.Find[s1: name, s2: command, case: FALSE] = 0 THEN val _ CONS[name, val]; RETURN[FALSE]; }; Correct: PROC = { ENABLE UNWIND => NULL; r _ UserExec.GetTheOne[unknown: command, generator: commandGenerator, event: event, exec: exec]; }; IF Commander.Lookup[command].proc # NIL THEN RETURN[LIST[command]] ELSE IF exec = NIL OR UserExecExtras.CorrectionDisabled[event: event] THEN RETURN[NIL]; [] _ Commander.Enumerate[search]; IF val # NIL THEN RETURN; Correct[]; RETURN[IF r # NIL THEN LIST[r] ELSE NIL]; }; <> CallCommanderProc: PROC[command: ROPE, event: UserExec.HistoryEvent, exec: UserExec.ExecHandle] RETURNS[handled: BOOL] = { commandProc: Commander.CommandProc = Commander.Lookup[command].proc; IF commandProc = NIL THEN RETURN[FALSE]; PushPropsAndCallProc[commandProc, command, event, exec]; RETURN[TRUE]; }; PushPropsAndCallProc: PROC [proc: Commander.CommandProc, command: ROPE, event: UserExec.HistoryEvent, exec: UserExec.ExecHandle] = { in, out: IO.STREAM; commanderHandle: Commander.Handle; inner: PROCEDURE = { proc[commanderHandle]; }; [in, out] _ UserExec.GetStreams[exec]; commanderHandle _ NEW[Commander.CommandObject _ [ in: in, out: out, err: out, commandLine: event.commandLine, command: command, propertyList: NIL ]]; CommandProcOps.PutProperty[handle: commanderHandle, key: $UserExec, val: exec, thisEventOnly: TRUE]; CommandProcOps.PutProperty[handle: commanderHandle, key: $HistoryEvent, val: event, thisEventOnly: TRUE]; ProcessProps.AddPropList[ LIST[List.DotCons[$CommanderHandle, commanderHandle]], inner]; }; dummyCommanderProc: PUBLIC Commander.CommandProc = { cmd.out.PutRope["This command can only be executed in a Work Area."]; }; LookupEnumerate: PROC [self: Generator.Handle] = { proc: PROC[name: ROPE, proc: Commander.CommandProc, doc: ROPE] RETURNS[stop: BOOL] = { RETURN[Generator.Produce[self, name]]; -- somebody terminated the generator. }; [] _ Commander.Enumerate[proc]; }; commandGenerator: PUBLIC Spell.SpellingGenerator _ Spell.GeneratorFromEnumerator[enumerator: LookupEnumerate, clientData: NIL]; <> GetRegisteredCommands: UserProfile.ProfileChangedProc = { GetCommands: PROC [name: ROPE, fromCatalogue: BOOL] = { GetRope: PROC RETURNS [ROPE] = { whiteSpace: IO.BreakProc = { RETURN[IF char = CR THEN break ELSE IO.WhiteSpace[char]] }; ref: REF ANY _ IO.GetRefAny[source ! ANY => GOTO Exit]; IF ref = NIL THEN RETURN[NIL]; IO.SkipOver[source, whiteSpace]; -- skips spaces, tabs, etc., but not CR. WITH ref SELECT FROM a: ATOM => RETURN[Atom.GetPName[a]]; r: ROPE => RETURN[r]; ENDCASE => ERROR; -- ERROR TO ERROR LOG. EXITS Exit => RETURN[NIL] }; source: STREAM _ NIL; IF name # NIL THEN { IF Rope.Find[name, "\n"] = -1 THEN -- r is name of file source _ FileIO.Open[fileName: name, accessOptions: read, createOptions: oldOnly ! FileIO.OpenFailed => IF why = fileNotFound THEN CONTINUE] ELSE source _ IO.RIS[name]; }; IF source # NIL THEN { source _ IO.CreateFilterCommentsStream[source]; WHILE NOT source.EndOf[] DO name, nameOfBCD, documentation: ROPE _ NIL; name _ GetRope[]; IF name = NIL THEN EXIT; IF source.PeekChar[] # CR THEN documentation _ GetRope[]; IF source.PeekChar[] # CR THEN nameOfBCD _ GetRope[]; IF nameOfBCD = NIL THEN nameOfBCD _ name; IF Rope.Find[s1: nameOfBCD, s2: "."] = -1 THEN nameOfBCD _ Rope.Concat[nameOfBCD, ".bcd"]; Register[name: name, briefDoc: documentation, doc: documentation, nameOfBCD: nameOfBCD, fromCatalogue: fromCatalogue]; ENDLOOP; IO.Close[source]; }; }; GetCommands["RegisteredCommands.Catalogue", TRUE]; GetCommands[UserProfile.Token["RegisteredCommands"], FALSE]; }; UserProfile.CallWhenProfileChanges[GetRegisteredCommands]; END. -- of UserExecRegCmdsImpl <<>> <<>> <> <> <> <> <> <> <> <> <> <> <> <<>> <<>> <<>> <<>> <<>> <> <> <> <> <<>>