diff --git a/docs/system.md b/docs/system.md index 8f77b99e..1610b939 100644 --- a/docs/system.md +++ b/docs/system.md @@ -15,7 +15,7 @@ See [the BQN specification](https://mlochbaum.github.io/BQN/spec/system.html) fo | `•name` | | | `•wdpath` | | | `•Exit` | | -| `•file` | Fields: `path`, `At`, `List`, `Bytes`, `Chars`, `Lines`, `Type`, `Exists`, `Name`, `Parent`, `MapBytes`, `CreateDir`, `Rename`, `Remove` | +| `•file` | Fields: `path`, `At`, `List`, `Bytes`, `Chars`, `Lines`, `Type`, `Exists`, `Name`, `Parent`, `MapBytes`, `CreateDir`, `Rename`, `Remove`, `Created`, `Modified`, `Accessed` | | `•FChars` | | | `•FBytes` | | | `•FLines` | | diff --git a/src/builtins/sysfn.c b/src/builtins/sysfn.c index bf0a2c92..db25c533 100644 --- a/src/builtins/sysfn.c +++ b/src/builtins/sysfn.c @@ -726,6 +726,9 @@ void clearImportCache() { static NFnDesc* fTypeDesc; +static NFnDesc* fCreatedDesc; +static NFnDesc* fAccessedDesc; +static NFnDesc* fModifiedDesc; static NFnDesc* fExistsDesc; static NFnDesc* fListDesc; static NFnDesc* fMapBytesDesc; @@ -759,6 +762,11 @@ B ftype_c1(B d, B x) { if (ty==0) thrM("•file.Type: Error while accessing file"); return m_c32(ty); } + +B fcreated_c1 (B d, B x) { return path_info(path_rel(nfn_objU(d), x), 0); } +B faccessed_c1(B d, B x) { return path_info(path_rel(nfn_objU(d), x), 1); } +B fmodified_c1(B d, B x) { return path_info(path_rel(nfn_objU(d), x), 2); } + B fexists_c1(B d, B x) { char ty = path_type(path_rel(nfn_objU(d), x)); return m_f64(ty!=0); @@ -1208,7 +1216,7 @@ B sys_c1(B t, B x) { if(!fileNS.u) { REQ_PATH; #define F(X) m_nfn(X##Desc, inc(path)) - fileNS = m_nns(file_nsGen, q_N(path)? m_c32(0) : inc(path), F(fileAt), F(fList), F(fBytes), F(fChars), F(fLines), F(fType), F(fExists), inc(bi_fName), inc(bi_fParent), F(fMapBytes), F(createdir), F(rename), F(remove)); + fileNS = m_nns(file_nsGen, q_N(path)? m_c32(0) : inc(path), F(fileAt), F(fList), F(fBytes), F(fChars), F(fLines), F(fType), F(fCreated), F(fAccessed), F(fModified), F(fExists), inc(bi_fName), inc(bi_fParent), F(fMapBytes), F(createdir), F(rename), F(remove)); #undef F } cr = incG(fileNS); @@ -1241,6 +1249,7 @@ B sys_c1(B t, B x) { else if (eqStr(c, U"rebqn")) cr = incG(bi_reBQN); else if (eqStr(c, U"primitives")) cr = getPrimitives(); else if (eqStr(c, U"fromutf8")) cr = incG(bi_fromUtf8); + else if (eqStr(c, U"toutf8")) cr = incG(bi_toUtf8); else if (eqStr(c, U"path")) cr = inc(REQ_PATH); else if (eqStr(c, U"name")) cr = inc(REQ_NAME); else if (eqStr(c, U"fchars")) cr = m_nfn(fCharsDesc, inc(REQ_PATH)); @@ -1279,6 +1288,9 @@ void sysfn_init() { fBytesDesc = registerNFn(m_c8vec_0("(file).Bytes"), fbytes_c1, fbytes_c2); fListDesc = registerNFn(m_c8vec_0("(file).List"), list_c1, c2_bad); fTypeDesc = registerNFn(m_c8vec_0("(file).Type"), ftype_c1, c2_bad); + fCreatedDesc = registerNFn(m_c8vec_0("(file).Created"), fcreated_c1, c2_bad); + fModifiedDesc= registerNFn(m_c8vec_0("(file).Modified"), fmodified_c1, c2_bad); + fAccessedDesc= registerNFn(m_c8vec_0("(file).Accessed"), faccessed_c1, c2_bad); createdirDesc= registerNFn(m_c8vec_0("(file).CreateDir"), createdir_c1, c2_bad); renameDesc = registerNFn(m_c8vec_0("(file).Rename"), c1_bad, rename_c2); removeDesc = registerNFn(m_c8vec_0("(file).Remove"), remove_c1, c2_bad); @@ -1289,6 +1301,6 @@ void sysfn_init() { ffiloadDesc = registerNFn(m_c32vec_0(U"•FFI"), c1_bad, ffiload_c2); } void sysfnPost_init() { - file_nsGen = m_nnsDesc("path","at","list","bytes","chars","lines","type","exists","name","parent","mapbytes","createdir","rename","remove"); + file_nsGen = m_nnsDesc("path","at","list","bytes","chars","lines","type","created","accessed","modified","exists","name","parent","mapbytes","createdir","rename","remove"); c(BMd1,bi_bitcast)->im = bitcast_im; } diff --git a/src/utils/file.c b/src/utils/file.c index 41778090..8bcaa37e 100644 --- a/src/utils/file.c +++ b/src/utils/file.c @@ -139,7 +139,7 @@ B path_name(B path) { guaranteeStr(path); for (i64 i = (i64)pia-1; i >= 0; i--) { if (o2cu(GetU(path, i))=='/') { - if (i == pia-1) thrF("File path ended with a slash: '%R'", path); + if (i == pia-1) thrF("File path ended with a slash: \"%R\"", path); return taga(arr_shVec(TI(path,slice)(path, i+1, pia - (i+1)))); } } @@ -301,7 +301,7 @@ void mmap_init() { } #else B mmap_file(B path) { - thrM("CBQN was compiled without mmap"); + thrM("CBQN was compiled without •file.MapBytes support"); } void mmap_init() { } #endif @@ -335,15 +335,20 @@ bool path_remove(B path) { return ok; } -char path_type(B path) { +int path_stat(struct stat* s, B path) { // doesn't consume; get stat of s; errors if path isn't string; returns non-zero on failure char* p = toCStr(path); - struct stat path_stat; - int r = stat(p, &path_stat); + int r = stat(p, s); freeCStr(p); + return r; +} + +char path_type(B path) { + struct stat s; + int r = path_stat(&s, path); dec(path); if (r==-1) return 0; - i64 mode = path_stat.st_mode; + i64 mode = s.st_mode; if (S_ISREG (mode)) return 'f'; if (S_ISDIR (mode)) return 'd'; if (S_ISLNK (mode)) return 'l'; @@ -353,6 +358,21 @@ char path_type(B path) { if (S_ISCHR (mode)) return 'c'; thrM("Unexpected file type"); } + +B get_timespec(struct timespec ts) { + return m_f64(ts.tv_sec + ts.tv_nsec*1e-9); +} +B path_info(B path, i32 mode) { + struct stat s; + int r = path_stat(&s, path); + if (r==-1) thrF("Failed to access file \"%R\": %S", path, strerror(errno)); + dec(path); + if (mode==0) return get_timespec(s.st_ctim); + if (mode==1) return get_timespec(s.st_atim); + if (mode==2) return get_timespec(s.st_mtim); + thrM("Unknown path_info mode"); +} + void mmX_dumpHeap(FILE* f); void writeNum(FILE* f, u64 v, i32 len) { u8 buf[8]; diff --git a/src/utils/file.h b/src/utils/file.h index ec9d7cd5..676c4df8 100644 --- a/src/utils/file.h +++ b/src/utils/file.h @@ -27,5 +27,6 @@ void path_wBytes(B path, B x); // consumes path void file_wBytes(FILE* file, B name, B x); // doesn't consume B path_list(B path); // consumes -char path_type(B path); // consumes +char path_type(B path); // consumes; errors only if path isn't a string +B path_info(B path, i32 mode); // consumes; mode: 0:created 1:accessed 2:modified void cbqn_heapDump(void);