Initial commit

This commit is contained in:
Sasha Koshka 2024-10-08 15:51:30 -04:00
commit 22c5179503
2 changed files with 136 additions and 0 deletions

27
README.md Normal file
View File

@ -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 <board-name> Message text
```
To read recent messages:
```
bulb read
bulb read -b <board-name>
bulb read -n <message-count>
```

109
cmd/bulb/main.ha Normal file
View File

@ -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
//};