1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#[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,
}