Functional Weave
Code in Rust

todo.summary

Counts for a todo list's header or footer: total, active, done, overdue, due today, percent done and "3 items left".

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 13 tests, run in TypeScript, Python and Rust.

What it does

The numbers a todo list shows about itself, in one call: how many there are, how many are open and done, how many are overdue or due today, the percentage done and the "N items left" text.

- `overdue` and `dueToday` come from todo.due-status, so they count exactly the todos in the Overdue and Today sections of todo.group-by-due. Done todos are never counted as overdue. - **`percentDone` rounds down** (math.round-div, mode `down`): 199 of 200 done is 99, not 100, so the bar only reads 100% when everything is done, and only reads 0% when nothing is. An empty list is 0. - `itemsLeft` counts active todos, with English plurals: `"0 items left"`, `"1 item left"`, `"2 items left"`. A localised app builds its own string from `active`. - `today` is checked even for an empty list, and a malformed or impossible date anywhere is an error (dates.add-days's messages).

For example

  • summarise_todos(, 2026-09-28) → total 0, active 0, done 0, overdue 0, due today 0, percent done 0, items left 0 items left an empty list is all zeros
  • summarise_todos(todos ×3, 2026-09-28) → total 3, active 0, done 3, overdue 0, due today 0, percent done 100, items left 0 items left everything done is 100%, 0 items left, and a done late todo is not overdue
  • summarise_todos(todos ×1, 2026-09-28) → total 1, active 1, done 0, overdue 0, due today 1, percent done 0, items left 1 item left one open todo due today is 1 item left

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

pub fn summarise_todos(todos: &[Todo], today: &str) -> TodoSummary
todosTodo[]
todaydatethe user's local date, from the app; never the clock
returnsTodoSummary

The type it declares, generated into your project

/// The numbers a todo list shows about itself.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TodoSummary {
    pub total: i64,
    /// not done
    pub active: i64,
    pub done: i64,
    /// open and past due
    pub overdue: i64,
    /// open and due today
    pub due_today: i64,
    /// done * 100 / total rounded down; 0 for an empty list
    pub percent_done: i64,
    /// "0 items left", "1 item left", "3 items left"
    pub items_left: String,
}

Your code names it in one line, in the file that uses it

fune!(todo.summary@^1);  // then call summarise_todos(…)
impl/rust.rs · 57 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::dates_day_of_week::day_of_week;  ← from dates.day-of-week ^1.0.0 · built alongside by fune
use super::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune
use super::todo_due_status::due_status;  ← from todo.due-status ^1.0.0 · built alongside by fune
use super::todo_item::Todo;  ← from todo.item ^1.0.0 · built alongside by fune
use super::todo_item_validate_todo::todos_from_value;

/// Counts for a todo list. percent_done rounds down so 100 means everything
/// is done; overdue and due_today agree with due_status.
///
/// # Panics
/// Panics on an impossible or malformed date.
pub fn summarise_todos(todos: &[Todo], today: &str) -> TodoSummary {
    day_of_week(today); // a bad today is an error even for an empty list
    let mut done = 0;
    let mut overdue = 0;
    let mut due_today = 0;
    for todo in todos {
        let status = due_status(todo, today);
        if todo.done {
            done += 1;
        }
        if status == "overdue" {
            overdue += 1;
        }
        if status == "today" {
            due_today += 1;
        }
    }
    let total = todos.len() as i64;
    let active = total - done;
    TodoSummary {
        total,
        active,
        done,
        overdue,
        due_today,
        percent_done: if total == 0 { 0 } else { round_div(done * 100, total, "down") },
        items_left: if active == 1 { "1 item left".to_string() } else { format!("{} items left", active) },
    }
}

pub fn todo_summary_to_value(s: &TodoSummary) -> Value {
    Value::obj(vec![
        ("total", Value::Int(s.total)),
        ("active", Value::Int(s.active)),
        ("done", Value::Int(s.done)),
        ("overdue", Value::Int(s.overdue)),
        ("dueToday", Value::Int(s.due_today)),
        ("percentDone", Value::Int(s.percent_done)),
        ("itemsLeft", Value::str(&s.items_left)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    todo_summary_to_value(&summarise_todos(&todos_from_value(&args[0]), args[1].as_str()))
}

Install

fune build

With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 4 dependencies, pins them in fune.lock, downloads only the Rust package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:

fune add todo.summary
Download for Rust todo.summary-1.0.0-rust.fune · 63,700 bytes sha256 cc7eabc75919e1f52a5a35901a572fcb0d86e4243e9c2314cdc4447f5266a993

The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./todo.summary-1.0.0-rust.fune, or fetch it from a terminal with fune pull todo.summary@1.0.0:rust.

The whole function, every language, is one file too: todo.summary-1.0.0.fune, 66,009 bytes, sha256 c18e7aa1361361be82208402f37176789c3697b153d357e69072e6d82a83db45. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

// fune: before todo.summary

after — your function gets the result and the arguments, and returns the final result.

// fune: after todo.summary

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

// fune: replace dates.day-of-week in todo.summary
// fune: replace math.round-div in todo.summary
// fune: replace todo.due-status in todo.summary
// fune: replace todo.item in todo.summary

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show todo.summary --steps.

// fune: step todo.summary after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
an empty list is all zeros , 2026-09-28 → total 0, active 0, done 0, overdue 0, due today 0, percent done 0, items left 0 items left
everything done is 100%, 0 items left, and a done late todo is not overdue todos ×3, 2026-09-28 → total 3, active 0, done 3, overdue 0, due today 0, percent done 100, items left 0 items left
one open todo due today is 1 item left todos ×1, 2026-09-28 → total 1, active 1, done 0, overdue 0, due today 1, percent done 0, items left 1 item left
a mixed list todos ×4, 2026-09-28 → total 4, active 3, done 1, overdue 1, due today 1, percent done 25, items left 3 items left
two of three done rounds down to 66 todos ×3, 2026-09-28 → total 3, active 1, done 2, overdue 0, due today 0, percent done 66, items left 1 item left
one of three done rounds down to 33 todos ×3, 2026-09-28 → total 3, active 2, done 1, overdue 0, due today 0, percent done 33, items left 2 items left
199 of 200 done is 99, not 100 todos ×200, 2026-09-28 → total 200, active 1, done 199, overdue 0, due today 0, percent done 99, items left 1 item left
a done todo due today does not count as due today todos ×2, 2026-09-28 → total 2, active 1, done 1, overdue 0, due today 1, percent done 50, items left 1 item left
two overdue, from last year and yesterday todos ×3, 2026-09-28 → total 3, active 3, done 0, overdue 2, due today 0, percent done 0, items left 3 items left
on Sunday, yesterday's todo is overdue todos ×2, 2026-10-04 → total 2, active 2, done 0, overdue 1, due today 1, percent done 0, items left 2 items left
Show the other 3 tests
CaseArgumentsExpected
a malformed today is an error even for an empty list , 20260928 → error: is not an ISO date
a today that never existed todos ×1, 2026-02-29 → error: is not a real calendar date
a due date that never existed todos ×1, 2026-09-28 → error: is not a real calendar date

Files

PathBytes
README.md898
impl/python.py1,074
impl/rust.rs1,831
impl/typescript.ts1,100
vectors.json50,954