sys

A set of unix utils in hare!
Log | Files | Refs | README

commit 2c66e68df793d05c857b32e3e0bd005ae2a00eeb
parent d4fdb88ba11e5b638eeb0be9faf177fa3c747447
Author: thing1 <thing1@seacrossedlovers.xyz>
Date:   Mon, 23 Feb 2026 21:55:53 +0000

fixedcat and added uniq

Diffstat:
MMakefile | 4+++-
Mcmd/cat.ha | 28+++++++++++++++++-----------
Acmd/uniq.ha | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autil/escape.ha | 19+++++++++++++++++++
4 files changed, 102 insertions(+), 12 deletions(-)

diff --git a/Makefile b/Makefile @@ -7,7 +7,7 @@ DESTDIR= PREFIX=/usr/local BINDIR=$(PREFIX)/bin -all: bin/ls bin/rainbow bin/cat +all: bin/ls bin/rainbow bin/cat bin/uniq clean: rm -rf bin/* @@ -25,3 +25,5 @@ bin/cat: cmd/cat.ha +bin/uniq: cmd/uniq.ha + $(HARE) build $(HAREFLAGS) -o $@ cmd/uniq.ha diff --git a/cmd/cat.ha b/cmd/cat.ha @@ -6,20 +6,26 @@ use fs; use util; -export fn main() void = { +fn print(h: io::handle) void = { let b: []u8 = [0]; - for (let f .. os::args[1..]) { - let file = match(os::open(f)) { - case let f: io::file => yield f; - case let e: fs::error => util::die(fs::strerror(e), f); + for (let i = 0z; true; i += 1) { + match (io::read(h, b)) { + case size => fmt::print(b[0]: rune)!; + case => break; }; + }; +}; - for (let i = 0z; true; i += 1) { - match (io::read(file, b)) { - case size => fmt::print(b[0]: rune)!; - case => break; - }; - }; +export fn main() void = { + for (let f .. os::args[1..]) { + io::close(match(os::open(f)) { + case let f: io::file => print(f); yield f; + case let e: fs::error => util::die(fs::strerror(e), f); + } + )!; }; + + if (len(os::args) == 1) + print(os::stdin); }; diff --git a/cmd/uniq.ha b/cmd/uniq.ha @@ -0,0 +1,63 @@ +use sort; +use sort::cmp; +use fmt; +use io; +use os; +use strings; +use bufio; +use encoding::utf8; +use getopt; + +use util; + +let delim = "\n"; +let newline = true; + +fn run() void = { + let sc = bufio::newscanner(os::stdin); + defer bufio::finish(&sc); + + let lines: []str = []; + defer free(lines); + + for (true) { + match (bufio::scan_line(&sc)) { + case let s: str => append(lines, s)!; + case io::EOF => break; + case let e: utf8::invalid => util::die(utf8::strerror(e)); + case let e: io::error => util::die(io::strerror(e)); + }; + }; + + sort::sort(lines, size(str), &cmp::strs)!; + + let prev = ""; + for (let i = 0z; i < len(lines); i += 1) { + if (lines[i] == prev) continue + else fmt::printf("{}{}", lines[i], if (i != len(lines) - 1) delim else "")!; + prev = lines[i]; + }; +}; + +export fn main() void = { + let cmd = getopt::parse(os::args, + "unique", + ('d', "delim", "set a deliminer between printed tokens"), + ('n', "print a newline on exit (default)"), + ('N', "don't print a newline on exit") + ); + defer getopt::finish(&cmd); + + for (let opt .. cmd.opts) { + switch (opt.0) { + case 'd' => delim = util::escape(opt.1); + case 'n' => newline = true; + case 'N' => newline = false; + case => abort(); + }; + }; + + run(); + + if (newline) fmt::println()!; +}; diff --git a/util/escape.ha b/util/escape.ha @@ -0,0 +1,19 @@ +use strings; + +export fn escape(s: str) str = { + let bytes = strings::toutf8(s); + if (len(bytes) <= 1) return s; + if (bytes[0]: rune != '\\') + return s; + + + switch (bytes[1]: rune) { + case 'n' => return "\n"; + case 't' => return "\t"; + case 'v' => return "\v"; + case 'r' => return "\r"; + case '0' => return "\0"; + case '\\' => return "\\"; + case => return s; + }; +};