MODULE ShellCommands;
IMPORT Machine, Streams, Pipes, AosModules := Modules, Files, Dates, Strings, Commands;
CONST
MaxString = 256;
TYPE
String = ARRAY MaxString OF CHAR;
CmdAlias = POINTER TO RECORD
alias, cmd, help: String;
next: CmdAlias
END;
CmdParameters = POINTER TO ARRAY OF CHAR;
AliasList = OBJECT
VAR alias: CmdAlias;
PROCEDURE Alias(alias, cmd, help: ARRAY OF CHAR);
VAR a: CmdAlias;
BEGIN {EXCLUSIVE}
a := SELF.alias;
WHILE (a # NIL) & (a.alias # alias) DO
a := a.next
END;
IF a = NIL THEN
NEW(a); a.next := SELF.alias; SELF.alias := a; COPY(alias, a.alias)
END;
COPY(cmd, a.cmd); COPY(help, a.help)
END Alias;
PROCEDURE Find(alias: ARRAY OF CHAR): CmdAlias;
VAR a: CmdAlias;
BEGIN {EXCLUSIVE}
a := SELF.alias;
WHILE (a # NIL) & (a.alias # alias) DO
a := a.next
END;
RETURN a
END Find;
PROCEDURE List(out: Streams.Writer);
VAR a: CmdAlias;
BEGIN {EXCLUSIVE}
a := alias;
WHILE a # NIL DO
out.String(a.alias); out.Char(09X); out.String(a.cmd); out.Ln();
IF a.help # "" THEN
out.Char(09X); out.String(a.help); out.Ln()
END;
a := a.next
END
END List;
PROCEDURE &Init*;
BEGIN
alias := NIL;
Alias("alias", "ShellCommands.Alias", "alias [ [ alias cmd ] help ]");
Alias("del", "ShellCommands.Delete", "del { file }");
Alias("dir", "ShellCommands.Directory", "dir [ pattern ]");
Alias("echo", "ShellCommands.Echo", "echo { par }");
Alias("exit", "ShellCommands.Exit", "exit");
Alias("free", "ShellCommands.Free", "free mod");
Alias("help", "ShellCommands.Help", "help [ alias ]");
Alias("mods", "ShellCommands.Modules", "mods");
Alias("start", "ShellCommands.Start", "start cmd { pars }");
Alias("ver", "ShellCommands.Version", "ver")
END Init;
END AliasList;
Context* = OBJECT (Commands.Context)
VAR
alias: AliasList;
C: ANY;
PROCEDURE &New*(C: ANY; in: Streams.Reader; out, err: Streams.Writer);
BEGIN
Init(in, NIL, out, err, NIL);
SELF.C := C; alias := NIL
END New;
END Context;
Command = OBJECT
VAR
ctx: Context;
cmd: String;
next: Command;
PROCEDURE SetContext(C: ANY; in: Streams.Reader; out, err: Streams.Writer);
VAR a: AliasList;
BEGIN
IF (C # ctx.C) OR (in # ctx.in) OR (out # ctx.out) OR (err # ctx.error) THEN
a := ctx.alias;
NEW(ctx, C, in, out, err);
ctx.alias := a
END
END SetContext;
PROCEDURE &Init*(ctx: Context);
BEGIN
ASSERT(ctx # NIL);
SELF.ctx := ctx; cmd := ""; next := NIL
END Init;
END Command;
PROCEDURE GetPar(par: ANY; VAR p: CmdParameters; VAR w: Streams.Writer);
VAR arg : Streams.StringReader; len : LONGINT;
BEGIN
p := NIL; w := NIL;
IF (par # NIL) & (par IS Commands.Context) & (par(Commands.Context).arg IS Streams.StringReader) THEN
arg := par(Commands.Context).arg (Streams.StringReader);
len := arg.Available();
IF (len > 0) THEN
NEW(p, len);
arg.Bytes(p^, 0, len, len);
w := par(Commands.Context).out;
END;
END
END GetPar;
PROCEDURE GetAliasList(p: Commands.Context): AliasList;
BEGIN
IF (p # NIL) & (p IS Context) THEN
RETURN p(Context).alias
END;
RETURN NIL
END GetAliasList;
PROCEDURE Close(p: Commands.Context);
VAR ctx: Context;
BEGIN
IF (p # NIL) & (p IS Context) THEN
ctx := p(Context);
IF ctx.C IS Pipes.Pipe THEN
ctx.out.Update(); ctx.C(Pipes.Pipe).Close()
END
END
END Close;
PROCEDURE Alias*(context : Commands.Context);
VAR al: AliasList; alias, cmd, help: String;
BEGIN
al := GetAliasList(context);
IF context.arg.GetString(alias) & context.arg.GetString(cmd) THEN
context.out.String(alias); context.out.Char(09X); context.out.String(cmd); context.out.Ln;
IF context.arg.GetString(help) THEN
context.out.Char(09X); context.out.String(help); context.out.Ln()
ELSE help := "";
END;
al.Alias(alias, cmd, help);
ELSE
al.List(context.out)
END;
Close(context);
END Alias;
PROCEDURE Delete*(context : Commands.Context);
VAR filename: Files.FileName; res: LONGINT;
BEGIN
WHILE context.arg.GetString(filename) DO
context.out.String(filename); context.out.Char(09X);
Files.Delete(filename, res);
IF res = Files.Ok THEN
context.out.String("done");
ELSE
context.out.String("error: "); context.out.Int(res, 0)
END;
context.out.Ln;
END;
Close(context);
END Delete;
PROCEDURE Directory*(context : Commands.Context);
VAR
enum: Files.Enumerator;
name: Files.FileName; flags: SET; time, date, size: LONGINT; tdrec: Dates.DateTime; str: ARRAY 32 OF CHAR;
BEGIN
IF ~context.arg.GetString(str) THEN str := "*"; END;
NEW(enum);
enum.Open(str, {Files.EnumSize, Files.EnumTime});
WHILE enum.GetEntry(name, flags, time, date, size) DO
context.out.String(name); context.out.Char(09X); context.out.Int(size, 0); context.out.Char(09X);
tdrec := Dates.OberonToDateTime(date, time);
Strings.TimeToStr(tdrec, str);
context.out.String(str); context.out.Ln()
END;
Close(context);
END Directory;
PROCEDURE Echo*(context : Commands.Context);
VAR in : Streams.Reader;
BEGIN
IF (context.arg.Available() > 0) THEN
in := context.arg;
ELSE
in := context.in;
END;
Streams.Copy (in, context.out);
Close(context);
END Echo;
PROCEDURE Exit*(context : Commands.Context);
VAR ctx: Context;
BEGIN
IF (context # NIL) & (context IS Context) THEN
context.out.Ln(); context.out.String("logout"); context.out.Ln();
ctx := context(Context);
IF ctx.C IS Streams.Connection THEN
ctx.C(Streams.Connection).Close()
END
END;
Close(context);
END Exit;
PROCEDURE Free*(context : Commands.Context);
VAR name: AosModules.Name; msg: String; res: LONGINT;
BEGIN
IF context.arg.GetString(name) THEN
context.out.String(name); context.out.Char(09X);
AosModules.FreeModule(name, res, msg);
IF res = 0 THEN
context.out.String("done")
ELSE
context.out.Int(res, 0); context.out.String(": "); context.out.String(msg)
END;
context.out.Ln;
END;
Close(context);
END Free;
PROCEDURE Help*(context : Commands.Context);
VAR name: String; al: AliasList; a: CmdAlias;
BEGIN
IF ~context.arg.GetString(name) THEN name := "help" END;
al := GetAliasList(context);
a := al.Find(name);
IF a # NIL THEN
context.out.String(a.help);
ELSE
context.out.String(name); context.out.Char(09X); context.out.String("no such alias");
END;
context.out.Ln;
Close(context);
END Help;
PROCEDURE Modules*(context : Commands.Context);
VAR mod: AosModules.Module;
BEGIN
mod := AosModules.root;
WHILE mod # NIL DO
context.out.String(mod.name); context.out.Char(09X);
context.out.Int(mod.refcnt, 0); context.out.Ln();
mod := mod.next
END;
Close(context);
END Modules;
PROCEDURE execute(context : Commands.Context; VAR cmdline: ARRAY OF CHAR; flags: SET; VAR res: LONGINT; VAR msg: ARRAY OF CHAR);
VAR
ctx: Context; R: Streams.StringReader; cmd, prev, cmds: Command; i, j, p, ofs, len: LONGINT; ch, filter: CHAR;
in, pR: Streams.Reader; out, err, pW: Streams.Writer; file: Files.FileName; F, newF: Files.File; fR: Files.Reader;
fW: Files.Writer; pipe: Pipes.Pipe; a: CmdAlias; cmdString : POINTER TO ARRAY OF CHAR;
BEGIN
ctx := context(Context);
len := LEN(cmdline); newF := NIL; cmds := NIL; prev := NIL;
IF ctx.alias = NIL THEN NEW(ctx.alias) END; ofs := 0;
in := ctx.in; out := ctx.out; err := ctx.error; pipe := NIL;
NEW(R, len); R.Set(cmdline); R.SkipWhitespace();
LOOP
NEW(cmd, ctx); R.String(cmd.cmd);
IF prev # NIL THEN
prev.next := cmd;
Streams.OpenReader(pR, pipe.Receive)
ELSE
cmds := cmd; pR := in
END;
prev := cmd;
R.SkipWhitespace(); p := ofs + R.Pos();
IF p < len THEN
i := p; ch := cmdline[i];
WHILE (ch # 0X) & (ch # "|") & (ch # "<") & (ch # ">") DO
INC(i); ch := cmdline[i]
END;
filter := ch
ELSE
i := len; filter := 0X
END;
IF p < i THEN
NEW(cmdString, i-p+2); j := 0;
ch := cmdline[p]; j := 0;
WHILE (ch # 0X) & (p < i) DO
cmdString[j] := ch; INC(j);
INC(p); ch := cmdline[p]
END;
cmdString[j] := " "; INC(j);
cmdString[j] := 0X;
IF (cmd.ctx.arg IS Streams.StringReader) THEN
cmd.ctx.arg(Streams.StringReader).SetRaw(cmdString^, 0, j +1);
END;
ELSE
cmdString := NIL
END;
CASE filter OF
"|": ofs := i+1; R.SetRaw(cmdline, ofs, len-ofs);
NEW(pipe, 1024);
Streams.OpenWriter(pW, pipe.Send);
cmd.SetContext(pipe, pR, pW, err)
|"<": ofs := i+1; R.SetRaw(cmdline, ofs, len-ofs);
R.SkipWhitespace(); R.String(file);
R.SkipWhitespace(); R.Char(ch);
IF ch = "|" THEN
NEW(pipe, 1024);
Streams.OpenWriter(pW, pipe.Send)
ELSE
pipe := NIL
END;
F := Files.Old(file);
IF F # NIL THEN
Files.OpenReader(fR, F, 0);
IF pR # in THEN
res := -1; COPY("invalid command syntax", msg); RETURN
END;
IF pipe # NIL THEN
cmd.SetContext(pipe, fR, pW, err)
ELSE
cmd.SetContext(ctx.C, fR, out, err)
END
ELSE
res := -1; COPY("input file not found", msg); RETURN
END;
IF pipe = NIL THEN
R.SkipWhitespace();
IF R.res # Streams.EOF THEN
res := -1; COPY("invalid command syntax", msg); RETURN
END;
EXIT
END
|">": IF cmdline[i+1] = ">" THEN
ofs := i+2; R.SetRaw(cmdline, ofs, len-ofs);
R.SkipWhitespace(); R.String(file);
F := Files.Old(file);
IF F # NIL THEN
Files.OpenWriter(fW, F, F.Length());
cmd.SetContext(ctx.C, pR, fW, err)
ELSE
res := -1; COPY("ouput file not found", msg); RETURN
END
ELSE
ofs := i+1; R.SetRaw(cmdline, ofs, len-ofs);
R.SkipWhitespace(); R.String(file);
F := Files.New(file);
IF F # NIL THEN
Files.OpenWriter(fW, F, 0);
cmd.SetContext(ctx.C, pR, fW, err);
newF := F
ELSE
res := -1; COPY("ouput file not created", msg); RETURN
END
END;
R.SkipWhitespace();
IF R.res # Streams.EOF THEN
res := -1; COPY("invalid command syntax", msg); RETURN
END;
EXIT
ELSE
cmd.SetContext(ctx.C, pR, out, err); EXIT
END;
R.SkipWhitespace();
IF R.res # Streams.Ok THEN
res := -1; COPY("invalid command syntax", msg); RETURN
END
END;
prev := NIL; cmd := cmds;
WHILE cmd # NIL DO
a := ctx.alias.Find(cmd.cmd);
IF a # NIL THEN COPY(a.cmd, cmd.cmd) END;
IF cmd.next = NIL THEN
Commands.Activate(cmd.cmd, cmd.ctx, flags, res, msg)
ELSE
Commands.Activate(cmd.cmd, cmd.ctx, {}, res, msg)
END;
IF res # 0 THEN RETURN END;
prev := cmd; cmd := cmd.next
END;
IF newF # NIL THEN
prev.ctx.out.Update();
Files.Register(newF)
END
END execute;
PROCEDURE Start*(context : Commands.Context);
VAR msg: String; res: LONGINT; string : POINTER TO ARRAY OF CHAR;
BEGIN
IF (context.arg IS Streams.StringReader) & (context.arg.Available() > 0) THEN
NEW(string, context.arg.Available() +1);
context.arg.Bytes(string^, 0, context.arg.Available(), res);
string[LEN(string)-1] := 0X;
execute(context, string^, {}, res, msg);
IF res # 0 THEN
context.out.Int(res, 0); context.out.String(": "); context.out.String(msg); context.out.Ln()
END
END;
Close(context);
END Start;
PROCEDURE Version*(context : Commands.Context);
BEGIN
context.out.String(Machine.version); context.out.Ln;
Close(context);
END Version;
PROCEDURE Execute*(par: Commands.Context; VAR cmdline: ARRAY OF CHAR; VAR res: LONGINT; VAR msg: ARRAY OF CHAR);
BEGIN
execute(par, cmdline, {Commands.Wait}, res, msg)
END Execute;
END ShellCommands.