diff options
| author | Marshall Lochbaum <mwlochbaum@gmail.com> | 2021-05-29 16:16:18 -0400 |
|---|---|---|
| committer | Marshall Lochbaum <mwlochbaum@gmail.com> | 2021-05-29 16:16:18 -0400 |
| commit | 053305eb5c623488d40a91cf4601d30f3b41ef91 (patch) | |
| tree | 1e4ab678d9c7d7a96e8702f4cb497ab48e4c2756 | |
| parent | e6bee5e0cf0621fa00c1aeef29deb33d0818744c (diff) | |
Implement filesystem API, except •file.Open
| -rwxr-xr-x | bqn.js | 92 | ||||
| -rw-r--r-- | docs/bqn.js | 2 |
2 files changed, 84 insertions, 10 deletions
@@ -6,19 +6,20 @@ let fs = require('fs'); let bqn = require("./docs/bqn.js"); let {fmt,fmtErr,sysvals}=bqn; -let {has,list,str,unstr,dynsys,req1str}=bqn.util; +let {has,list,str,unstr,dynsys,req1str,makens}=bqn.util; 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; }; -let getres = e => { - let p = sysvals.path; let res; - if (p) { p=unstr(p); res = f=>path.resolve(p,f); } - else { res = f => { if (!path.isAbsolute(f)) throw Error(e+": Paths must be absolute when not running from a file"); return f; }; } - return x => res(req1str(e,x)); +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(() => fn(getres(e))); +let withres = (e,fn) => dynsys(() => fn(getres(sysvals.path)(e))); 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); } @@ -31,6 +32,79 @@ sysvals.fchars = withres("•FChars",fchars); sysvals.flines = withres("•FLines",flines); sysvals.fbytes = withres("•FBytes",fbytes); +sysvals.file = dynsys(() => { + let p = sysvals.path; + let res = getres(p); + let files = { + // Paths and parsing + path: p, + at: (x,w) => { + let e="•file.At", f=res(e)(has(w)?w:x); + return str(has(w)?path.resolve(f,req1str(e,x)):f); + }, + 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 list([dir(p.dir),p.name,p.ext].map(str)); }, + + // 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); + } + }, + 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]); + } + }, + + // 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) => list(fs.readdirSync(res("•file.List")(x,w)).map(str)), + 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)); +}); + let set_state = w => { w = w||[]; sysvals.state=list(w); sysvals.path=w[0]; sysvals.name=w[1]; sysvals.args=w[2]; @@ -39,7 +113,7 @@ let bqn_state = sysvals.bqn = (x,w) => { set_state(w); return bqn(x); } sysvals.exit = (x,w) => process.exit(Number.isInteger(x)?x:0); sysvals.bqn = (x,w) => bqn_state(req1str("•BQN",x), w); let bqn_file = (f,t,w) => bqn_state( - t, [ str(path.resolve(f,'..')+'/'), str(path.basename(f)), w ] + t, [ str(dir(path.dirname(f))), str(path.basename(f)), w ] ); let imports = {}; sysvals.import = withres("•Import", resolve => (x,w) => { @@ -58,7 +132,7 @@ if (!module.parent) { let arg0 = args[0]; let cl_state = () => { let s = str(""); - return [str(path.resolve(__dirname)+'/'), s, list([],s)]; + return [str(dir(path.resolve(__dirname))), s, list([],s)]; } let exec = fn => src => { try { diff --git a/docs/bqn.js b/docs/bqn.js index 7b1ef220..37bcd252 100644 --- a/docs/bqn.js +++ b/docs/bqn.js @@ -506,6 +506,6 @@ if (typeof process!=='undefined') { if (typeof module!=='undefined') { // Node.js bqn.fmt=fmt; bqn.fmtErr=fmtErr; bqn.compile=compile; bqn.run=run; - bqn.sysvals=sysvals; bqn.util={has,list,str,unstr,dynsys,req1str}; + bqn.sysvals=sysvals; bqn.util={has,list,str,unstr,dynsys,req1str,makens}; module.exports=bqn; } |
