CLIte/clite/shell.js
2023-11-26 19:54:37 +10:00

383 lines
7.8 KiB
JavaScript

clite.commands.data = function() {
// insert commands below this line
clite.commands.load('sh',function(args,env,io) {
var stdio = io.include('stdio');
var stdlib = io.include('stdlib');
var term = io.include('term');
var active_pid = 0;
var prompt = '$ ';
var macro = {
clear:function(args,io) {
term.clear();
},
cd:function(args,io) {
var dir = env.HOME;
if (args.length > 1) {
dir = stdlib.resolvePath(args[1]);
}
var fd = stdio.open(dir,false);
if (!fd) {
stdio.write(io.stderr,'invalid directory: '+dir);
return;
}
if (!fd.node.data.isdir) {
stdio.write(io.stderr,'invalid directory: '+dir);
return;
}
stdio.close(fd);
env.PWD = dir;
},
pwd:function(args,io) {
stdio.write(io.stdout,env.PWD);
},
echo:function(args,io) {
args.shift();
var txt = args.join(' ');
stdio.write(io.stdout,txt);
},
which:function(args,io) {
if (args.length <2)
return;
if (typeof macro[args[1]] != 'undefined') {
return;
}else{
var path = resolvePATH(args[1]);
if (!path)
return;
stdio.write(io.stdout,args[1]+' is '+path);
}
},
type:function(args,io) {
if (args.length <2)
return;
if (typeof macro[args[1]] != 'undefined') {
stdio.write(io.stdout,args[1]+' is a shell builtin');
}else{
var path = resolvePATH(args[1]);
if (!path)
return;
stdio.write(io.stdout,args[1]+' is '+path);
}
},
whoami:function(args,io) {
stdio.write(io.stdout,env.USER);
},
alias:function(args,io) {
stdio.write(io.stdout,'unimplemented');
},
export:function(args,io) {
if (args.length == 1) {
Object.keys(env).forEach(function(key) {
stdio.write(io.stdout,key+'='+env[key]);
});
return;
}
var parts = args[1].split('=');
if (parts.length != 2)
return;
env[parts[0]] = parts[1];
}
};
var history = {
data:[],
current:'',
index:0,
add:function(txt) {
if (txt.length < 1)
return;
if (history.data.length > 0 && history.data[history.data.length-1] == txt)
return;
history.data.push(txt);
history.index = history.data.length;
},
up:function() {
if (history.index == 0)
return;
history.index--;
term.ttyctrl('iset',history.getCurrent());
},
down:function() {
if (history.index == history.data.length)
return;
history.index++;
term.ttyctrl('iset',history.getCurrent());
},
setCurrent:function(txt) {
history.current = txt;
},
getCurrent:function() {
if (history.index == history.data.length)
return history.current;
return history.data[history.index];
},
resetCurrent:function() {
history.current = '';
history.index = history.data.length;
}
}
function active_exit(pid,v) {
if (pid != active_pid)
return;
inputRead();
}
function resolvePATH(txt) {
if (typeof env.PATH === 'undefined')
env.PATH = '/bin';
var paths = env.PATH.split(':');
var path = null;
paths.forEach(function(p) {
if (path)
return;
var rp = stdlib.resolvePath(txt,p);
var fd = stdio.open(rp,false);
if (fd) {
stdio.close(fd);
path = rp;
}
});
return path;
}
function preprocess(txt) {
var parts = txt.split('$');
var pptxt = '';
if (parts.length == 1)
return txt;
parts.forEach(function(p,i) {
if (i > 0) {
var b = p;
var e = '';
if (b[0] == '{') {
var pi = b.indexOf('}');
e = b.substring(pi+1);
b = b.substring(1,pi);
}else{
var pi = b.indexOf(' ');
if (pi > 0) {
e = b.substring(pi);
b = b.substring(0,pi);
}
}
if (typeof env[b] === 'string') {
b = env[b];
}else{
b = '';
}
pptxt += b+e;
return;
}
pptxt += p;
});
txt = pptxt;
parts = txt.split('`');
pptxt = '';
if (parts.length == 1)
return txt;
parts.forEach(function(p,i) {
if (i > 0) {
var b = p;
var e = '';
var pi = b.indexOf('`');
if (pi > 0) {
e = b.substring(pi);
b = b.substring(0,pi);
}
// TODO: replace b with the output of the command in b (command substitution)
pptxt = b+e;
return;
}
pptxt += p;
});
return pptxt;
}
function tabfill() {
var c = term.ttyctrl('iget');
var parts = stdlib.strToArgs(c);
var ai = parts.length-1;
if (ai < 0)
return;
var a = parts[ai];
var add = '';
if (ai == 0) {
// test for shell macros/builtins
Object.keys(macro).forEach(function(key) {
if (add == '' && key.substring(0,a.length) == a) {
add = key.substring(a.length);
}
});
if (add == '') {
// test for commands
var pparts = env.PATH.split(':');
pparts.forEach(function(pp) {
if (add != '')
return;
var fd = stdio.open('/bin',false);
var f;
while (add == '' && (f = stdio.read(fd)) != null) {
if (f.substring(0,a.length) == a) {
add = f.substring(a.length)+' ';
}
}
stdio.close(fd);
});
}
}else{
// TODO: path fill
}
if (add == '')
return;
term.ttyctrl('iset',c+add);
}
function parseScript(fd) {
var l;
while ((l = stdio.readLine(fd)) != null) {
if (l[0] == '#')
continue;
run(l,false);
}
}
function run(txt,interactive) {
if (txt.length < 1) {
if (interactive) {
stdio.write(io.stdout,prompt);
inputRead();
}
return;
}
history.add(txt);
history.resetCurrent();
if (interactive)
stdio.write(io.stdout,prompt+txt);
// clear the tty prompt
term.ttyctrl('prompt','');
// split command line into arguments
var args = stdlib.strToArgs(txt);
args.forEach(function(val,i) {
args[i] = preprocess(val);
});
// prepare io (stdin,stdout,stderr)
// TODO: check for io redirects and piping, then setup stdio accordingly
var fio = {
pid:io.pid,
stdin:io.stdin,
stdout:io.stdout,
stderr:io.stderr,
exit:active_exit, // filled in by fork()
include:null, // filled in by fork()
};
// check for a macro
// then either run the macro or expand the program to be executed via PATH
if (typeof macro[args[0]] != 'undefined') {
var r = macro[args[0]](args,fio);
if (interactive)
inputRead();
return;
}
var path = resolvePATH(args[0]);
if (!path) {
stdio.write(io.stderr,'Shell: unknown command: '+args[0]);
if (interactive)
inputRead();
return;
}
function execFunc(env,io) {
var r = stdlib.exec(path,args,env,io);
if (r > 0)
return;
if (r == -1) {
stdlib.write(io.stderr,'Shell: unknown command: '+args[0]);
io.exit(-1);
}
if (r == -2) {
stdio.write(io.stderr,'Shell: error in command:'+args[0]);
io.exit(-1);
}
}
active_pid = stdlib.fork(env,fio,execFunc);
if (active_pid == 0) {
stdio.write(io.stderr,'Shell: internal error');
if (interactive)
inputRead();
}
}
function inputControl(key) {
switch(key) {
case 'Tab':
tabfill();
break;
case 'Down':
case 'ArrowDown':
history.down();
break;
case 'Up':
case 'ArrowUp':
history.up();
break;
default:;
}
}
function input(str) {
if (str[0] == '\1') {
inputControl(str.substring(1));
inputRead();
return;
}
active_pid = 0;
run(str,true);
}
function inputRead() {
// generate the prompt
var p = env.PS1;
if (typeof p === 'undefined')
p = '${USER}:${PWD}'
prompt = preprocess(p);
if (stdlib.getuid() == 0) {
prompt += '# ';
}else{
prompt += '$ ';
}
// set the prompt
term.ttyctrl('prompt',prompt);
// then read from stdin
stdio.read(io.stdin,input);
}
// run a shell script
if (!stdio.isatty(io.stdin)) {
parseScript(io.stdin);
return 0;
// otherwise we have an interactive shell
// so run /etc/shrc and ~/.shrc
}else{
var fd = stdio.open('/etc/shrc',false);
if (fd) {
parseScript(fd);
stdio.close(fd);
}
fd = stdio.open(env.HOME+'/.shrc',false);
if (fd) {
parseScript(fd);
stdio.close(fd);
}
}
stdlib.waitall(inputRead);
return null;
});
}