tix/cli/cli_recurring.rs
1#[allow(clippy::wildcard_imports)]
2use super::*;
3
4/// What `tix recurring` does.
5///
6/// Every read-only subcommand resolves its date explicitly (`--date`), and every
7/// mutating one requires the confirmation flag it documents. The subcommands are a
8/// thin adapter: they parse, call one service, and print.
9#[derive(Debug, Clone, Subcommand)]
10pub enum RecurringAction {
11 /// List every series, one tab-separated row per series
12 ///
13 /// Columns: `id`, `name`, `state`, `next_cursor`, `skipped`.
14 List,
15 /// Show one series: identity, rule, state, cursor, skipped dates, occurrences
16 Show {
17 /// Stable series id, or its unique display name
18 series: String,
19 },
20 /// Create a series: the issue that *is* the definition
21 ///
22 /// The issue's own fields are the template an occurrence is created from, so
23 /// `--title`, `--labels` and the rest are also what each occurrence inherits.
24 /// The rule is validated **before** the issue is written.
25 Create(RecurringCreateArgs),
26 /// Change a series' name and/or rule, keeping its stable id
27 ///
28 /// Only the flags you name change; the rest of the rule is carried over, so
29 /// `tix recurring edit <id> --name X` is a rename and nothing else.
30 Edit {
31 /// Stable series id, or its unique display name
32 series: String,
33 #[command(flatten)]
34 args: RecurringEditArgs,
35 },
36 /// Report what is due, read-only, for an explicit date
37 Detect {
38 /// Evaluate against this `YYYY-MM-DD` date instead of today
39 #[arg(long, value_name = "YYYY-MM-DD")]
40 date: Option<String>,
41 /// Timezone policy to carry on the context, e.g. `+02:00`
42 #[arg(long, value_name = "TZ")]
43 timezone: Option<String>,
44 },
45 /// Create the occurrence for one due date; requires `--yes`
46 ///
47 /// Idempotent: the occurrence key is checked first, so a repeated call is a
48 /// no-op and never a second issue.
49 Generate {
50 /// Stable series id, or its unique display name
51 series: String,
52 /// The occurrence date, `YYYY-MM-DD`
53 #[arg(long, value_name = "YYYY-MM-DD")]
54 date: String,
55 #[command(flatten)]
56 confirmation: RecurringLifecycleArgs,
57 },
58 /// Suspend generation until an explicit resume
59 Pause {
60 /// Stable series id, or its unique display name
61 series: String,
62 #[command(flatten)]
63 confirmation: RecurringLifecycleArgs,
64 },
65 /// Resume a paused series; never inferred from a refresh
66 Resume {
67 /// Stable series id, or its unique display name
68 series: String,
69 #[command(flatten)]
70 confirmation: RecurringLifecycleArgs,
71 },
72 /// End a series: terminal for generation
73 End {
74 /// Stable series id, or its unique display name
75 series: String,
76 #[command(flatten)]
77 confirmation: RecurringLifecycleArgs,
78 },
79 /// Record one occurrence date as skipped, so it is never generated
80 Skip {
81 /// Stable series id, or its unique display name
82 series: String,
83 /// The occurrence date, `YYYY-MM-DD`
84 #[arg(long, value_name = "YYYY-MM-DD")]
85 date: String,
86 #[command(flatten)]
87 confirmation: RecurringLifecycleArgs,
88 },
89 /// Move the candidate past a date without recording it as skipped
90 Defer {
91 /// Stable series id, or its unique display name
92 series: String,
93 /// The occurrence date, `YYYY-MM-DD`
94 #[arg(long, value_name = "YYYY-MM-DD")]
95 date: String,
96 #[command(flatten)]
97 confirmation: RecurringLifecycleArgs,
98 },
99 /// Stop offering one occurrence date: recorded, like `skip`
100 Ignore {
101 /// Stable series id, or its unique display name
102 series: String,
103 /// The occurrence date, `YYYY-MM-DD`
104 #[arg(long, value_name = "YYYY-MM-DD")]
105 date: String,
106 #[command(flatten)]
107 confirmation: RecurringLifecycleArgs,
108 },
109}
110
111/// The rule flags `tix recurring create` and `edit` share.
112#[derive(Debug, Clone, Args)]
113pub struct RecurringRuleArgs {
114 /// Family: `daily`, `weekly`, `monthly`, `interval`, `cron-like`
115 #[arg(long, value_name = "KIND")]
116 pub rule: Option<String>,
117 /// Schedule origin, `YYYY-MM-DD` (required by every family)
118 #[arg(long, value_name = "YYYY-MM-DD")]
119 pub anchor: Option<String>,
120 /// Cadence, where the family takes one (1-1000)
121 #[arg(long, value_name = "N")]
122 pub interval: Option<u32>,
123 /// What an `interval` rule counts in: `days`, `weeks`, `months`
124 #[arg(long, value_name = "UNIT")]
125 pub unit: Option<String>,
126 /// Cron-like expression: `@daily`, `@weekly`, `@monthly`
127 #[arg(long, value_name = "EXPR")]
128 pub expression: Option<String>,
129 /// Timezone policy, e.g. `+02:00`
130 #[arg(long, value_name = "TZ")]
131 pub timezone: Option<String>,
132}
133
134impl RecurringRuleArgs {
135 /// Whether the caller named any rule field at all.
136 pub const fn is_any(&self) -> bool {
137 self.rule.is_some()
138 || self.anchor.is_some()
139 || self.interval.is_some()
140 || self.unit.is_some()
141 || self.expression.is_some()
142 || self.timezone.is_some()
143 }
144}
145
146/// The confirmation every mutating `tix recurring` subcommand takes.
147#[derive(Debug, Clone, Args)]
148pub struct RecurringLifecycleArgs {
149 /// Confirm the action without prompting
150 #[arg(long)]
151 pub yes: bool,
152 /// Why; recorded in the series' History
153 #[arg(long, value_name = "TEXT")]
154 pub reason: Option<String>,
155}
156
157/// The fields of the definition issue plus its rule.
158#[derive(Debug, Clone, Args)]
159pub struct RecurringCreateArgs {
160 /// Title of the series issue (and the default series name)
161 #[arg(long, value_name = "TITLE")]
162 pub title: String,
163 /// Body an occurrence inherits
164 #[arg(long, value_name = "BODY")]
165 pub body: Option<String>,
166 /// Column the definition (and its occurrences) starts in
167 #[arg(long, value_name = "STATUS")]
168 pub status: Option<String>,
169 /// Priority: `low`, `medium`, `high`
170 #[arg(long, value_name = "PRIORITY")]
171 pub priority: Option<String>,
172 /// Labels, comma-separated
173 #[arg(long, value_name = "LABELS")]
174 pub labels: Option<String>,
175 /// Assignee (`@name`)
176 #[arg(long, value_name = "ASSIGNEE")]
177 pub assignee: Option<String>,
178 /// Display name; defaults to the title
179 #[arg(long, value_name = "NAME")]
180 pub name: Option<String>,
181 /// Stable series id; generated when omitted
182 #[arg(long, value_name = "ID")]
183 pub id: Option<String>,
184 #[command(flatten)]
185 pub rule: RecurringRuleArgs,
186}
187
188/// What `tix recurring edit` may change.
189#[derive(Debug, Clone, Args)]
190pub struct RecurringEditArgs {
191 /// New display name; omitted means keep the current one
192 #[arg(long, value_name = "NAME")]
193 pub name: Option<String>,
194 #[command(flatten)]
195 pub rule: RecurringRuleArgs,
196}