shell macros, start of commands (ls,cat)

This commit is contained in:
Lisa Milne 2023-11-07 18:11:05 +10:00
parent 148b1dc133
commit c38dd412e7
4 changed files with 423 additions and 50 deletions

View file

@ -1,9 +1,135 @@
clite.commands.data = function() {
// insert commands below this line
clite.commands.load('ls',function(args) {
clite.log.write('hello world (l)');
clite.shell.writeLine('hello world (s)');
clite.commands.load('help',function(args,env,io) {
io.write('yeah, I should probably implement this');
return 0;
});
clite.commands.load('ls',function(args,env,io) {
var dirs = [];
var long = false;
function help() {
io.write(`
Usage: ls [OPTION] [FILE]
Options:
-? Print this help information
-l Display data in long format
`);
}
function writeNodeData(node) {
if (long) {
io.write(node.perms+'\t'+node.uid+'\t'+node.gid+'\t'+node.name);
}else{
io.write(node.name);
}
}
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 'l':
long = true;
break;
default:
io.error('unknown argument: '+args[i]);
}
}
}else if (args[i][0] == '/') {
dirs.push(args[i]);
}else{
dirs.push(clite.lib.resolvePath(args[i],false));
}
}
if (dirs.length < 1)
dirs.push(env.PWD);
dirs.forEach(function(dir,index) {
if (dirs.length > 1)
io.write(dir+':');
var fd = clite.io.open(dir);
if (!fd) {
io.error('cannot open directory: '+dir);
return;
}
if (!fd.node.data.isdir) {
writeNodeData(fd.node);
clite.io.close(fd);
return;
}
var e;
while ((e = clite.io.read(fd)) != null) {
writeNodeData(e);
}
clite.io.close(fd);
});
return 0;
});
clite.commands.load('cat',function(args,env,io) {
var files = [];
var pending = 0;
for (var i=1; i<args.length; i++) {
if (args[i][0] == '-') {
io.error('unknown argument: '+args[i]);
}else if (args[i][0] == '/') {
files.push(args[i]);
}else{
files.push(clite.lib.resolvePath(args[i],false));
}
}
if (files.length < 1)
return 1;
pending = files.length;
function fcb(fd) {
if (fd.node.data.isdir || fd.node.data.islink || fd.node.data.isdev) {
io.error('not a normal file: '+file);
}else{
var d = clite.io.readAll(fd);
if (d != null) {
io.write(d);
}
}
pending--;
clite.io.close(fd);
if (pending <1)
io.exit(0);
}
files.forEach(function(file,index) {
var fd = clite.io.open(file,fcb);
if (!fd) {
io.error('cannot open file: '+file);
pending--;
return;
}
if (fd.node.data.isdir || fd.node.data.islink || fd.node.data.isdev) {
io.error('not a normal file: '+file);
pending--;
return;
}
});
if (pending <1)
return 0;
return null;
});
clite.commands.load('download',function(args,env,io) {
return 0;
});
// insert commands above this line

View file

@ -1,12 +1,12 @@
html,body {margin:0; padding:0; overflow:auto; width:100%; height:100%; background-color:#000000; color:#FFFFFF; font-family:monospace; font-size:14px;}
body {}
body {display:flex; flex-direction: column;}
div, header, section, article, p, form, h1 {display:block; margin:0; padding:0;}
header {padding:10px;}
header h1 {line-height:40px; font-size:30px;}
section {}
section.content {}
section.content div#terminal {border: 1px solid #FFFFFF; width:100%; min-width: 500px; max-width:1000px; margin:10px auto; overflow:hidden; min-height:100px; padding:10px;}
section.content {flex-grow:1;}
section.content div#terminal {width:100%; min-width: 500px; max-width:1000px; margin:0 auto; overflow:hidden; height:100%; min-height:100px;}
section.content article {unicode-bidi: embed; white-space: pre;}
section.content form {}
section.content form label, section.content form input, section.content form input:focus {display:block; float:left; border:none; margin:0; padding:0; font-family:monospace; font-size:14px; line-height:20px; background-color:#000000; color:#FFFFFF; outline:none;}

View file

@ -150,7 +150,6 @@ var clite = {
return;
f.perms = '-r-xr-xr-x';
f.data.content = fn;
clite.log.write('loaded ('+name+')');
}
}
clite.core.load.script('clite/commands.js',function() {
@ -158,14 +157,24 @@ var clite = {
clite.commands = null;
// check cookies for login
clite.log.write('Checking for user session');
if (clite.user.init()) {// if logged in:
if (clite.user.init(vfsapi)) {// if logged in:
clite.log.write('found user session?');
// create user session
}else{// if new user:
clite.log.write('Generating guest session');
// read in /usr/share/introduction and write to shell
var fd = clite.io.open('/usr/share/introduction',function(fd) {
clite.user.genGuest();
if (!fd)
return;
var d = clite.io.readAll(fd);
if (d != null)
clite.shell.writeLine(d);
clite.io.close(fd);
});
}
clite.log.write('Creating Shell');
clite.events.refocus();
}); // end command load callback
}); // end filesys load callback
@ -174,7 +183,51 @@ var clite = {
};
clite.user = {
init:function() { // returns true if there's a user login or false for guest
init:function(vfs) { // returns true if there's a user login or false for guest
var udata = {
name:'root',
uid:0,
group:0,
groups:[]
};
clite.user.getUID = function() {
return udata.uid;
}
clite.user.getGID = function() {
return udata.gid;
}
clite.user.checkGID = function(gid) {
if (gid == udata.gid)
return true;
if (udata.groups.indexOf(gid) > -1)
return true;
return false;
}
function setLogin(user,uid,gid,groups) {
udata.name = user;
udata.uid = uid;
udata.group = gid;
udata.groups = groups;
clite.shell.env.HOME = '/usr/home/'+user;
clite.shell.env.PWD = '/usr/home/'+user;
clite.shell.env.USER = user;
clite.shell.prompt.generate();
clite.user.hasLogin = function() {
return true;
}
}
clite.user.genGuest = function() {
clite.term.clear();
vfs.mkDir('/usr/home/guest');
var n = vfs.getNode('/usr/home/guest');
if (n) {
n.perms = '-rwx------';
n.uid = 1;
n.gid = 1;
}
setLogin('guest',1,1,[]);
clite.events.refocus();
}
return false;
},
getUID:function() { // get the user's user id
@ -185,7 +238,11 @@ clite.user = {
},
checkGID:function(gid) { // check if the user is in a group
return true;
}
},
hasLogin:function() { // check if we're in a user session
return false;
},
genGuest:null
};
clite.io = {
@ -223,7 +280,7 @@ clite.io = {
}
};
function getFileDes(path,link) {
function getFileDes(path,link,cb) {
var n = vfsapi.getNode(path);
if (!n)
return null;
@ -238,19 +295,24 @@ clite.io = {
canexec:false,
remote:{
ispending:p,
callback:null
callback:cb
}
});
if (p)
if (p) {
clite.core.load.file(n.data.remote,function(d) {
n.data.content = d;
fd.remote.ispending = false;
try{
fd.remote.callback(fd);
} catch(err) {}
});
}
fd.canread = perms.checkReadable(fd.node.perms,fd.node.uid,fd.node.gid);
fd.canwrite = perms.checkWritable(fd.node.perms,fd.node.uid,fd.node.gid);
fd.canexec = perms.checkExecutable(fd.node.perms,fd.node.uid,fd.node.gid);
if (!p && cb != null) {
clite.core.execSafeAsync(function() {cb(fd);});
}
return fd;
}
clite.io.creat = function(path,type) {
@ -265,8 +327,8 @@ clite.io = {
return vfsapi.mkFile(path);
}
}
clite.io.open = function(path) {
return getFileDes(path,false);
clite.io.open = function(path,cb) {
return getFileDes(path,false,cb);
}
clite.io.close = function(fd) {
try{
@ -299,6 +361,9 @@ clite.io = {
fd.pos += l.length;
return l;
}
clite.io.readAll = function(fd) {
return fd.node.data.content;
}
clite.io.write = function(fd,data) {
if (!fd.canwrite)
return false;
@ -327,7 +392,7 @@ clite.io = {
return true;
}
clite.io.truncate = function(path,len) {
var fd = getFileDes(path,false);
var fd = getFileDes(path,false,null);
if (!fd)
return false;
var r = clite.io.ftruncate(fd,len);
@ -347,15 +412,15 @@ clite.io = {
return fd.pos;
}
clite.io.remove = function(path) {
var fd = getFileDes(path,true);
var fd = getFileDes(path,true,null);
if (!fd || !fd.canwrite)
return false;
return vfsapi.remove(path);
}
clite.io.link = function(path,target) {
if (!getFileDes(target,false))
if (!getFileDes(target,false,null))
return false;
var fd = getFileDes(path,true);
var fd = getFileDes(path,true,null);
if (fd) {
if (!fd.canwrite)
return false;
@ -370,6 +435,7 @@ clite.io = {
close:null,
read:null,
readLine:null,
readAll:null,
write:null,
ftruncate:null,
truncate:null,
@ -394,14 +460,20 @@ clite.vfs = {
}
}
function checkCanOpen(node) {
return true;
if (node.perms[7] == 'r')
return true;
if (clite.user.checkGID(node.gid) && node.perms[4] == 'r')
return true;
if (node.uid == clite.user.getUID() && node.perms[1] == 'r')
return true;
return false;
}
function mkNode() {
var fsnode = {
name: '',
uid: 0,
gid: 0,
perms:'-rw-r-----',
perms:'-rw-r--r--',
data:{
remote:null,
isdir:false,
@ -421,8 +493,9 @@ clite.vfs = {
vfsdata.fs.perms[0] = 'd';
clite.vfs.getApi = function() {
// TODO: only allow for the root user
return vfsdata.api;
if (clite.user.getUID() == 0)
return vfsdata.api;
return null;
}
vfsdata.api.getNode = function(path) {
@ -467,8 +540,9 @@ clite.vfs = {
n.data.parent = parent;
n.data.content = [];
n.data.isdir = true;
n.perms[0] = 'd';
// TODO: set uid/gid/permissions
n.perms = 'drwxr-xr-x';
n.uid = clite.user.getUID();
n.gid = clite.user.getGID();
parent.data.content.push(n);
return true;
}
@ -487,7 +561,9 @@ clite.vfs = {
n.name = name;
n.data.parent = parent;
n.data.content = '';
// TODO: set uid/gid/permissions
n.perms = '-rw-r--r--';
n.uid = clite.user.getUID();
n.gid = clite.user.getGID();
parent.data.content.push(n);
return true;
}
@ -506,9 +582,10 @@ clite.vfs = {
n.name = name;
n.data.parent = parent;
n.data.islink = true;
n.perms[0] = 'l';
n.data.content = target;
// TODO: set uid/gid/permissions
n.perms = 'lrwxr-xr-x';
n.uid = clite.user.getUID();
n.gid = clite.user.getGID();
parent.data.content.push(n);
return true;
}
@ -539,14 +616,11 @@ clite.vfs = {
clite.log = {
init:function(vfs) {
clite.log.write = function(txt) {
// TODO: after login don't write to term or shell
try {
if (clite.term.hasInput()) {
clite.shell.writeLine(txt);
}else{
if (!clite.user.hasLogin()) {
try {
clite.term.writeLine(txt);
}
} catch(e) {}
} catch(e) {}
}
var n = vfs.getNode('/var/logs');
if (n)
n.data.content += txt+'\n';
@ -648,24 +722,39 @@ clite.term = {
};
clite.shell = {
exit:function(v) {
clite.shell.prompt.pop();
clite.events.refocus();
},
readLine:function(cb) {
},
writeLine:function(txt) {
clite.term.writeLine(txt);
clite.events.refocus();
},
exec:function(txt) {
// TODO: split this better
var args = txt.split(' ');
// TODO: resolve using PATH
var path = '/bin/'+args[0];
resolvePath:function(txt) {
// TODO: resolve using PATH (check each colon separated path)
return clite.lib.resolvePath(txt,clite.shell.env.PATH)
},
exec:function(args,io) {
var path = clite.shell.resolvePath(args[0]);
var fd = clite.io.open(path);
// TODO: should errors here write to stderr? (io.error)
if (!fd) {
clite.shell.writeLine('Shell: unknown command: '+args[0]);
clite.term.writeLine('Shell: unknown command: '+args[0]);
io.exit(-1);
return;
}
if (!clite.core.execSafe(function() {fd.node.data.content(args)}))
clite.shell.writeLine('Shell: error in command:'+args[0]);
var r = clite.core.execSafe(function() {
var rr = fd.node.data.content(args,Object.create(clite.shell.env),io);
if (typeof rr == 'number')
io.exit(rr);
});
if (!r) {
clite.term.writeLine('Shell: error in command:'+args[0]);
io.exit(-1);
}
clite.io.close(fd);
},
parse:function(txt) {
@ -676,13 +765,108 @@ clite.shell = {
clite.shell.history.add(txt);
clite.shell.history.resetCurrent();
clite.shell.writeLine(clite.shell.prompt.getHTML()+txt);
clite.shell.prompt.push('');
clite.term.preventInput();
// ready stdio (stdout,stdin)
var stdio = {
error:clite.term.writeLine,
write:clite.term.writeLine,
read:clite.shell.readLine,
exit:clite.shell.exit,
istty:true
};
// check for a macro
var args = clite.lib.strToArgs(txt);
// then either run the macro or expand the program to be executed via PATH
clite.shell.exec(txt);
if (typeof clite.shell.macro[args[0]] != 'undefined') {
var r = clite.shell.macro[args[0]](args,stdio);
clite.shell.exit(r);
}else{
clite.shell.exec(args,stdio);
}
},
env:{
USER:'root',
PWD:'/',
HOME:'/',
PATH:'/bin'
},
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);
if (!fd) {
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]);
var fd = clite.io.open(path);
if (!fd)
return;
clite.io.close(fd);
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]);
var fd = clite.io.open(path);
if (!fd)
return;
clite.io.close(fd);
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,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:'$ ',
data:'# ',
set:function(txt) {
clite.shell.prompt.list = [];
clite.shell.prompt.data = txt;
@ -694,6 +878,15 @@ clite.shell = {
pop:function() {
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;
},
@ -759,5 +952,61 @@ clite.lib = {
var parts = txt.split('/');
parts.pop();
return parts.join('/');
},
// returns an absolute path: ('foo','/etc') -> '/etc/foo': ('foo','bar') -> $PWD/bar/foo
resolvePath:function(txt,base) {
if (txt[0] == '/')
return txt;
if (txt[0] == '~')
return clite.shell.env.HOME+txt.substring(1);
if (typeof base != 'string')
base = clite.shell.env.PWD;
if (base[0] != '/')
base = clite.shell.env.PWD+'/'+base;
return base+'/'+txt;
},
// convert a string into an argument array: 'ls /etc' -> ['ls',/etc]
strToArgs:function(txt) {
// TODO: split this properly (escape strings and insert env variables)
var parts = [];
var s = '';
var e = false;
var q = false;
for (var i=0; i<txt.length; i++) {
switch (txt[i]) {
case '//':
if (e) {
s+='//';
}else{
e = true;
}
break;
case '"':
if (!e) {
if (!q) {
q = true;
break;
}else{
q = false;
}
}else{
s+='"';
}
case ' ':
if (!q) {
parts.push(s);
s = '';
e = false;
q = false;
}
break;
default:
s+=txt[i];
e = false;
}
}
if (s.length > 0)
parts.push(s);
return parts;//txt.split(' ');
}
}

View file

@ -1,6 +1,4 @@
BEGINDATAWelcome, this site runs on Clite, the command line site management system.
Welcome, this site runs on Clite, the command line site management system.
Clite works like a Unix terminal, a guest login will be automatically generated for you.
Clite has many Unix-like commands for navigating the website, simply type in a command and press enter to run it.
Not sure how this works? Use the `help' command at any time.
Press Enter to begin.