cronspeak 0.1.1

Convert cron expressions into clear, human-readable English descriptions (like cronstrue / cron-descriptor). Zero-dependency, no_std.
Documentation
//! # cronspeak — say cron out loud
//!
//! Convert cron expressions into clear, human-readable English, the way
//! [`cronstrue`] (JavaScript) and [`cron-descriptor`] (Python) do.
//!
//! ```
//! assert_eq!(
//!     cronspeak::describe("0 9 * * 1-5").unwrap(),
//!     "At 09:00 AM, Monday through Friday"
//! );
//! assert_eq!(cronspeak::describe("*/15 * * * *").unwrap(), "Every 15 minutes");
//! assert_eq!(cronspeak::describe("@daily").unwrap(), "At 12:00 AM");
//! ```
//!
//! Pure logic, zero dependencies, `#![no_std]` (needs only `alloc`).
//!
//! ## Supported syntax
//!
//! Standard 5-field cron (`minute hour day-of-month month day-of-week`) with
//! `*`, single values, lists (`a,b`), ranges (`a-b`), and steps (`*/n`, `a-b/n`,
//! `a/n`). Month and day-of-week accept names (`JAN`…`DEC`, `SUN`…`SAT`,
//! case-insensitive). The macros `@yearly`/`@annually`, `@monthly`, `@weekly`,
//! `@daily`/`@midnight`, and `@hourly` are also recognized.
//!
//! [`cronstrue`]: https://github.com/bradymholt/cRonstrue
//! [`cron-descriptor`]: https://pypi.org/project/cron-descriptor/

#![no_std]
#![doc(html_root_url = "https://docs.rs/cronspeak/0.1.0")]

extern crate alloc;

use alloc::string::String;

mod describe;
mod names;
mod parse;
mod segment;

pub use parse::CronError;

/// Clock style used when rendering times of day.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum TimeFormat {
    /// 12-hour clock with `AM`/`PM` (e.g. `09:00 AM`). The default.
    #[default]
    H12,
    /// 24-hour clock (e.g. `09:00`).
    H24,
}

/// Options controlling how a cron expression is described.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Options {
    time_format: TimeFormat,
}

impl Options {
    /// Create options with the defaults (12-hour clock).
    #[must_use]
    pub const fn new() -> Self {
        Self {
            time_format: TimeFormat::H12,
        }
    }

    /// Set the clock style used for times of day.
    #[must_use]
    pub const fn time_format(mut self, format: TimeFormat) -> Self {
        self.time_format = format;
        self
    }

    pub(crate) const fn format(self) -> TimeFormat {
        self.time_format
    }
}

/// Describe a cron expression in human-readable English using default options.
///
/// ```
/// assert_eq!(cronspeak::describe("0 0 1 * *").unwrap(), "At 12:00 AM, on day 1 of the month");
/// ```
///
/// # Errors
///
/// Returns [`CronError`] if the expression is empty, has the wrong number of
/// fields, or contains an invalid value, range, or step.
pub fn describe(expr: &str) -> Result<String, CronError> {
    describe_with(expr, &Options::new())
}

/// Describe a cron expression using the given [`Options`].
///
/// # Errors
///
/// Returns [`CronError`] for the same reasons as [`describe`].
pub fn describe_with(expr: &str, options: &Options) -> Result<String, CronError> {
    let schedule = parse::parse(expr)?;
    Ok(describe::describe(&schedule, *options))
}