# A "shell" module. I want to test how an interface for such a module would look like. use CnileInst use Slice (Slice) use Mem (undefined, cast) use Error (panic) use Iter (map, any, append, for-each) use List (to-list) use StrBuilder use Alloc (Allocator(*)) use Str (Str(*), split) use Array # instances use Misc (with) use StrView (StrView) use Bits (u32-and) fn older-than (l, r) stat = undefined() Cnile.stat(l, Cnile.ptr-castptr(&stat)) lmtim = stat.mtim Cnile.stat(r, Cnile.ptr-castptr(&stat)) rmtim = stat.mtim return lmtim cmp (rmtim) == LT # depends-on (short name for faster typing) # currently screwed up due to default String being StrView and not ConstStr. # btw, when we fix the class fun type thing and associated types, this will stop being a problem. # fix for now: deps -> ddeps (dynamic deps, they might be needed); old deps uses an array, so type inference can do its megicke fn ddeps (d, it): it any(fn origin: d older-than(origin)) fn deps (d, arr Array n ConstStr): d ddeps(arr) fn run' (args Slice ConstStr) pid = Cnile.fork() if pid < 0 panic('fork() failed') # child # TODO: turn casts into proper conversions if pid == 0 Cnile.execvp(args Slice.get(0), args.ptr cast()) panic('execvp() failed') Cnile.exit(127) # parent else status = 0 if Cnile.waitpid(pid, &status, 0) < 0 panic('waitpid() failed') if not Cnile.wifexited(status) panic('something else happened to process; todo') fn run (args, al) args = args split() map(fn s: s StrBuilder.mk-const-str(al)) append(Mem.null-ptr() Mem.cast()) to-list(al) List.to-slice() ret = run'(args) # no return value for now, but in the future it'll prolly return something. # free all this stuff args for-each(fn s: al free(Slice { ptr: s Mem.cast() as Ptr Cnile.Void, count: 0 })) al free(args) return ret # fn command-exists (cmd Str, al Allocator) -> Bool # path-env = Cnile.getenv('PATH') # dir =& StrBuilder.mk(al) # found =& False # fn check-and-clear () -> Bool # dir StrBuilder.write-char('/') # dir StrBuilder.write(cmd) # candidate = dir StrBuilder.as-str() StrBuilder.mk-const-str(al) # result = Cnile.access(candidate, 1) == 0 # X_OK = 1 # candidate StrBuilder.free-const-str(al) # while dir StrBuilder.count() > 0 # dir StrBuilder.pop() # return result # path-env chars() for-each(fn(c)) # if found& # return # if c == ':' # if check-and-clear() # found <&= True # else # dir StrBuilder.write-char(c) # if not found& # found <&= check-and-clear() # StrBuilder.free(dir) # return found& fn rm (s) if Cnile.remove(s) /= 0 panic('remove() failed') fn mkdir (path Str, al Allocator) path = path StrBuilder.mk-const-str(al) if Cnile.mkdir(path, Convert.from-octal([7, 7, 7])) /= 0 if not Cnile.errno-EEXIST() panic('mkdir() failed') stat = undefined() Cnile.stat(path, Cnile.ptr-castptr(&stat)) if Bits.u32-and(stat.mode, Cnile.s-ifmt()) /= Cnile.s-ifdir() panic('mkdir() failed: path exists but is not a directory') path StrBuilder.free-const-str(al) fn mkdir-p (path Str, al Allocator) sb =& StrBuilder.mk(al) path chars() for-each(fn(c)) if c == '/' if sb StrBuilder.count() > 0 sb& mkdir(al) sb StrBuilder.write-char(c) sb& mkdir(al) StrBuilder.free(sb) fn cat(path Str, al Allocator) -> StrView: path StrBuilder.mk-const-str(al) with(fn(path): path StrBuilder.free-const-str(al), fn(path): path File.read-contents(al)) fn ls (path Str, al Allocator) -> Slice ConstStr path = path StrBuilder.mk-const-str(al) dir = Cnile.opendir(path) if Mem.is-ptr-null(dir) panic('ls: could not open directory') dirs =& List.mk(al) while True entry = Cnile.readdir(dir) Cnile.castptr-ptr() if Mem.is-ptr-null(entry) break name = @cast(&entry&.d-name) as ConstStr if Cnile.strcmp(name, '.') /= 0 and Cnile.strcmp(name, '..') /= 0 dirs List.add(name) Cnile.closedir(dir) path StrBuilder.free-const-str(al) return dirs& List.to-slice() # NOTE: Allocates the ConstStr! # Dirent shit FileType File Dir inst Eq FileType eq (l, r) case (l, r) (File, File) return True (Dir, Dir) return True _ return False # currently ignore symlinks!! :) fn d-type-to-type (x U8) if x == 8 return Just(File) elif x == 4 return Just(Dir) else return None fn ls-Ra-opt (path Str, fun (ConstStr, Maybe FileType, Bool) -> { should-dealloc Bool, skip Bool }, al Allocator) fn ls-R-into (path ConstStr) dir = Cnile.opendir(path) if Mem.is-ptr-null(dir) panic('ls-R: could not open directory "\(path)"') while True entry = Cnile.readdir(dir) Cnile.castptr-ptr() if Mem.is-ptr-null(entry) break name = @cast(&entry&.d-name) as ConstStr if Cnile.strcmp(name, '.') /= 0 and Cnile.strcmp(name, '..') /= 0 filetype = d-type-to-type(entry&.d-type) child = '\(path)/\(name)' StrBuilder.mk-const-str(al) hidden = name[0] == '.' res = fun(child, filetype, hidden) if not res.skip and filetype == Just(Dir) ls-R-into(child) if res.should-dealloc child StrBuilder.free-const-str(al) Cnile.closedir(dir) path-cs = path StrBuilder.mk-const-str(al) ls-R-into(path-cs) path-cs StrBuilder.free-const-str(al) # fn ls-Ra (path Str, al Allocator) # dirs =& List.mk(al) # path ls-Ra-opt(fn(child, filetype, hidden), al) # dirs List.add(child) # return { should-dealloc: False, skip: False } # return dirs& List.to-slice() # OKAY, the names are funny. I should later put all this stuff in its own, more normal module and just make custom names for Sh. # -p flag filters only files fn ls-R (path Str, al Allocator) dirs =& List.mk(al) path ls-Ra-opt(fn(child, filetype, hidden), al) if hidden return { should-dealloc: True, skip: True } if filetype == Just(Dir) return { should-dealloc: True, skip: False } dirs List.add(child) return { should-dealloc: False, skip: False } return dirs& List.to-slice()