1use clap::{
2 Parser, Subcommand, ValueEnum,
3 builder::styling::{AnsiColor, Styles},
4};
5use color_print::cstr;
6
7pub const STYLES: Styles = Styles::styled()
9 .header(AnsiColor::Green.on_default().bold())
10 .usage(AnsiColor::Green.on_default().bold())
11 .literal(AnsiColor::Blue.on_default().bold())
12 .placeholder(AnsiColor::Cyan.on_default());
13
14pub const AFTER_LONG_HELP: &str = cstr!(
16 r#"
17<green><bold>Environment Variables:</bold></green>
18 <bold><blue>TARDIS_FORMAT</blue></bold> Default output format or preset name.
19 <bold><blue>TARDIS_TIMEZONE</blue></bold> Default IANA time zone (e.g. America/Sao_Paulo).
20 <bold><blue>TARDIS_NOW</blue></bold> Override "now" (RFC 3339). Same as --now.
21
22<green><bold>Configuration File:</bold></green>
23 <blue><bold>$XDG_CONFIG_HOME</bold>/tardis/config.toml</blue>
24
25 if XDG_CONFIG_HOME is unset:
26 • Linux: ~/.config/tardis/config.toml
27 • macOS: ~/Library/Application Support/tardis/config.toml
28 • Windows: %APPDATA%\tardis\config.toml
29
30 The file is created automatically on first run and contains commented
31 examples for every field.
32
33<green><bold>Precedence:</bold></green>
34 CLI flags → env vars → config file
35
36For more info, visit <underline>https://github.com/hvpaiva/tardis-cli</underline>
37"#
38);
39
40pub const INPUT_HELP: &str = cstr!(
42 r#"
43<bold>A natural-language expression</bold> like <underline>"next Friday at 9:30"</underline>.
44If omitted and STDIN is a pipe, reads from it. If omitted in a terminal, defaults to <bold>"now"</bold>.
45
46Supports <bold>@<<epoch>></bold> syntax for Unix timestamps (e.g. <bold>@1719244800</bold>).
47Smart precision: seconds, milliseconds, microseconds, and nanoseconds auto-detected.
48
49Supports arithmetic (<bold>"tomorrow + 3 hours"</bold>), periods (<bold>"this week"</bold>), and boundaries (<bold>"eod"</bold>, <bold>"sow"</bold>).
50"#
51);
52
53const FORMAT_HELP: &str = cstr!(
54 r#"
55<bold>Output format.</bold>
56
57Accepts strftime patterns (e.g. <bold>"%Y‑%m‑%d"</bold>) or a named
58preset defined in the config file.
59
60Special values: <bold>"epoch"</bold> or <bold>"unix"</bold> output a Unix timestamp (seconds).
61
62Reference:
63<underline>https://github.com/hvpaiva/tardis-cli/blob/main/docs/FORMAT-SPECIFIERS.md</underline>
64
65If not provided, tries to read from <bold><blue>TARDIS_FORMAT</blue></bold> and
66falls back to the default format defined in the config file.
67"#
68);
69
70pub const TIMEZONE_HELP: &str = cstr!(
72 r#"
73<bold>Time‑zone to apply</bold> (IANA/Olson ID). If not provided, uses system local time.
74
75Examples: <italic>"UTC", "America/Sao_Paulo", "Europe/London".</italic>
76
77Reference:
78<underline>https://www.iana.org/time-zones</underline>
79
80If not provided, tries to read from <bold><blue>TARDIS_TIMEZONE</blue></bold> and
81falls back to the default time zone defined in the config file.
82"#
83);
84
85pub const NOW_HELP: &str = cstr!(
87 r#"
88Override "now". Format <bold>RFC 3339</bold>, e.g. <italic>2025‑06‑24T09:00:00Z</italic>.
89"#
90);
91
92const SKIP_ERRORS_HELP: &str = cstr!(
93 r#"
94<bold>Skip unparseable lines in batch mode</bold> instead of aborting.
95
96Errors are printed to stderr. Stdout emits an empty line for each
97failed input to <bold>preserve line alignment</bold> with the original input.
98
99Exit code is <bold>1</bold> if any line failed, <bold>0</bold> if all succeeded.
100"#
101);
102
103pub const ABOUT_HELP: &str = cstr!(
105 r#"
106<magenta>TARDIS — Time And Relative Date Input Simplifier</magenta>
107
108Translates natural-language time expressions into formatted datetimes.
109
110A lightweight CLI tool for converting human-readable date and time phrases
111like <bold>"next Friday at 2:00"</bold> or <bold>"in 3 days"</bold> into machine-usable output.
112"#
113);
114
115#[derive(Debug, Parser)]
117#[command(
118 name = "td",
119 about,
120 long_about = ABOUT_HELP,
121 version,
122 color = clap::ColorChoice::Auto,
123 after_long_help = AFTER_LONG_HELP,
124 after_help = cstr!("For more information, visit <underline>https://github.com/hvpaiva/tardis-cli</underline>"),
125 styles = STYLES,
126)]
127pub struct Cli {
128 #[arg(help = INPUT_HELP)]
129 pub input: Option<String>,
130
131 #[arg(value_name = "FMT", short, long, long_help = FORMAT_HELP)]
133 pub format: Option<String>,
134
135 #[arg(value_name = "TZ", short, long, long_help = TIMEZONE_HELP)]
137 pub timezone: Option<String>,
138
139 #[arg(value_name = "DATETIME", long, long_help = NOW_HELP)]
141 pub now: Option<String>,
142
143 #[arg(short, long)]
145 pub json: bool,
146
147 #[arg(short = 'n', long = "no-newline")]
149 pub no_newline: bool,
150
151 #[arg(short = 'v', long)]
153 pub verbose: bool,
154
155 #[arg(long, long_help = SKIP_ERRORS_HELP)]
157 pub skip_errors: bool,
158
159 #[command(subcommand)]
160 pub subcmd: Option<SubCmd>,
161}
162
163#[non_exhaustive]
165#[derive(Debug, Subcommand)]
166pub enum SubCmd {
167 Config {
169 #[command(subcommand)]
170 action: ConfigAction,
171 },
172 Completions {
174 shell: ShellType,
176 },
177 Diff(DiffArgs),
179 Convert(ConvertArgs),
181 Tz(TzArgs),
183 Info(InfoArgs),
185 Range(RangeArgs),
187}
188
189#[derive(Debug, Clone, ValueEnum)]
191pub enum DiffOutput {
192 Human,
194 Seconds,
196 Iso,
198}
199
200#[derive(Debug, clap::Args)]
202pub struct DiffArgs {
203 pub date1: String,
205 pub date2: String,
207 #[arg(short, long, value_enum, default_value = "human")]
209 pub output: DiffOutput,
210 #[arg(short, long)]
212 pub json: bool,
213 #[arg(short = 'n', long = "no-newline")]
215 pub no_newline: bool,
216 #[arg(long)]
218 pub now: Option<String>,
219 #[arg(short, long)]
221 pub timezone: Option<String>,
222 #[arg(short = 'v', long)]
224 pub verbose: bool,
225}
226
227#[derive(Debug, clap::Args)]
229pub struct ConvertArgs {
230 pub input: String,
232 #[arg(long)]
234 pub from: Option<String>,
235 #[arg(long)]
237 pub to: String,
238 #[arg(short, long)]
240 pub json: bool,
241 #[arg(short = 'n', long = "no-newline")]
243 pub no_newline: bool,
244 #[arg(long)]
246 pub now: Option<String>,
247 #[arg(short, long)]
249 pub timezone: Option<String>,
250 #[arg(short = 'v', long)]
252 pub verbose: bool,
253}
254
255#[derive(Debug, clap::Args)]
257pub struct TzArgs {
258 pub input: String,
260 #[arg(long)]
262 pub from: Option<String>,
263 #[arg(long)]
265 pub to: String,
266 #[arg(short, long)]
268 pub json: bool,
269 #[arg(short = 'n', long = "no-newline")]
271 pub no_newline: bool,
272 #[arg(long)]
274 pub now: Option<String>,
275 #[arg(short = 'v', long)]
277 pub verbose: bool,
278}
279
280#[derive(Debug, clap::Args)]
282pub struct InfoArgs {
283 #[arg(default_value = "now")]
285 pub input: String,
286 #[arg(short, long)]
288 pub json: bool,
289 #[arg(short = 'n', long = "no-newline")]
291 pub no_newline: bool,
292 #[arg(long)]
294 pub now: Option<String>,
295 #[arg(short, long)]
297 pub timezone: Option<String>,
298 #[arg(short = 'v', long)]
300 pub verbose: bool,
301}
302
303#[derive(Debug, clap::Args)]
305pub struct RangeArgs {
306 pub input: String,
308 #[arg(value_name = "FMT", short, long)]
310 pub format: Option<String>,
311 #[arg(value_name = "TZ", short, long)]
313 pub timezone: Option<String>,
314 #[arg(long)]
316 pub now: Option<String>,
317 #[arg(short = 'd', long, default_value = "\n")]
319 pub delimiter: String,
320 #[arg(short, long)]
322 pub json: bool,
323 #[arg(short = 'n', long = "no-newline")]
325 pub no_newline: bool,
326 #[arg(short = 'v', long)]
328 pub verbose: bool,
329}
330
331#[non_exhaustive]
333#[derive(Debug, Subcommand)]
334pub enum ConfigAction {
335 Path,
337 Show,
339 Edit,
341 Presets,
343}
344
345#[non_exhaustive]
347#[derive(Debug, Clone, ValueEnum)]
348pub enum ShellType {
349 Bash,
350 Zsh,
351 Fish,
352 Elvish,
353 Powershell,
354}