commit 22c5179503e915d77acc28df6e2e374d8c7aae19 Author: Sasha Koshka Date: Tue Oct 8 15:51:30 2024 -0400 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ae8eda --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# bulb + +A simple bulletin board for your server. Users of the system can post and read +messages using the "bulb" command. You can also set up multiple boards. + +## Usage + +To list available boards: + +``` +bulb +``` + +To post a message: + +``` +bulb post Message text +bulb post -b Message text +``` + +To read recent messages: + +``` +bulb read +bulb read -b +bulb read -n +``` diff --git a/cmd/bulb/main.ha b/cmd/bulb/main.ha new file mode 100644 index 0000000..34f66c2 --- /dev/null +++ b/cmd/bulb/main.ha @@ -0,0 +1,109 @@ +use errors; +use fmt; +use fs; +use getopt; +use os; +use strconv; +use strings; +use unix; + +const default_board = "general"; + +let name = ""; +@init fn name() void = name = os::args[0]; + +let path: []str = []; +@init fn path() void = path = strings::split(os::tryenv("BULBPATH", "/var/bulb"), ":"); +@fini fn path() void = free(path); + +export fn main() void = { + // parse args + const cmd = getopt::parse( + os::args, + "bulletin board", + ("post", [ + ('a', "post anonymously"), + ('b', "board", "which board to post on"), + "message...", + ]): getopt::subcmd_help, + ("read", [ + ('b', "board", "which board to read"), + ('n', "number", "display N most recent messages"), + ]): getopt::subcmd_help); + defer getopt::finish(&cmd); + + match (cmd.subcmd) { + case let subcmd: (str, *getopt::command) => + if (subcmd.0 == "post") { + subcmd_post(subcmd.1); + } else if (subcmd.0 == "read") { + match (subcmd_read(subcmd.1)) { + case errors::invalid => + getopt::printhelp(cmd); + os::exit(os::status::FAILURE); + case void => void; + }; + }; + case void => + subcmd_ls(&cmd); + }; +}; + +fn subcmd_post(cmd: *getopt::command) void = { + let anonymous = false; + let board = default_board; + for (let opt .. cmd.opts) { + switch (opt.0) { + case 'a' => anonymous = true; + case 'b' => board = opt.1; + case => abort(); + }; + }; + + // TODO +}; + +fn subcmd_read(cmd: *getopt::command) (void | errors::invalid) = { + let board = default_board; + let number = 8; + for (let opt .. cmd.opts) { + switch (opt.0) { + case 'b' => + board = opt.1; + case 'n' => + match (strconv::stoi(opt.1)) { + case let num: int => number = num; + case => return errors::invalid; + }; + case => abort(); + }; + }; + + // TODO +}; + +fn subcmd_ls(cmd: *getopt::command) void = { + for (let directory .. path) { + match (list_boards_in(directory)) { + case let err: fs::error => + fmt::errorf ( + "{}: Can't list boards in {}: {}\n", + name, + directory, + fs::strerror(err))!; + case void => void; + }; + }; +}; + +fn list_boards_in(directory: str) (void | fs::error) = { + let entries = os::readdir(directory)?; + defer fs::dirents_free(entries); + for (let entry .. entries) { + fmt::println(entry.name)!; + }; +}; + +//fn look_up_board(board: str, path: []str) (str | void) { + // TODO +//};