mirror of
https://codeberg.org/TicklishHoneyBee/CLIte.git
synced 2026-03-11 09:04:37 +00:00
556 lines
11 KiB
JavaScript
556 lines
11 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 clite = io.include('clite');
|
|
var has_exited = false;
|
|
var macro = {
|
|
clear:function(args,io) {
|
|
term.clear();
|
|
},
|
|
cd:function(args,io) {
|
|
var dir = env.HOME;
|
|
if (args.length > 1)
|
|
dir = clite.resolvePath(args[1]);
|
|
var fd = stdio.open(dir,stdio.flags.O_SEARCH|stdio.flags.O_DIRECTORY);
|
|
if (!fd) {
|
|
stdio.fprintf(io.stderr,'invalid directory: %s\n',dir);
|
|
return;
|
|
}
|
|
stdio.close(fd);
|
|
env.PWD = dir;
|
|
},
|
|
pwd:function(args,io) {
|
|
stdio.fprintf(io.stdout,'%s\n',env.PWD);
|
|
},
|
|
echo:function(args,io) {
|
|
args.shift();
|
|
var txt = args.join(' ')+'\n';
|
|
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.fprintf(io.stdout,'%s is %s\n',args[1],path);
|
|
}
|
|
},
|
|
type:function(args,io) {
|
|
if (args.length <2)
|
|
return;
|
|
if (typeof macro[args[1]] != 'undefined') {
|
|
stdio.fprintf(io.stdout,'%s is a shell builtin\n',args[1]);
|
|
}else{
|
|
var path = resolvePATH(args[1]);
|
|
if (!path)
|
|
return;
|
|
stdio.fprintf(io.stdout,'%s is %s\n',args[1],path);
|
|
}
|
|
},
|
|
whoami:function(args,io) {
|
|
stdio.fprintf(io.stdout,'%s\n',env.USER);
|
|
},
|
|
alias:function(args,io) {
|
|
stdio.fprintf(io.stdout,'unimplemented\n');
|
|
},
|
|
export:function(args,io) {
|
|
if (args.length == 1) {
|
|
Object.keys(env).forEach(function(key) {
|
|
stdio.fprintf(io.stdout,'%s=%s\n',key,env[key]);
|
|
});
|
|
return;
|
|
}
|
|
var parts = args[1].split('=');
|
|
if (parts.length != 2)
|
|
return;
|
|
env[parts[0]] = parts[1];
|
|
},
|
|
exit:function(args,io) {
|
|
has_exited = true;
|
|
var ev = 0;
|
|
if (args.length > 1)
|
|
ev = parseInt(args[1]);
|
|
io.exit(ev);
|
|
}
|
|
};
|
|
|
|
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 < 1)
|
|
return null;
|
|
history.index--;
|
|
return history.getCurrent();
|
|
},
|
|
down:function() {
|
|
if (history.index >= history.data.length)
|
|
return null;
|
|
history.index++;
|
|
return 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;
|
|
},
|
|
clear:function() {
|
|
history.data = [];
|
|
history.current = '';
|
|
history.index = 0;
|
|
}
|
|
};
|
|
|
|
var parser = {
|
|
files:[],
|
|
callback:null,
|
|
queue:function(file) {
|
|
parser.files.push(file);
|
|
},
|
|
internal:{
|
|
lines:[],
|
|
parseFile:function(f) {
|
|
var fn = clite.resolvePath(f);
|
|
if (fn == null) {
|
|
parser.run();
|
|
return;
|
|
}
|
|
var st = stdio.stat(fn);
|
|
if (st == null || st.type != stdio.types.FT_SCRIPT) {
|
|
parser.run();
|
|
return;
|
|
}
|
|
var fd = stdio.open(fn,stdio.flags.O_RDONLY,parser.internal.prepFile);
|
|
if (!fd)
|
|
parser.run();
|
|
},
|
|
prepFile:function(fd) {
|
|
var data = stdio.readAll(fd);
|
|
stdio.close(fd);
|
|
if (!data) {
|
|
parser.run();
|
|
return;
|
|
}
|
|
parser.internal.lines = data.split('\n');
|
|
if (parser.internal.lines.length < 1) {
|
|
parser.run();
|
|
return;
|
|
}
|
|
parser.internal.parseLine();
|
|
},
|
|
parseLine:function() {
|
|
var l = parser.internal.lines.shift();
|
|
if (l == null) {
|
|
parser.run();
|
|
return;
|
|
}
|
|
if (l[0] == '#') {
|
|
parser.internal.parseLine();
|
|
return;
|
|
}
|
|
run(l,parser.internal.parseLine);
|
|
}
|
|
},
|
|
exit:function() {
|
|
if (typeof parser.callback === 'function') {
|
|
try{
|
|
parser.callback();
|
|
} catch(err) {
|
|
stdio.write(io.stderr,'Shell: internal error in parser\n');
|
|
io.exit(1);
|
|
return;
|
|
}
|
|
}
|
|
},
|
|
run:function(cb) {
|
|
if (typeof cb === 'function')
|
|
parser.callback = cb;
|
|
var f = parser.files.shift();
|
|
if (!f) {
|
|
parser.exit();
|
|
return;
|
|
}
|
|
parser.internal.parseFile(f);
|
|
}
|
|
};
|
|
|
|
function help() {
|
|
stdio.write(io.stdout,`
|
|
sh - command interpreter (shell)
|
|
Usage: sh [OPTION] [FILE]
|
|
|
|
Options:
|
|
-? Print this help information
|
|
|
|
`);
|
|
}
|
|
|
|
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 = clite.resolvePath(txt,p);
|
|
var st = stdio.stat(rp);//open(rp,stdio.flags.O_RDONLY|stdio.flags.O_SYNC);
|
|
if (st) {
|
|
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(c) {
|
|
var parts = clite.strToArgs(c);
|
|
var ai = parts.length-1;
|
|
if (ai < 0)
|
|
return null;
|
|
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',stdio.flags.O_SEARCH|stdio.flags.O_DIRECTORY);
|
|
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 null;
|
|
stdio.write(io.stdout,'\b'+add);
|
|
return c+add;
|
|
}
|
|
|
|
function run(txt,cb) {
|
|
if (txt.length < 1) {
|
|
if (typeof cb === 'function')
|
|
cb();
|
|
return;
|
|
}
|
|
history.add(txt);
|
|
history.resetCurrent();
|
|
// split command line into arguments
|
|
var args = clite.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:null, // 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') {
|
|
fio.exit = io.exit;
|
|
var r = macro[args[0]](args,fio);
|
|
if (typeof cb === 'function')
|
|
cb();
|
|
return;
|
|
}
|
|
|
|
var path = resolvePATH(args[0]);
|
|
if (!path) {
|
|
stdio.fprintf(io.stderr,'Shell: unknown command: %s\n',args[0]);
|
|
if (typeof cb === 'function')
|
|
cb();
|
|
return;
|
|
}
|
|
|
|
function execFunc(env,io) {
|
|
var r = stdlib.exec(path,args,env,io);
|
|
if (r > 0)
|
|
return;
|
|
|
|
if (r == -1) {
|
|
stdlib.fprintf(io.stderr,'Shell: unknown command: %s\n',args[0]);
|
|
io.exit(-1);
|
|
return;
|
|
}
|
|
|
|
if (r == -2) {
|
|
stdio.fprintf(io.stderr,'Shell: error in command: %s\n',args[0]);
|
|
io.exit(-1);
|
|
return;
|
|
}
|
|
|
|
if (r == -3 || r == -4) {
|
|
stdio.fprintf(io.stderr,'Shell: not an executable file: %s\n',args[0]);
|
|
io.exit(-3);
|
|
return;
|
|
}
|
|
|
|
if (r == -5) {
|
|
stdio.fprintf(io.stderr,'Shell: invalid parser for file: %s\n',args[0]);
|
|
io.exit(-3);
|
|
return;
|
|
}
|
|
|
|
if (r == -6) {
|
|
stdio.fprintf(io.stderr,'Shell: no valid parser for file: %s\n',args[0]);
|
|
io.exit(-3);
|
|
return;
|
|
}
|
|
|
|
if (r<0) {
|
|
stdio.fprintf(io.stderr,'Shell: could not exec file: %s\n',args[0]);
|
|
io.exit(-3);
|
|
}
|
|
}
|
|
var pid = stdlib.fork(env,fio,execFunc);
|
|
if (pid == 0) {
|
|
stdio.write(io.stderr,'Shell: internal error\n');
|
|
if (typeof cb === 'function')
|
|
cb();
|
|
return;
|
|
}
|
|
|
|
if (typeof cb === 'function')
|
|
stdlib.waitpid(pid,cb);
|
|
}
|
|
|
|
function writePrompt() {
|
|
// generate the prompt
|
|
var p = env.PS1;
|
|
if (typeof p === 'undefined')
|
|
p = '${USER}:${PWD}'
|
|
|
|
var prompt = preprocess(p);
|
|
if (stdlib.getuid() == 0) {
|
|
prompt += '# ';
|
|
}else{
|
|
prompt += '$ ';
|
|
}
|
|
|
|
stdio.write(io.stdout,prompt);
|
|
}
|
|
|
|
function inputRead() {
|
|
if (has_exited)
|
|
return;
|
|
|
|
var c = '';
|
|
var cc = 0;
|
|
|
|
function inputProcess(str) {
|
|
switch (str) {
|
|
case '\t': // tab
|
|
case 9:
|
|
var ct = tabfill(c);
|
|
if (ct != null) {
|
|
c = ct;
|
|
cc = c.length;
|
|
}
|
|
break;
|
|
case '\n': // enter
|
|
term.ttyctrl('raw',false);
|
|
run(c,inputRead);
|
|
c = '';
|
|
cc = 0;
|
|
return;
|
|
break;
|
|
case '\b':
|
|
case 8:
|
|
if (c.length > 0) {
|
|
cc--;
|
|
c = c.substring(0,cc);
|
|
stdio.write(io.stdout,'\b \b');
|
|
}
|
|
break;
|
|
case -3: // down arrow
|
|
var ct = history.down();
|
|
if (ct != null) {
|
|
for (var i=0; i<c.length; i++) {
|
|
stdio.write(io.stdout,'\b \b');
|
|
}
|
|
c = ct;
|
|
cc = c.length;
|
|
stdio.write(io.stdout,c);
|
|
}
|
|
break;
|
|
case -4: // up arrow
|
|
var ct = history.up();
|
|
if (ct != null) {
|
|
for (var i=0; i<c.length; i++) {
|
|
stdio.write(io.stdout,'\b \b');
|
|
}
|
|
c = ct;
|
|
cc = c.length;
|
|
stdio.write(io.stdout,c);
|
|
}
|
|
break;
|
|
default:
|
|
if (typeof str === 'string') {
|
|
c += str;
|
|
cc++;
|
|
history.setCurrent(c);
|
|
}
|
|
}
|
|
stdio.read(io.stdin,inputProcess);
|
|
}
|
|
|
|
// write the prompt
|
|
writePrompt();
|
|
// set tty to raw mode
|
|
term.ttyctrl('raw',true);
|
|
// then read from stdin
|
|
stdio.read(io.stdin,inputProcess);
|
|
}
|
|
|
|
function main(args) {
|
|
var f = null;
|
|
var s = false;
|
|
for (var i=1; i<args.length; i++) {
|
|
if (args[i][0] == '-') {
|
|
for (var j=1; j<args[i].length; j++) {
|
|
switch (args[i][j]) {
|
|
case '?':
|
|
help();
|
|
return 0;
|
|
break;
|
|
case 's':
|
|
s = true;
|
|
break;
|
|
default:
|
|
stdio.fprintf(io.stderr,'unknown argument -%c\n',args[i][j]);
|
|
}
|
|
}
|
|
}else if (f == null) {
|
|
f = clite.resolvePath(args[i]);
|
|
}else{
|
|
stdio.fprintf(io.stderr,'unknown argument - %s\n',args[i]);
|
|
}
|
|
}
|
|
|
|
// run a shell script if one is specified
|
|
if (f != null) {
|
|
parser.queue(f);
|
|
parser.run(function() {
|
|
io.exit(0);
|
|
});
|
|
// run a shell script from stdin
|
|
}else if (!stdio.isatty(io.stdin)) {
|
|
if (!s)
|
|
return 0;
|
|
// TODO: parser stdin support
|
|
return 1;
|
|
// otherwise we have an interactive shell
|
|
// so run /etc/shrc and ~/.shrc
|
|
}else{
|
|
parser.queue('/etc/shrc');
|
|
parser.queue(env.HOME+'/.shrc');
|
|
parser.run(function() {
|
|
history.clear();
|
|
inputRead();
|
|
});
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
return main(args);
|
|
});
|
|
|
|
}
|