tix-tracker 0.12.0

A terminal issue tracker: markdown + YAML frontmatter issues with a kanban TUI and CLI
Documentation
#[allow(clippy::wildcard_imports)]
use super::*;

/// What `tix recurring` does.
///
/// Every read-only subcommand resolves its date explicitly (`--date`), and every
/// mutating one requires the confirmation flag it documents. The subcommands are a
/// thin adapter: they parse, call one service, and print.
#[derive(Debug, Clone, Subcommand)]
pub enum RecurringAction {
    /// List every series, one tab-separated row per series
    ///
    /// Columns: `id`, `name`, `state`, `next_cursor`, `skipped`.
    List,
    /// Show one series: identity, rule, state, cursor, skipped dates, occurrences
    Show {
        /// Stable series id, or its unique display name
        series: String,
    },
    /// Create a series: the issue that *is* the definition
    ///
    /// The issue's own fields are the template an occurrence is created from, so
    /// `--title`, `--labels` and the rest are also what each occurrence inherits.
    /// The rule is validated **before** the issue is written.
    Create(RecurringCreateArgs),
    /// Change a series' name and/or rule, keeping its stable id
    ///
    /// Only the flags you name change; the rest of the rule is carried over, so
    /// `tix recurring edit <id> --name X` is a rename and nothing else.
    Edit {
        /// Stable series id, or its unique display name
        series: String,
        #[command(flatten)]
        args: RecurringEditArgs,
    },
    /// Report what is due, read-only, for an explicit date
    Detect {
        /// Evaluate against this `YYYY-MM-DD` date instead of today
        #[arg(long, value_name = "YYYY-MM-DD")]
        date: Option<String>,
        /// Timezone policy to carry on the context, e.g. `+02:00`
        #[arg(long, value_name = "TZ")]
        timezone: Option<String>,
    },
    /// Create the occurrence for one due date; requires `--yes`
    ///
    /// Idempotent: the occurrence key is checked first, so a repeated call is a
    /// no-op and never a second issue.
    Generate {
        /// Stable series id, or its unique display name
        series: String,
        /// The occurrence date, `YYYY-MM-DD`
        #[arg(long, value_name = "YYYY-MM-DD")]
        date: String,
        #[command(flatten)]
        confirmation: RecurringLifecycleArgs,
    },
    /// Suspend generation until an explicit resume
    Pause {
        /// Stable series id, or its unique display name
        series: String,
        #[command(flatten)]
        confirmation: RecurringLifecycleArgs,
    },
    /// Resume a paused series; never inferred from a refresh
    Resume {
        /// Stable series id, or its unique display name
        series: String,
        #[command(flatten)]
        confirmation: RecurringLifecycleArgs,
    },
    /// End a series: terminal for generation
    End {
        /// Stable series id, or its unique display name
        series: String,
        #[command(flatten)]
        confirmation: RecurringLifecycleArgs,
    },
    /// Record one occurrence date as skipped, so it is never generated
    Skip {
        /// Stable series id, or its unique display name
        series: String,
        /// The occurrence date, `YYYY-MM-DD`
        #[arg(long, value_name = "YYYY-MM-DD")]
        date: String,
        #[command(flatten)]
        confirmation: RecurringLifecycleArgs,
    },
    /// Move the candidate past a date without recording it as skipped
    Defer {
        /// Stable series id, or its unique display name
        series: String,
        /// The occurrence date, `YYYY-MM-DD`
        #[arg(long, value_name = "YYYY-MM-DD")]
        date: String,
        #[command(flatten)]
        confirmation: RecurringLifecycleArgs,
    },
    /// Stop offering one occurrence date: recorded, like `skip`
    Ignore {
        /// Stable series id, or its unique display name
        series: String,
        /// The occurrence date, `YYYY-MM-DD`
        #[arg(long, value_name = "YYYY-MM-DD")]
        date: String,
        #[command(flatten)]
        confirmation: RecurringLifecycleArgs,
    },
}

/// The rule flags `tix recurring create` and `edit` share.
#[derive(Debug, Clone, Args)]
pub struct RecurringRuleArgs {
    /// Family: `daily`, `weekly`, `monthly`, `interval`, `cron-like`
    #[arg(long, value_name = "KIND")]
    pub rule: Option<String>,
    /// Schedule origin, `YYYY-MM-DD` (required by every family)
    #[arg(long, value_name = "YYYY-MM-DD")]
    pub anchor: Option<String>,
    /// Cadence, where the family takes one (1-1000)
    #[arg(long, value_name = "N")]
    pub interval: Option<u32>,
    /// What an `interval` rule counts in: `days`, `weeks`, `months`
    #[arg(long, value_name = "UNIT")]
    pub unit: Option<String>,
    /// Cron-like expression: `@daily`, `@weekly`, `@monthly`
    #[arg(long, value_name = "EXPR")]
    pub expression: Option<String>,
    /// Timezone policy, e.g. `+02:00`
    #[arg(long, value_name = "TZ")]
    pub timezone: Option<String>,
}

impl RecurringRuleArgs {
    /// Whether the caller named any rule field at all.
    pub const fn is_any(&self) -> bool {
        self.rule.is_some()
            || self.anchor.is_some()
            || self.interval.is_some()
            || self.unit.is_some()
            || self.expression.is_some()
            || self.timezone.is_some()
    }
}

/// The confirmation every mutating `tix recurring` subcommand takes.
#[derive(Debug, Clone, Args)]
pub struct RecurringLifecycleArgs {
    /// Confirm the action without prompting
    #[arg(long)]
    pub yes: bool,
    /// Why; recorded in the series' History
    #[arg(long, value_name = "TEXT")]
    pub reason: Option<String>,
}

/// The fields of the definition issue plus its rule.
#[derive(Debug, Clone, Args)]
pub struct RecurringCreateArgs {
    /// Title of the series issue (and the default series name)
    #[arg(long, value_name = "TITLE")]
    pub title: String,
    /// Body an occurrence inherits
    #[arg(long, value_name = "BODY")]
    pub body: Option<String>,
    /// Column the definition (and its occurrences) starts in
    #[arg(long, value_name = "STATUS")]
    pub status: Option<String>,
    /// Priority: `low`, `medium`, `high`
    #[arg(long, value_name = "PRIORITY")]
    pub priority: Option<String>,
    /// Labels, comma-separated
    #[arg(long, value_name = "LABELS")]
    pub labels: Option<String>,
    /// Assignee (`@name`)
    #[arg(long, value_name = "ASSIGNEE")]
    pub assignee: Option<String>,
    /// Display name; defaults to the title
    #[arg(long, value_name = "NAME")]
    pub name: Option<String>,
    /// Stable series id; generated when omitted
    #[arg(long, value_name = "ID")]
    pub id: Option<String>,
    #[command(flatten)]
    pub rule: RecurringRuleArgs,
}

/// What `tix recurring edit` may change.
#[derive(Debug, Clone, Args)]
pub struct RecurringEditArgs {
    /// New display name; omitted means keep the current one
    #[arg(long, value_name = "NAME")]
    pub name: Option<String>,
    #[command(flatten)]
    pub rule: RecurringRuleArgs,
}