calcli 0.4.0

Fast terminal calculator (TUI) with history, variables and engineering helpers
Documentation
//! Sample data for the `--demo` launch flag.
//!
//! The demo is a curated script of input lines that exercises the calculator's
//! features (arithmetic, `ans` chaining, variables, constants, comments and
//! notes, unit conversion and unit arithmetic). It is handed to the normal
//! startup path as a [`PersistedState`]; values, units and variables are then
//! regenerated by the service's recompute, so only the inputs are listed here.

use crate::storage::{PersistedEntry, PersistedState};

/// The demo input lines, oldest first, shown as the history on `--demo`.
const DEMO_INPUTS: &[&str] = &[
    "# calcli demo - press F12 or ? for help, Ctrl+Q to quit",
    "2 + 3 * 4",
    "ans ^ 2",
    "r = 5            # a radius",
    "2 * pi * r       # circumference",
    "# Units: write `value unit`, convert with ->",
    "123 MPa -> bar",
    "100 km/h -> m/s",
    "20 kN + 300 N",
    "2 kN / 4 m^2 -> kN/m^2",
    "x = 50 kN",
    "x -> N",
];

/// Builds a [`PersistedState`] that fills the session with the demo history.
///
/// The entries carry no precomputed value (`None`); the startup recompute
/// derives every value, unit and variable from the inputs. Settings are left
/// unset so the configured defaults apply.
pub fn demo_state() -> PersistedState {
    PersistedState {
        settings: None,
        ui: Default::default(),
        variables: Default::default(),
        history: DEMO_INPUTS
            .iter()
            .map(|input| PersistedEntry {
                input: (*input).to_string(),
                value: None,
                unit: None,
            })
            .collect(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::domain::evaluator::MevalEvaluator;
    use crate::domain::history::{History, HistoryEntry};
    use crate::domain::variables::VariableStore;
    use crate::services::CalcService;

    /// Replays the demo history exactly as `main::build_service` does, so the
    /// test sees the same recomputed values the TUI would show.
    fn replay() -> CalcService {
        let state = demo_state();
        let entries = state
            .history
            .iter()
            .map(|entry| HistoryEntry {
                input: entry.input.clone(),
                value: None,
                error: None,
            })
            .collect();
        let mut service = CalcService::new(
            Box::new(MevalEvaluator::new()),
            crate::config::Config::default().format_settings(),
            History::from_entries(entries, 500),
            VariableStore::new(),
        );
        service.recompute_all();
        service
    }

    #[test]
    fn every_demo_line_evaluates_without_error() {
        let service = replay();
        for entry in service.history().entries() {
            assert!(
                entry.error.is_none(),
                "demo line {:?} errored: {:?}",
                entry.input,
                entry.error,
            );
        }
    }

    #[test]
    fn demo_showcases_chaining_variables_and_units() {
        let service = replay();
        let value_at = |index: usize| {
            service.history().entries()[index]
                .value
                .as_ref()
                .map(|q| q.display_value())
        };
        // `2 + 3 * 4` then `ans ^ 2` = 14 then 196.
        assert_eq!(value_at(1), Some(14.0));
        assert_eq!(value_at(2), Some(196.0));
        // The variable conversion `x -> N` is the last line: 50 kN = 50000 N.
        let last = service.history().entries().last().unwrap();
        assert_eq!(last.value.as_ref().unwrap().unit_symbol(), Some("N"));
        assert!(
            (last.value.as_ref().unwrap().display_value() - 50_000.0).abs()
                < 1e-6
        );
    }
}