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; } } 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 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) inputRead(); 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 (interactive) inputRead(); return; } var path = resolvePATH(args[0]); if (!path) { stdio.fprintf(io.stderr,'Shell: unknown command: %s\n',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.fprintf(io.stderr,'Shell: unknown command: %s\n',args[0]); io.exit(-1); } if (r == -2) { stdio.fprintf(io.stderr,'Shell: error in command: %s\n',args[0]); io.exit(-1); } if (r == -3 || r == -4) { stdio.fprintf(io.stderr,'Shell: not an executable 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 (interactive) inputRead(); return; } // TODO: if we're not interactive, we still want to waitpid if (interactive) stdlib.waitpid(pid,inputRead); } 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,true); 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