aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbqn.js92
-rw-r--r--docs/bqn.js2
2 files changed, 84 insertions, 10 deletions
diff --git a/bqn.js b/bqn.js
index 3f92e9a6..f03c3c66 100755
--- a/bqn.js
+++ b/bqn.js
@@ -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;
}