hautils

[hare] Set of POSIX utilities
Log | Files | Refs | README | LICENSE

commit 9e255cb4b2d49ccf5959763b9988b4e926ddef2c
parent 0d31e3a320640510549ee90a789a54b4eca49b81
Author: iamthenoname <iamthenoname@perso.be>
Date:   Mon,  6 Mar 2023 20:55:12 +0800

ls: new command

Diffstat:
M.gitignore | 1+
MMakefile | 1+
Als.ha | 399+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 401 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -4,6 +4,7 @@ dirname env false head +ls nl pwd rm diff --git a/Makefile b/Makefile @@ -37,6 +37,7 @@ dirname: dirname.ha main/main.ha env: env.ha main/main.ha false: false.ha head: head.ha main/main.ha +ls: ls.ha main/main.ha nl: nl.ha main/main.ha pwd: pwd.ha main/main.ha rm: rm.ha main/main.ha diff --git a/ls.ha b/ls.ha @@ -0,0 +1,399 @@ +use datetime; +use fmt; +use fs; +use getopt; +use main; +use os; +use sort; +use strings; +use time; +use time::chrono; +use unix::passwd; + +type tstamp = (atime | ctime | mtime); +type atime = void; // access time +type ctime = void; // creation time +type mtime = void; // modification time + +type sorttype = (name | time | filesize); +type name = void; +type time = void; +type filesize = void; + +type entry = struct { + name: str, + mask: fs::stat_mask, + mode: fs::mode, + uid: uint, + gid: uint, + sz: size, + inode: u64, + time: time::instant, + target_mode: fs::mode, + target_inode: u64, +}; + +type flags = struct { + detailed: bool, + detailedid: bool, + fileindicator: bool, + listdir: bool, + printinode: bool, + recurse: bool, + reverseorder: bool, + showhidden: bool, + showhiddenexcept: bool, + sortby: sorttype, + timestamp: tstamp, + unsorted: bool, + usetargetinfo: bool, +}; + +type dirstate = struct { + listdir: bool, + firstdir: bool, + showdir: bool, +}; + +export fn utilmain() (void | main::error) = { + const help: []getopt::help = [ + "list directory contents", + ('A', "list hidden files except '.' and '..'"), + ('a', "list hidden files"), + ('c', "use ctime"), + ('d', "list directories themselves"), + ('F', "append a type indicator to filename"), + ('L', "show information for the target of the link"), + ('i', "print the index number for every file"), + ('l', "list detailed information"), + ('n', "like '-l' but with numeric IDs"), + ('R', "list recursively"), + ('r', "reverse order"), + ('S', "sort by size in decreasing order"), + ('t', "sort by timestamp"), + ('U', "unsorted"), + ('u', "use atime"), + "[dir...]", + ]; + const cmd = getopt::parse(os::args, help...); + defer getopt::finish(&cmd); + + let flg = flags{...}; + flg.timestamp = mtime; + flg.sortby = name; + for (let i = 0z; i < len(cmd.opts); i += 1) { + const opt = cmd.opts[i]; + switch (opt.0) { + case 'A' => + flg.showhiddenexcept = true; + case 'a' => + flg.showhidden = true; + case 'c' => + flg.sortby = time; + flg.timestamp = ctime; + case 'd' => + flg.listdir = true; + case 'F' => + flg.fileindicator = true; + case 'i' => + flg.printinode = true; + case 'L' => + flg.usetargetinfo = true; + case 'l' => + flg.detailed = true; + case 'n' => + flg.detailed = true; + flg.detailedid = true; + case 'R' => + flg.recurse = true; + case 'r' => + flg.reverseorder = true; + case 't' => + flg.sortby = time; + flg.timestamp = mtime; + case 'S' => + flg.sortby = filesize; + case 'U' => + flg.unsorted = true; + case 'u' => + flg.sortby = time; + flg.timestamp = atime; + }; + }; + + let state = dirstate{...}; + state.firstdir = true; + switch (len(cmd.args)) { + case 0 => + const ent: entry = mkent(".", flg)?; + state.listdir = shouldlistdir(ent, flg); + ls("", ent, &state, flg)?; + case 1 => + const path = cmd.args[0]; + const ent: entry = mkent(path, flg)?; + state.listdir = shouldlistdir(ent, flg); + ls("", ent, &state, flg)?; + case => + let fileents: []entry = []; + let dirents: []entry = []; + let fs = 0z, ds = 0z; + defer free(fileents); + defer free(dirents); + + for (let i = 0z; i < len(cmd.args); i += 1z) { + const ent = mkent(cmd.args[i], flg)?; + if (!flg.listdir && fs::isdir(ent.mode)) { + append(dirents, ent); + ds += 1; + } else { + append(fileents, ent); + fs += 1; + }; + }; + + state.showdir = ds > 1 || (ds > 0 && fs > 0); + if (!flg.unsorted) { + ls_sortents(fileents, flg); + ls_sortents(dirents, flg); + }; + + for (let i = 0z; i < len(fileents); i += 1z) { + state.listdir = false; + ls("", fileents[i], &state, flg)?; + }; + if (len(fileents) > 0 && len(dirents) > 0) fmt::printf("\n")?; + for (let i = 0z; i < len(dirents); i += 1z) { + state.listdir = true; + ls("", dirents[i], &state, flg)?; + }; + }; +}; + +fn mkent(path: str, flg: flags) (entry | main::error) = { + const stat = if (flg.usetargetinfo) { + yield os::stat(os::realpath(path)?)?; + } else { + yield os::stat(path)?; + }; + + let f = entry { + name = path, + mask = stat.mask, + mode = stat.mode, + uid = stat.uid, + gid = stat.gid, + sz = stat.sz, + inode = stat.inode, + time = match (flg.timestamp) { + case atime => + yield stat.atime; + case ctime => + yield stat.ctime; + case mtime => + yield stat.mtime; + }, + ..., + }; + if (fs::islink(stat.mode)) { + let targetstat = os::stat(os::readlink(f.name)?)?; + f.target_mode = targetstat.mode; + f.target_inode = targetstat.inode; + }; + return f; +}; + +fn ls(path: str, ent: entry, st: *dirstate, flg: flags) (void | main::error) = { + if (!st.listdir) { + ls_printent(ent, flg)?; + } else if (fs::isdir(ent.mode) || (fs::islink(ent.mode) + && fs::isdir(ent.target_mode))) { + if (st.firstdir) { + st.firstdir = false; + } else { + fmt::printf("\n")?; + }; + lsdir(path, ent, st, flg)?; + }; +}; + +fn lsdir( + path: str, + dirent: entry, + st: *dirstate, + flg: flags, +) (void | main::error) = { + const rootdir = os::getcwd(); + const dir = os::readdir(dirent.name)?; + let ents: []entry = []; + defer fs::dirents_free(dir); + defer free(ents); + + os::chdir(dirent.name)?; + for (let i = 0z; i < len(dir); i += 1) { + const path = dir[i].name; + if (strings::hasprefix(path, ".") && !flg.showhidden + && !flg.showhiddenexcept) { + continue; + } else if (flg.showhiddenexcept) { + if (strings::compare(path, "..") == 0 || + strings::compare(path, ".") == 0) { + continue; + }; + }; + append(ents, mkent(dir[i].name, flg)?); + }; + + if (!flg.unsorted) ls_sortents(ents, flg); + + if (path != "" || st.showdir) fmt::printfln("{}{}:", path, dirent.name)?; + for (let i = 0z; i < len(ents); i += 1) { + ls_printent(ents[i], flg)?; + }; + + if (flg.recurse) { + for (let i = 0z; i < len(ents); i += 1z) { + const ent = ents[i]; + if (strings::compare(ent.name, "..") == 0 || + strings::compare(ent.name, ".") == 0) { + continue; + }; + if (fs::islink(ent.mode) && fs::isdir(ent.target_mode) && + !flg.usetargetinfo) { + continue; + }; + + const prefix = strings::concat(path, dirent.name, "/"); + st.listdir = true; + ls(prefix, ents[i], st, flg)?; + free(prefix); + }; + }; + + os::chdir(rootdir)?; +}; + +fn ls_sortents(ents: []entry, flg: flags) void = { + match (flg.sortby) { + case name => + sort::sort(ents, size(entry), &namecmp); + case time => + sort::sort(ents, size(entry), &timecmp); + case filesize => + sort::sort(ents, size(entry), &sizecmp); + }; + if (flg.reverseorder) { + reverse(ents, size(entry)); + }; +}; + +fn ls_printent(ent: entry, flg: flags) (void | main::error) = { + if (flg.printinode) fmt::printf("{} ", ent.inode)?; + if (flg.detailed) { + fmt::printf("{} ", fs::mode_str(ent.mode))?; + + if (flg.detailedid) { + fmt::printf("{:-8} {:-8} ", ent.uid, ent.gid)?; + } else { + match (passwd::getuid(ent.uid)) { + case let pw: passwd::pwent => + fmt::printf("{:-8} ", pw.username)?; + passwd::pwent_finish(&pw); + case void => + fmt::printf("{:-8} ", ent.uid)?; + }; + + match (passwd::getgid(ent.gid)) { + case let gr: passwd::grent => + fmt::printf("{:-8} ", gr.name)?; + passwd::grent_finish(&gr); + case void => + fmt::printf("{:-8} ", ent.gid)?; + }; + }; + + fmt::printf("{:10} ", ent.sz)?; + + const date = datetime::from_instant(ent.time, chrono::LOCAL); + const now = time::now(time::clock::REALTIME); + if (now.sec > ent.time.sec + (180 * 24 * 60 * 60)) { + datetime::format(os::stdout, "%b %d %Y", &date)!; + } else { + datetime::format(os::stdout, "%b %d %H:%M", &date)!; + }; + fmt::printf(" ")?; + }; + + fmt::printf(ent.name)?; + if (flg.fileindicator) fmt::printf(ls_indicator(ent.mode))?; + if (fs::islink(ent.mode)) { + const target = os::readlink(ent.name)?; + fmt::printf(" -> {}", target)?; + if (flg.fileindicator) fmt::printf(ls_indicator(ent.target_mode))?; + }; + fmt::print("\n")?; +}; + +fn ls_indicator(m: fs::mode) str = { + if (fs::isdir(m)) { + return "/"; + } else if (fs::isfifo(m)) { + return "@"; + } else if (fs::issocket(m)) { + return "="; + } else if (fs::isfile(m)) { + const perm = fs::mode_perm(m); + if (perm & fs::mode::USER_X == fs::mode::USER_X + || perm & fs::mode::GROUP_X == fs::mode::GROUP_X + || perm & fs::mode::OTHER_X == fs::mode::OTHER_X) { + return "*"; + }; + }; + return ""; +}; + +fn shouldlistdir(e: entry, flg: flags) bool = { + return (!flg.listdir && fs::isdir(e.mode)) || + (fs::islink(e.mode) && fs::isdir(e.target_mode) && + (!flg.listdir && !flg.fileindicator && !flg.detailed)); +}; + +fn timecmp(a: const *void, b: const *void) int = { + const a = a: *entry, b = b: *entry; + const dist = b.time.sec - a.time.sec; + if (dist > 0) { + return 1; + } else if (dist < 0) { + return -1; + } else { + return 0; + }; +}; + +fn sizecmp(a: const *void, b: const *void) int = { + const a = a: *entry, b = b: *entry; + const asize = a.sz: int, bsize = b.sz: int; + const dist = bsize - asize; + if (dist > 0) { + return 1; + } else if (dist < 0) { + return -1; + } else { + return 0; + }; +}; + +fn namecmp(a: const *void, b: const *void) int = { + const a = a: *entry, b = b: *entry; + return strings::compare(a.name, b.name); +}; + +fn reverse(ents: []entry, itemsz: size) void = { + for (let i = 0z, r = len(ents) - 1; i < r) { + let l = ents[i]; + ents[i] = ents[r]; + ents[r] = l; + i += 1; + r -= 1; + }; +}; +