#! /usr/bin/env node "use strict"; let path = require('path'); let fs = require('fs'); let bqn = require("./docs/bqn.js"); module.exports = bqn; let {fmt,fmtErr,sysvals,sysargs,makebqn,makerepl}=bqn; let {has,list,str,unstr,dynsys,req1str,makens}=bqn.util; let strlist = l=>list(l.map(str),str("")); let bqn_state=makebqn((x,w,u,s)=>(u(s,w),x)); let bqn_nostate=makebqn(x=>x); let show = x => console.log(fmt(x)); sysvals.show = (x,w) => { show(x); return x; }; sysvals.out = (x,w) => { console.log(req1str("•Out",x,w)); return x; }; sysvals.exit = (x,w) => process.exit(Number.isInteger(x)?x:0); let dir = f=>f==='/'?f:f+'/'; // BQN uses trailing slash let getres = p => { let res; if (p) { p=unstr(p); res = (e,f)=>path.resolve(p,f); } else { res = (e,f) => { if (!path.isAbsolute(f)) throw Error(e+": Paths must be absolute when not running from a file"); return f; }; } return e => (x,w) => res(e,req1str(e,x,w)); } let withres = (e,fn) => dynsys(state => fn(state.resolve(e), state)); let ff = (fr,fw,o) => resolve => (x,w) => { let f = resolve(has(w)?w:x); if (has(w)) { fs.writeFileSync(f,fw(x),o); return str(f); } else { return fr(fs.readFileSync(f,o)); } }; let fchars = ff(str,unstr,"utf-8"); let flines = ff(s=>strlist(s.replace(/\n$/,'').split('\n')),s=>s.map(unstr).join('\n')+'\n',"utf-8"); let fbytes = ff(s=>list(Array.from(s).map(c=>String.fromCodePoint(c))),s=>Buffer.from(s.map(c=>c.codePointAt(0)))); sysvals.fchars = withres("•FChars",fchars); sysvals.flines = withres("•FLines",flines); sysvals.fbytes = withres("•FBytes",fbytes); sysvals.file = dynsys(state => { let p = state.path; let res = state.resolve; let files = { // Paths and parsing path: p, at: (x,w) => { let e="•file.At"; return str(has(w)?path.join(req1str(e,w),req1str(e,x)):res(e)(x)); }, name: (x,w) => str(path.basename(req1str("•file.Name",x,w))), extension: (x,w) => str(path.extname (req1str("•file.Extension",x,w))), basename: (x,w) => str(path.parse(req1str("•file.BaseName",x,w)).name), parent: (x,w) => str(dir(path.dirname(res("•file.Parent")(x,w)))), parts: (x,w) => { let p=path.parse(res("•file.Parts")(x,w)); return strlist([dir(p.dir),p.name,p.ext]); }, // Metadata exists: (x,w) => fs.existsSync(res("•file.Exists")(x,w))?1:0, type: (x,w) => { let c = fs.constants; switch (c.S_IFMT & fs.lstatSync(res("•file.Type")(x,w)).mode) { case c.S_IFREG: return 'f'; case c.S_IFDIR: return 'd'; case c.S_IFLNK: return 'l'; case c.S_IFIFO: return 'p'; case c.S_IFSOCK: return 's'; case c.S_IFBLK: return 'b'; case c.S_IFCHR: return 'c'; } }, created: (x,w) => fs.statSync(res("•file.Created" )(x,w)).birthtimeMs/1000, accessed: (x,w) => fs.statSync(res("•file.Accessed")(x,w)).atimeMs/1000, modified: (x,w) => fs.statSync(res("•file.Modified")(x,w)).mtimeMs/1000, size: (x,w) => fs.statSync(res("•file.Size" )(x,w)).size, permissions: (x,w) => { let f=res("•file.Permissions")(x); let mode=fs.statSync(f).mode; if (has(w)) { if (!w.sh||w.sh.length!==1||w.sh[0]!==3) throw Error("•file.Permissions: 𝕨 must be a list of 3 numbers"); if (!w.every(n=>Number.isInteger(n)&&0<=n&&n<8)) throw Error("•file.Permissions: each permission must belong to ↕8"); let p=0; w.map(n=>p=8*p+n); fs.chmodSync(f,(mode&fs.constants.S_IFMT)|p); return w; } else { let p=[]; for (let i=3;i--;) { p[i]=mode&7; mode=Math.floor(mode/8); } return list(p,0); } }, owner: (x,w) => { let f=res("•file.Owner")(x); if (has(w)) { if (!w.sh||w.sh.length!==1||w.sh[0]!==2) throw Error("•file.Owner: 𝕨 must be a uid‿gid pair"); fs.chownSync(f,w[0],w[1]); return w; } else { let s=fs.statSync(f); return list([s.uid,s.gid],0); } }, // Access rename: (x,w) => {let r=res("•file.Rename"),f=r(w); fs.renameSync (r(x),f); return str(f);}, copy: (x,w) => {let r=res("•file.Copy" ),f=r(w); fs.copyFileSync(r(x),f); return str(f);}, createdir: (x,w) => {let f=res("•file.CreateDir")(x,w); fs.mkdirSync(f); return str(f);}, remove: (x,w) => {fs.rmSync(res("•file.Remove")(x,w)); return 1;}, removedir: (x,w) => {fs.rmSync(res("•file.RemoveDir")(x,w),{recursive:true,force:true}); return 1;}, list: (x,w) => strlist(fs.readdirSync(res("•file.List")(x,w))), chars: fchars(res("•file.Chars")), lines: flines(res("•file.Lines")), bytes: fbytes(res("•file.Bytes")), // TODO Open Return an open file object based on 𝕩 } return makens(Object.keys(files), Object.values(files)); }); sysvals.getline = () => { let l = 1024, b = Buffer.alloc(l); let fd = fs.openSync("/dev/stdin", "rs"); let r = ''; do { let n = fs.readSync(fd, b, 0, b.length); if (!n) return '\0'; r += b.toString('utf-8', 0, n); } while (r[r.length-1]!=='\n'); fs.closeSync(fd); return str(r.slice(0,-1)); } sysargs.resolve = sysargs.parres = getres(); let push_state = st => { st.parres = st.resolve; } let update_state = (st,w) => { w=w||[]; st.path=w[0]&&str(st.parres("Setting •path")(w[0])); st.resolve = getres(st.path); st.state=list(w); st.name=w[1]; st.args=w[2]; } sysvals.path=dynsys(s=>s.path); sysvals.name=dynsys(s=>s.name); sysvals.args=dynsys(s=>s.args); sysvals.state=dynsys(s=>s.state); sysvals.wdpath=dynsys(_=>str(dir(path.resolve('.')))); bqn.setexec(update_state, push_state); let bqn_file = (st,f,t,w) => bqn_state(st)( t, [ str(dir(path.dirname(f))), str(path.basename(f)), w ] ); let imports = {}; sysvals.import = withres("•Import", (resolve,state) => (x,w) => { let f = resolve(x); let save = r=>r; if (!has(w)) { let c=imports[f]; if (has(c)) return c; save = r => (imports[f]=r); w=list([]); } return save(bqn_file(state, f, fs.readFileSync(f,'utf-8'), w)); }); if (!module.parent) { let args = process.argv.slice(2); let arg0 = args[0]; let cl_state = () => { let s = str(""); let w = [str(dir(path.resolve('.'))), s, list([],s)]; update_state(sysargs, w); return sysargs; } let exec = fn => src => { try { fn(src); } catch(e) { console.error(''+fmtErr(e)+''); } } if (!has(arg0) || arg0==='-r') { let stdin = process.stdin; let repl = makerepl(cl_state(), 1); let e = exec(s=>show(repl(s))); stdin.on('end', () => { process.exit(); }); stdin.on('readable', () => { let inp; while ((inp=stdin.read())!==null) { if (!/^[ \t]*[#\n]/.test(inp)) e(inp.toString()); } }); } else if (arg0[0] !== '-' || (arg0==='-f'&&(arg0=(args=args.slice(1))[0],1))) { let f=arg0, a=strlist(args.slice(1)); exec(s=>bqn_file(sysargs, path.resolve(f),s,a))(fs.readFileSync(f,'utf-8')); } else if (arg0 === '-e' || arg0 === '-p') { let ev=bqn_nostate(cl_state()); let evs = arg0!=='-p' ? ev : (s=>show(ev(s))); args.slice(1).map(exec(evs)); } }