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 | + |
M | Makefile | | | 1 | + |
A | ls.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;
+ };
+};
+