Functional Weave
Code in Python

todo.parse-repeat@1.0.0

impl/rust.rs

2,807 bytes · the Rust implementation · view 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_add_days::add_days;  ← from dates.add-days ^1.0.0 · built alongside by fune
use super::dates_day_of_week::day_of_week;  ← from dates.day-of-week ^1.0.0 · built alongside by fune
use super::todo_item::Recurrence;  ← from todo.item ^1.0.0 · built alongside by fune
use super::todo_item_validate_draft::recurrence_to_value;
use super::todo_parse_date_phrase::parse_date_phrase;  ← from todo.parse-date-phrase ^1.0.0 · built alongside by fune
use super::todo_parse_quick_add::parse_quick_add;  ← from todo.parse-quick-add ^1.0.0 · built alongside by fune

/// A Repeat box: an "every ..." phrase as todo.parse-quick-add reads it, then
/// optionally a start date. Anything else is None. The series starts on the
/// typed date, else on `due`, else where quick-add would start it; "every
/// <weekday>" and "every weekday" move the start forward to a day they allow.
///
/// # Panics
/// On a malformed or impossible `today` or `due` (dates.add-days's message).
pub fn parse_repeat(text: &str, due: Option<&str>, today: &str) -> Option<Recurrence> {
    add_days(today, 0);
    if let Some(date) = due {
        add_days(date, 0);
    }
    let words: Vec<&str> = text
        .split(|c: char| matches!(c, ' ' | '\t' | '\n' | '\r' | '\u{0B}' | '\u{0C}'))
        .filter(|w| !w.is_empty())
        .collect();
    if words.len() < 2 || words[0].to_ascii_lowercase() != "every" {
        return None;
    }

    // The repeat is 2 words ("every week") or 3 ("every 2 weeks"); no 2-word
    // form starts a 3-word one, so the first that parses as a bare repeat wins.
    let mut found: Option<(Recurrence, usize)> = None;
    for n in [2usize, 3] {
        if n > words.len() {
            break;
        }
        let draft = parse_quick_add(&words[..n].join(" "), today);
        if let Some(rule) = draft.recurrence {
            if draft.title.is_empty() {
                found = Some((rule, n));
                break;
            }
        }
    }
    let (repeat, length) = found?;

    let mut start: Option<String> = due.map(|d| d.to_string());
    if words.len() > length {
        start = Some(parse_date_phrase(&words[length..].join(" "), today)?);
    }
    let mut start = match start {
        None => return Some(repeat),
        Some(date) => date,
    };

    if repeat.frequency == "weekdays" {
        let day = day_of_week(&start);
        if day >= 6 {
            start = add_days(&start, 8 - day);
        }
    } else if repeat.frequency == "weekly" && length == 2 && parse_date_phrase(words[1], today).is_some() {
        // "every monday": quick-add anchored it on the next Monday, so that
        // anchor's weekday is the one the series keeps.
        let shift = (day_of_week(&repeat.anchor) - day_of_week(&start) + 7) % 7;
        start = add_days(&start, shift);
    }
    Some(Recurrence { anchor: start, ..repeat })
}

pub fn fune_vector(args: &[Value]) -> Value {
    let due = if args[1].is_null() { None } else { Some(args[1].as_str()) };
    recurrence_to_value(&parse_repeat(args[0].as_str(), due, args[2].as_str()))
}