diff --git a/cmd/bulb/main.ha b/cmd/bulb/main.ha index a4ba064..0ea0083 100644 --- a/cmd/bulb/main.ha +++ b/cmd/bulb/main.ha @@ -2,7 +2,9 @@ use errors; use fmt; use fs; use getopt; +use io; use os; +use path; use strconv; use strings; use unix; @@ -12,12 +14,11 @@ 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); +let bulb_path: []str = []; +@init fn bulb_path() void = bulb_path = strings::split(os::tryenv("BULBPATH", "/var/bulb"), ":"); +@fini fn bulb_path() void = free(bulb_path); export fn main() void = { - // parse args const cmd = getopt::parse( os::args, "bulletin board", @@ -39,7 +40,7 @@ export fn main() void = { } else if (subcmd.0 == "read") { match (subcmd_read(subcmd.1)) { case errors::invalid => - getopt::printhelp(cmd); + getopt::printhelp(os::stdout, name, cmd.help)!; os::exit(os::status::FAILURE); case void => void; }; @@ -60,28 +61,15 @@ fn subcmd_post(cmd: *getopt::command) void = { }; }; - let location = match(look_up_board(board)) { - case let location: str => - yield location; - case void => - fmt::errorf("{}: The board {} does not exist", name, board)!; - return; - }; + let user_name = if (anonymous) void else get_user_name(); + let message = strings::join(" ", cmd.args...); + defer free(message); - let file = match(os::open(location, fs::flag::APPEND)) { - case let file: io::file => - defer io::close(file); - yield file; - case let err: fs::error => - fmt::errorf( - "{}: Can't make post: {}", - name, fs::strerror(err)); - return; + match (post(board, user_name, message)) { + case let err: error => + fmt::errorf("{}: Could not read {}: {}\n", name, board, strerror(err))!; + case void => void; }; - - let user = - - fmt::fprintf(file, "{}: {}", user, message)!; }; fn subcmd_read(cmd: *getopt::command) (void | errors::invalid) = { @@ -99,12 +87,16 @@ fn subcmd_read(cmd: *getopt::command) (void | errors::invalid) = { case => abort(); }; }; - - // TODO + + match (read(board, number)) { + case let err: error => + fmt::errorf("{}: Could not read {}: {}\n", name, board, strerror(err))!; + case void => void; + }; }; fn subcmd_ls(cmd: *getopt::command) void = { - for (let directory .. path) { + for (let directory .. bulb_path) { match (list_boards_in(directory)) { case let err: fs::error => fmt::errorf( @@ -125,6 +117,20 @@ fn list_boards_in(directory: str) (void | fs::error) = { }; }; -//fn look_up_board(board: str, path: []str) (str | void) { - // TODO -//}; +fn look_up_board(board: str) (str | no_such_board) = { + static let buf = path::buffer { ... }; + for (let directory .. bulb_path) { + let location = path::set(&buf, directory, board)!; + match (os::stat(location)) { + case fs::filestat => return location; + case => void; + }; + }; + return no_such_board; +}; + +fn get_user_name() str = { + // FIXME: could not find support for getting a username from a uid in + // hare. when it is added, use that here. + return os::tryenv("USER", "anonymous"); +}; diff --git a/cmd/bulb/operation.ha b/cmd/bulb/operation.ha new file mode 100644 index 0000000..4b2286f --- /dev/null +++ b/cmd/bulb/operation.ha @@ -0,0 +1,69 @@ +use fs; +use fmt; +use io; +use os; + +type no_such_board = !void; +type error = (io::error | fs::error | no_such_board); + +fn strerror(err: error) str = match(err) { +case no_such_board => return "No such board"; +case let err: fs::error => return fs::strerror(err); +case let err: io::error => return io::strerror(err); +}; + +fn read(board: str, number: int) (void | error) = { + let location = look_up_board(board)?; + let file = os::open(location)?; + defer io::close(file)!; + seek_to_nth_last_line(file, number)?; + io::copy(os::stdout, file)?; +}; + +fn post(board: str, user_name: (str | void), message: str) (void | error) = { + let location = look_up_board(board)?; + let file = os::open(location, fs::flag::APPEND | fs::flag::WRONLY)?; + defer io::close(file)!; + + let user = match(user_name) { + case let user: str => yield(user); + case void => yield "anonymous"; + }; + fmt::fprintf(file, "{}: {}\n", user, message)?; +}; + +fn seek_to_nth_last_line(file: io::handle, number: int) (void | io::error) = { + let buffer: [256]u8 = [0...]; + let end = io::seek(file, 0, io::whence::END)?; + let step = len(buffer): io::off; + let current = end; + + for (true) { + // read a chunk in reverse, or stop if there is nothing left + let buffer = buffer[..]; + if (current - step > 0) { + current -= step; + } else { + buffer = buffer[..current]; + current = 0; + }; + io::seek(file, current, io::whence::SET)?; + let length = match (io::read(file, buffer)?) { + case let length: size => yield length; + case io::EOF => break; + }; + io::seek(file, current, io::whence::SET)?; + + // look for line breaks in that chunk + let buffer = buffer[..length]; + for (let index = len(buffer): int - 1; index >= 0; index -= 1) { + if (buffer[index: size] == '\n') number -= 1; + if (number <= 0) { + io::seek(file, current + index: io::off, io::whence::SET)?; + return void; + }; + }; + }; + + return void; +};