// Reads a stock list and writes it as an HTML page, largest count first. // // printf 'tools;Bolts & Nuts;12\npaper; A4;40\n' > stock.csv // nikaia run -- stock.csv report.html // 2 items, 52 in total // // The input has one `category;item;count` per line, as above. The page lands // in report.html. // // Shows: a `grammar` with `@frame`, sorting by a key, and a `dsl html` page // written to a file with `fs::write`. // // A package: `nikaia run` in this directory runs it, and `nikaia test` checks // what it prints and the file it writes against `tests/` (ADR-247). use std::fs use std::cli use std::html grammar Stock { // Up to the next `;` or the end of the line. A line missing its last field // then stops at the newline instead of eating the line after it. rule FIELD -> String = s:until(";" | frame_end) -> { s } rule COUNT -> i64 = n:dec[i64](digit+) -> { n } // `@frame` says an entry ends at a newline. The rule is uppercase, so it is // lexical: no whitespace is skipped, and ` Bolts` stays different from `Bolts`. @frame(boundary: "\n") rule ENTRY -> Entry = category:FIELD ";" => item:FIELD ";" count:COUNT frame_end -> { Entry { category, item, count } } entry rule file -> Vec[Entry] = entries:ENTRY* -> { entries } } // `String`: the category and the item are slices of the input file, not // copies. pub struct Entry { category: String, item: String, count: i64, } // Every hole in the template is escaped, so the `&`, `<` and `"` in item names // from the file arrive on the page as `&`, `<` and `"`. fn page(entries: Vec[Entry], total: i64) -> String { return dsl html { Stock

Stock

CategoryItemCount
{e.category}{e.item}{e.count}

{total} in total

} eod } fn main() throws { let path = cli::args().nth(1) ?? "stock.csv" let out = cli::args().nth(2) ?? "report.html" let data = fs::map(ref path, fs::Root::Anywhere) catch { eprintln(f"cannot open {path}: {error}") return } let mut entries = Stock::file(data) catch { eprintln(f"{path}: {error}") return } // Largest first. The sort is stable: items with the same count keep their // order from the file. entries.sort_by_key fn(entry) { -entry.count } let mut total = 0 for e in entries { total += e.count } let count = entries.len() let markup = page(entries, total) // If the page cannot be written, say so and stop, rather than report success. fs::write(ref out, fs::Root::Anywhere, ref markup) catch { eprintln(f"cannot write {out}: {error}") return } println(f"{count} items, {total} in total") }