From b34d9590cbc269acb257581b347c825d624576c2 Mon Sep 17 00:00:00 2001 From: Lisa Milne Date: Sat, 25 Nov 2023 20:43:51 +1000 Subject: [PATCH] move shell to /bin/sh (and shell.js) --- clite/core.js | 629 +++++++++++++------------------------------------ clite/shell.js | 269 +++++++++++++++++++++ readme.txt | 3 + 3 files changed, 440 insertions(+), 461 deletions(-) create mode 100644 clite/shell.js diff --git a/clite/core.js b/clite/core.js index 4a9dc50..19af49e 100644 --- a/clite/core.js +++ b/clite/core.js @@ -34,7 +34,7 @@ var clite = { file:function(name,callback) { if (window.location.protocol == 'file:') { // this is a dirty hack and I hate it, just let me open a file: path! clite.term.setCustom({type:'file',callback:callback}); - clite.shell.writeLine('open file: '+name); + //clite.shell.writeLine('open file: '+name); return; } @@ -79,7 +79,7 @@ var clite = { this.state.isinit = true; this.core.execSafeAsync(function() { - clite.term.clear(false); + clite.term.clear(); var vfsapi = null; function defaultDevices() { @@ -196,8 +196,9 @@ var clite = { buffer:[], input:function(str) { if (n.data.content.receive.callback != null) { + var fn = n.data.content.receive.callback; clite.core.execSafeAsync(function() { - n.data.content.receive.callback(str); + fn(str); }); n.data.content.receive.callback = null; return; @@ -212,6 +213,8 @@ var clite = { return true; } n.data.content.receive.callback = cb; + clite.term.genForm(); + clite.term.data.field.focus(); return true; }, write:clite.term.writeLine @@ -350,6 +353,10 @@ readme.txt:/usr/clite/readme:0:0:-rw-r--r--`; clite.core.load.script('clite/commands.js',init3); } function init3(data) { + clite.core.execSafe(clite.commands.data); + clite.core.load.script('clite/shell.js',init4); + } + function init4(data) { clite.core.execSafe(clite.commands.data); clite.commands = null; clite.log.write('Bringing up the process manager'); @@ -380,22 +387,56 @@ readme.txt:/usr/clite/readme:0:0:-rw-r--r--`; // read in /usr/share/introduction and write to shell if (window.location.protocol == 'file:') { clite.user.genGuest(); - clite.shell.writeLine(clite_text); + //clite.shell.writeLine(clite_text); }else{ - var fd = clite.io.open('/usr/share/introduction',function(fd) { - clite.user.genGuest(); - clite.shell.writeLine(clite_text); - if (!fd) - return; - var d = clite.io.readAll(fd); - if (d != null) - clite.shell.writeLine(d); - clite.io.close(fd); + //var fd = clite.io.open('/usr/share/introduction',function(fd) { + //clite.user.genGuest(); + //clite.shell.writeLine(clite_text); + //if (!fd) + //return; + //var d = clite.io.readAll(fd); + //if (d != null) + //clite.shell.writeLine(d); + //clite.io.close(fd); - }); + //}); } + // create the login shell + // should really have init/getty exec the shell with the env data etc + var env = clite.user.getEnv(); + var io = { + stdin:clite.io.open('/dev/tty',false), + stdout:clite.io.open('/dev/tty',false), + stderr:clite.io.open('/dev/tty',false), + exit:null, + include:function(name) { + switch (name) { + case 'stdlib': + return clite.lib.api; + break; + case 'stdio': + return clite.io; + break; + case 'term': + return clite.term.api; + break; + default: + return null; + break; + } + return null; + }, + istty:{ + stdin:true, // false if stdin (read) is not clite.shell.readLine + stdout:true // false if stdout (write) is not clite.term.writeLine + } + }; + clite.lib.fork(env,io,init5); } } + function init5(env,io) { + clite.lib.exec('/bin/sh',['sh'],env,io); + } init1(); @@ -577,9 +618,6 @@ clite.user = { vfs.mkLink('/usr/home/guest/web','/usr/share/site'); setLogin(1); - - // should really have init/getty exec the shell with the env data etc - clite.shell.init(); } clite.user.getEnv = function() { return structuredClone(udata.env); @@ -900,6 +938,13 @@ clite.io = { var fd = getFileDes(path,true,false); return clite.io.fchmod(fd,mode); } + clite.io.isatty = function(fd) { + return fd.node.data.istty; + } + clite.io.fprintf = function(fd,fmt) { + // TODO: actually support formatting + return clite.io.write(fd,fmt); + } }, // 0: unknown // 1: text file @@ -936,7 +981,9 @@ clite.io = { stat:null, fstat:null, chmod:null, - fchmod:null + fchmod:null, + isatty:null, + fprintf:null }; clite.vfs = { @@ -1174,54 +1221,35 @@ clite.term = { type:'text', form:null, field:null, - handler:null + handler:null, + prompt:'' }, events:{ keydown:function(e) { - return clite.term.events.keypress(e); - }, - keypress:function(e) { if (e.key == 'Tab') { - clite.shell.tabfill(); clite.term.events.refocus(); return false; } return true; }, keyup:function(e) { - //if (e.key.length > 1 && e.key != 'Spacebar' && clite.term.data.handler != null) { - //if (e.key == 'Enter') { - //clite.term.data.handler(e.target.value); - //}else{ - //clite.term.data.handler(e.key); - //} - //} - try { - if (e.key == 'Down' || e.key == 'ArrowDown') { - clite.shell.history.down(); - return false; + if (e.key.length > 1 && e.key != 'Spacebar' && clite.term.data.handler != null) { + if (e.key == 'Enter') { + clite.term.data.handler(e.target.value); + }else{ + clite.term.data.handler('\1'+e.key); } - if (e.key == 'Up' || e.key == 'ArrowUp') { - clite.shell.history.up(); - return false; - } - if (e.key == 'Tab') { - return false; - } - var t = clite.term.data.field.value; - if (e.key != 'Enter') { - clite.shell.history.setCurrent(t); - return true; - } - clite.shell.run(t); - return false; - } catch(err) {return false;} + } + return false; }, refocus:function(e) { - try { - clite.term.genForm(); - clite.term.data.field.focus(); - } catch(err) {} + clite.core.execSafeAsync(function() { + try{ + clite.term.data.field.focus() + } catch(err) { + clite.term.genForm(); + } + }); } }, tty:{ @@ -1242,10 +1270,8 @@ clite.term = { return clite.term.tty.data.data.onkeypress(e); } }, - clear:function(form) { + clear:function() { document.getElementById('terminal').innerHTML = ''; - if (form) - clite.term.genForm(); }, preventInput:function() { return; @@ -1276,11 +1302,11 @@ clite.term = { f.id = 'form'; f.innerHTML = ''; var l = document.createElement('label'); - l.innerHTML = clite.shell.prompt.getHTML(); + l.innerHTML = clite.lib.htmlEncode(clite.term.data.prompt); f.appendChild(l); var i = document.createElement('input'); i.type = clite.term.getType(); - i.value = clite.shell.history.getCurrent(); + //i.value = clite.shell.history.getCurrent(); f.appendChild(i); clite.term.data.form = f; clite.term.data.field = i; @@ -1288,19 +1314,8 @@ clite.term = { if (!t) return; t.appendChild(f); - if (clite.term.tty.data != null) { - i.onkeydown = clite.term.tty.keydown; - i.onkeypress = clite.term.tty.keypress; - i.onkeyup = clite.term.tty.keyup; - }else if (clite.shell.reading.is) { - i.onkeydown = clite.shell.reading.keydown; - i.onkeypress = clite.shell.reading.keypress; - i.onkeyup = clite.shell.reading.keyup; - }else{ - i.onkeydown = clite.term.events.keydown; - i.onkeypress = clite.term.events.keypress; - i.onkeyup = clite.term.events.keyup; - } + i.onkeydown = clite.term.events.keydown; + i.onkeyup = clite.term.events.keyup; i.onfocusout = clite.term.events.refocus; i.onblur = clite.term.events.refocus; if (i.type == 'file') { // this is only needed due to dirty hacks to read in a file: path @@ -1339,10 +1354,41 @@ clite.term = { return 'text'; }, api:{ - clear:function() { // clears the terminal - clite.term.clear(false); + // clears the terminal + clear:function() { + clite.term.clear(); }, - opentty:function() { // gets an empty element with key events (allows view/less to display content in a clean element) + ttyctrl:function(fn,v) { + switch (fn) { + case 'iget': // returns the current input string - bypassing tty read + try{ + return clite.term.data.field.value; + } catch(err) { + return ''; + } + break; + case 'iset': // sets the current input string + try{ + if (typeof v !== 'string') + v = ''; + clite.term.data.field.value = v; + return true; + } catch(err) { + return false; + } + break; + case 'prompt': // sets the prompt for input + if (typeof v !== 'string') + v = ''; + clite.term.data.prompt = v; + return true; + break; + default: + return false; + } + }, + // gets an empty element with key events (allows view/less to display content in a clean element) + opentty:function() { var tty = { handleKeys:function(cb) { this.data.cb = cb; @@ -1411,384 +1457,6 @@ clite.term = { } }; -clite.shell = { - reading:{ - is:false, - buff:'', - callback:null, - keydown:function(e) { - if (e.key == 'Tab') - return false; - }, - keypress:function(e) { - if (e.key == 'Tab') { - clite.term.events.refocus(); - return false; - } - }, - keyup:function(e) { - if (e.key.length > 1 && e.key != 'Spacebar') { - clite.shell.reading.is = false; - clite.term.preventInput(); - if (e.key == 'Enter') { - clite.shell.reading.callback(clite.shell.reading.buff); - }else{ - clite.shell.reading.callback(e); - } - return false; - } - clite.shell.reading.buff = e.target.value; - } - }, - init:function() { - clite.shell.env = clite.user.getEnv(); - clite.shell.prompt.generate(); - clite.term.events.refocus(); - }, - exit:function(pid,v) { - if (pid > -1) - clite.proc.exit(pid); - if (clite.term.tty.data != null) - clite.term.api.closetty(clite.term.tty.data); - clite.shell.prompt.pop(); - clite.term.events.refocus(); - }, - readLine:function(cb) { - clite.shell.reading.is = true; - clite.shell.reading.buff = ''; - clite.shell.reading.callback = cb; - clite.term.events.refocus(); - }, - writeLine:function(txt) { - clite.term.writeLine(txt); - clite.term.events.refocus(); - }, - resolvePath:function(txt) { - var paths = clite.shell.env.PATH.split(':'); - var path = null; - paths.forEach(function(p) { - if (path) - return; - var rp = clite.lib.resolvePath(txt,p); - var fd = clite.io.open(rp,false); - if (fd) { - clite.io.close(fd); - path = rp; - } - }); - return path; - }, - include:function(name) { - switch (name) { - case 'stdlib': - return clite.lib.api; - break; - case 'stdio': - return clite.io; - break; - case 'term': - return clite.term.api; - break; - default: - return null; - break; - } - return null; - }, - preprocess:function(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 = ''; - var pi = b.indexOf(' '); - if (pi > 0) { - e = b.substring(pi); - b = b.substring(0,pi); - } - if (b[0] == '{') { - var pi = b.indexOf('}'); - b = b.substring(1,pi); - } - if (typeof clite.shell.env[b] === 'string') { - b = clite.shell.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; - }, - run:function(txt) { - if (txt.length < 1) { - clite.shell.writeLine(clite.shell.prompt.get()); - return; - } - clite.shell.history.add(txt); - clite.shell.history.resetCurrent(); - clite.shell.writeLine(clite.shell.prompt.get()+txt); - clite.shell.prompt.push(''); - clite.term.preventInput(); - // split command line into arguments - var args = clite.lib.strToArgs(txt); - args.forEach(function(val,i) { - args[i] = clite.shell.preprocess(val); - }); - // prepare io (stdin,stdout,stderr) - // TODO: check for io redirects and piping, then setup stdio accordingly - var io = { - read:clite.shell.readLine, // stdin - write:clite.term.writeLine, // stdout - error:clite.term.writeLine, // stderr - exit:clite.shell.exit, // equivalent to C's exit(), needed by asynchronous programs - include:clite.shell.include, // include libraries, allows them to be used in a program - istty:{ - stdin:true, // false if stdin (read) is not clite.shell.readLine - stdout:true // false if stdout (write) is not clite.term.writeLine - } - }; - // check for a macro - // then either run the macro or expand the program to be executed via PATH - if (typeof clite.shell.macro[args[0]] != 'undefined') { - var r = clite.shell.macro[args[0]](args,io); - clite.shell.exit(r); - return; - } - - var path = clite.shell.resolvePath(args[0]); - if (!path) { - clite.term.writeLine('Shell: unknown command: '+args[0]); - clite.shell.exit(-1,-1); - return; - } - - function execFunc(env,io) { - var r = clite.lib.exec(path,args,env,io); - if (r == 0) - return; - - if (r == -1) { - clite.term.writeLine('Shell: unknown command: '+args[0]); - clite.shell.exit(-1,-2); - return; - } - - if (r == -2) { - clite.term.writeLine('Shell: error in command:'+args[0]); - clite.shell.exit(-1,-3); - } - } - if (!clite.lib.fork(clite.shell.env,io,execFunc)) { - clite.term.writeLine('Shell: internal error'); - clite.shell.exit(-1,-1); - } - }, - tabfill:function() { - var c = clite.shell.history.getCurrent(); - var parts = clite.lib.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(clite.shell.macro).forEach(function(key) { - if (add == '' && key.substring(0,a.length) == a) { - add = key.substring(a.length); - } - }); - if (add == '') { - // test for commands - var pparts = clite.shell.env.PATH.split(':'); - pparts.forEach(function(pp) { - if (add != '') - return; - var fd = clite.io.open('/bin'); - var f; - while (add == '' && (f = clite.io.read(fd)) != null) { - if (f.name.substring(0,a.length) == a) { - add = f.name.substring(a.length)+' '; - } - } - clite.io.close(fd); - }); - } - }else{ - // TODO: path fill - } - if (add == '') - return; - clite.shell.history.setCurrent(c+add); - }, - env:{}, - macro:{ - clear:function(args,io) { - clite.term.clear(); - }, - cd:function(args,io) { - var dir = clite.shell.env.HOME; - if (args.length > 1) { - dir = clite.lib.resolvePath(args[1]); - } - var fd = clite.io.open(dir,false); - if (!fd) { - io.error('invalid directory: '+dir); - return; - } - if (!fd.node.data.isdir) { - io.error('invalid directory: '+dir); - return; - } - clite.io.close(fd); - clite.shell.env.PWD = dir; - clite.shell.prompt.generate(); - }, - pwd:function(args,io) { - io.write(clite.shell.env.PWD); - }, - echo:function(args,io) { - args.shift(); - var txt = args.join(' '); - io.write(txt); - }, - which:function(args,io) { - if (args.length <2) - return; - if (typeof clite.shell.macro[args[1]] != 'undefined') { - return; - }else{ - var path = clite.shell.resolvePath(args[1]); - if (!path) - return; - io.write(args[1]+' is '+path); - } - }, - type:function(args,io) { - if (args.length <2) - return; - if (typeof clite.shell.macro[args[1]] != 'undefined') { - io.write(args[1]+' is a shell builtin'); - }else{ - var path = clite.shell.resolvePath(args[1]); - if (!path) - return; - io.write(args[1]+' is '+path); - } - }, - whoami:function(args,io) { - io.write(clite.shell.env.USER); - }, - alias:function(args,io) { - io.write('unimplemented'); - }, - export:function(args,io) { - if (args.length == 1) { - Object.keys(clite.shell.env).forEach(function(key) { - io.write(key+'='+clite.shell.env[key]); - }); - return; - } - var parts = args[1].split('='); - if (parts.length != 2) - return; - clite.shell.env[parts[0]] = parts[1]; - } - }, - prompt:{ - list:[], - data:'# ', - set:function(txt) { - clite.shell.prompt.list = []; - clite.shell.prompt.data = txt; - }, - push:function(txt) { - clite.shell.prompt.list.push(clite.shell.prompt.data); - clite.shell.prompt.data = txt; - }, - pop:function() { - if (clite.shell.prompt.list.length > 0) - clite.shell.prompt.data = clite.shell.prompt.list.pop(); - }, - generate:function() { - var p = clite.shell.env.USER+':'+clite.shell.env.PWD; - if (clite.user.getUID() == 0) { - p += '# '; - }else{ - p += '$ '; - } - clite.shell.prompt.set(p); - }, - get:function() { - return clite.shell.prompt.data; - }, - getHTML:function() { - return clite.lib.htmlEncode(clite.shell.prompt.data); - } - }, - history:{ - data:[], - current:'', - index:0, - add:function(txt) { - if (txt.length < 1) - return; - clite.shell.history.data.push(txt); - clite.shell.history.index = clite.shell.history.data.length; - }, - up:function() { - if (clite.shell.history.index == 0) - return; - clite.shell.history.index--; - clite.term.events.refocus(); - }, - down:function() { - if (clite.shell.history.index == clite.shell.history.data.length) - return; - clite.shell.history.index++; - clite.term.events.refocus(); - }, - setCurrent:function(txt) { - clite.shell.history.current = txt; - }, - getCurrent:function() { - if (clite.shell.history.index == clite.shell.history.data.length) - return clite.shell.history.current; - return clite.shell.history.data[clite.shell.history.index]; - }, - resetCurrent:function() { - clite.shell.history.current = ''; - clite.shell.history.index = clite.shell.history.data.length; - } - } -}; - clite.lib = { // makes text html safe htmlEncode:function(txt) { @@ -1812,14 +1480,16 @@ clite.lib = { resolvePath:function(txt,base) { if (txt == '/') return txt; - if (txt[0] == '~') - txt = clite.shell.env.HOME+txt.substring(1); + var env = clite.user.getEnv(); + if (txt[0] == '~') { + txt = env.HOME+txt.substring(1); + } var path = txt; if (txt[0] != '/') { if (typeof base != 'string') - base = clite.shell.env.PWD; + base = env.PWD; if (base[0] != '/') - base = clite.shell.env.PWD+'/'+base; + base = env.PWD+'/'+base; path = base+'/'+txt; } var parts = path.split('/'); @@ -1974,16 +1644,39 @@ clite.lib = { var pid = clite.proc.add(call); if (!pid) return 0; - io.exit = function(v) { - clite.shell.exit(pid,v); + var nio = Object.create(io); + nio.exit = function(v) { + clite.lib.exit(pid,v); + } + nio.include = function(name) { + switch (name) { + case 'stdlib': + return Object.create(clite.lib.api); + break; + case 'stdio': + var stdio = Object.create(clite.io); + stdio.printf = function(fmt) { + // TODO: pass arguments through + return stdio.fprintf(nio.stdout,fmt); + } + return stdio; + break; + case 'term': + return Object.create(clite.term.api); + break; + default: + return null; + break; + } + return null; } clite.core.execSafeAsync(function() { - call(structuredClone(env),io); + call(structuredClone(env),nio); }); return pid; }, exec:function(path,args,env,io) { - var fd = clite.io.open(path); + var fd = clite.io.open(path,false); if (!fd) return -1; @@ -2000,6 +1693,18 @@ clite.lib = { return -2; } return 0; + }, + exit:function(pid,v) { + if (pid > -1) + clite.proc.exit(pid); + if (clite.term.tty.data != null) + clite.term.api.closetty(clite.term.tty.data); + }, + getuid:function() { + return clite.user.getUID(); + }, + getgid:function() { + return clite.user.getGID(); } } clite.lib.api = { // this is passed to programs via include('stdlib') @@ -2009,5 +1714,7 @@ clite.lib.api = { // this is passed to programs via include('stdlib') strToArgs:clite.lib.strToArgs, uname:clite.lib.uname, fork:clite.lib.fork, - exec:clite.lib.exec + exec:clite.lib.exec, + getuid:clite.lib.getuid, + getgid:clite.lib.getgid }; diff --git a/clite/shell.js b/clite/shell.js new file mode 100644 index 0000000..8ac7a72 --- /dev/null +++ b/clite/shell.js @@ -0,0 +1,269 @@ +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 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]; + } + }; + + 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 run(txt) { + if (txt.length < 1) { + stdio.write(io.stdout,prompt); + inputRead(); + return; + } + //clite.shell.history.add(txt); + //clite.shell.history.resetCurrent(); + 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 = { + 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') { + var r = macro[args[0]](args,fio); + inputRead(); + return; + } + + var path = resolvePATH(args[0]); + if (!path) { + stdio.write(io.stderr,'Shell: unknown command: '+args[0]); + 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]); + + if (r == -2) + stdio.write(io.stderr,'Shell: error in command:'+args[0]); + inputRead(); + } + if (!stdlib.fork(env,fio,execFunc)) { + stdio.write(io.stderr,'Shell: internal error'); + inputRead(); + } + } + + function inputControl(key) { + //try { + //if (e.key == 'Down' || e.key == 'ArrowDown') { + //clite.shell.history.down(); + //return false; + //} + //if (e.key == 'Up' || e.key == 'ArrowUp') { + //clite.shell.history.up(); + //return false; + //} + //if (e.key == 'Tab') { + //return false; + //} + //var t = clite.term.data.field.value; + //if (e.key != 'Enter') { + //clite.shell.history.setCurrent(t); + //return true; + //} + //clite.shell.run(t); + //return false; + //} catch(err) {return false;} + } + function input(str) { + if (str[0] == '\1') { + inputControl(str.substring(1)); + inputRead(); + return; + } + + run(str); + } + 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); + } + + // TODO: this check will exec future shell scripts + if (!stdio.isatty(io.stdin)) + return 0; + + inputRead(); + + return null; +}); + +} diff --git a/readme.txt b/readme.txt index 24d2abf..34c5d5a 100644 --- a/readme.txt +++ b/readme.txt @@ -356,6 +356,9 @@ stdio: io.include('stdio') The user has all permissions, the group has read and execute permissions, others have only read permissions. + isattty(fd) + returns true if fd refers to a tty. + term: io.include('term') Provides access to raw terminal and tty functions.