CSV report
Write a small sales CSV to a temporary file, read it back line by line, and print a per-region summary table with aligned columns.
Code
// Write a small sales CSV, read it back line by line, and print a per-region report.
use rok::std::env;
use rok::std::fs;
use rok::std::fs::File;
use rok::std::io::{IoError, BufReader, BufWriter, Reader, Writer};
const ROWS: str = "date,region,product,units,unit_price
2026-01-03,north,widget,12,2.50
2026-01-03,south,gadget,3,19.99
2026-01-04,north,gadget,1,19.99
2026-01-05,east,widget,40,2.25
2026-01-05,south,widget,7,2.50
2026-01-06,west,gizmo,two,7.00
2026-01-07,east,gizmo,5,7.00
2026-01-08,north,gizmo,2,7.50
2026-01-09,east;widget,3,2.25";
struct Sale {
region: str;
units: i64;
price: f64;
}
struct Group {
orders: i64 = 0;
units: i64 = 0;
revenue: f64 = 0.0;
}
enum RowError {
Fields(usize),
Number(str),
}
fn parse_row(line: str) throws (RowError) -> Sale {
let f: List<str> = [];
for part in line.split(",") { f.push(part.trim()); }
if f.len() != 5 { throw RowError::Fields(f.len()); }
let units = try f[3].parse::<i64>() catch _ { throw RowError::Number(f[3]); };
let price = try f[4].parse::<f64>() catch _ { throw RowError::Number(f[4]); };
Sale { region: f[1], units: units, price: price }
}
fn write_csv(path: str) throws (IoError) {
let w = BufWriter::new(try File::create(path));
for line in ROWS.lines() {
try w.write_fmt(f"{line}\n");
}
try w.flush();
}
fn summarize(path: str) throws (IoError) -> Dict<str, Group> {
let groups: Dict<str, Group> = Dict::new();
let r = BufReader::new(try File::open(path));
let n: usize = 0;
for item in r.lines() {
let line = try item.get();
n += 1;
if n == 1 || line.is_empty() { continue; } // header
let sale = try parse_row(line) catch e: RowError {
match e {
RowError::Fields(k) => println(f"line {n}: expected 5 fields, got {k}, skipped"),
RowError::Number(s) => println(f"line {n}: bad number '{s}', skipped"),
}
continue;
};
let g = groups.get(sale.region) ?? Group {};
g.orders += 1;
g.units += sale.units;
g.revenue += sale.units as f64 * sale.price;
groups[sale.region] = g;
}
groups
}
fn print_report(groups: Dict<str, Group>) {
let regions: List<str> = [];
for k in groups.keys() { regions.push(k); }
regions.sort();
println(f"{"region":<8}|{"orders":>7}|{"units":>6}|{"revenue":>9}|{"avg":>7}");
println(f"{"":-<8}+{"":-<7}+{"":-<6}+{"":-<9}+{"":-<7}");
let total = Group {};
for name in regions {
let g = groups[name];
let avg = g.revenue / g.units as f64;
println(f"{name:<8}|{g.orders:>7}|{g.units:>6}|{g.revenue:>9.2}|{avg:>7.2}");
total.orders += g.orders;
total.units += g.units;
total.revenue += g.revenue;
}
println(f"{"total":<8}|{total.orders:>7}|{total.units:>6}|{total.revenue:>9.2}|{"":>7}");
}
fn main() throws (IoError) {
let path = (env::temp_dir() / "rok-sales-demo.csv").as_str();
try write_csv(path);
let groups = try summarize(path) finally { try! fs::remove_file(path); };
print_report(groups);
}
line 7: bad number 'two', skipped
line 10: expected 5 fields, got 4, skipped
region | orders| units| revenue| avg
--------+-------+------+---------+-------
east | 2| 45| 125.00| 2.78
north | 3| 15| 64.99| 4.33
south | 2| 10| 77.47| 7.75
total | 7| 70| 267.46|
Walkthrough
- The file is created under
env::temp_dir()and removed in thefinallyofmain, so no temporary file is left behind even if summarizing fails (Files and I/O,env::temp_dir). - Writing goes through a
BufWriterand reading through aBufReader. Each item ofr.lines()is aResult<str, IoError>, sotry item.get()takes the line out and propagates read errors (BufReader,fs). parse_rowthrows aRowErrorfor a malformed row; the caller prints a warning in itscatchblock and moves on withcontinue. One bad line does not sink the whole report (Errors).- The totals are a
Dict<str, Group>.groups.get(region) ?? Group {}fetches an entry or starts a new one, andgroups[region] = gstores the updated value back. Every field ofGrouphas a default, soGroup {}alone starts at zero (Collections and iterators). - Format specs line up the table:
{name:<8}left-aligns,{g.revenue:>9.2}uses width 9 and two decimals, and{"":-<8}fills an empty string with-to draw the rule (2.10.8 align and width).