// 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
| Category | Item | Count |
| {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")
}