Skip to main content

tardis_cli/
cli_defs.rs

1use clap::{
2    Parser, Subcommand, ValueEnum,
3    builder::styling::{AnsiColor, Styles},
4};
5use color_print::cstr;
6
7/// Clap color styles for CLI help output.
8pub 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
14/// Extended help text displayed after long help output.
15pub 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
40/// Help text for the positional input argument.
41pub 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
70/// Long help text for the `--timezone` flag.
71pub 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
85/// Long help text for the `--now` flag.
86pub 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
103/// Long about text displayed in `--help` output.
104pub 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/// TARDIS — Time And Relative Date Input Simplifier
116#[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    /// Output format.
132    #[arg(value_name = "FMT", short, long, long_help = FORMAT_HELP)]
133    pub format: Option<String>,
134
135    /// Time-zone to apply (IANA/Olson ID). If not provided, uses system local time.
136    #[arg(value_name = "TZ", short, long, long_help = TIMEZONE_HELP)]
137    pub timezone: Option<String>,
138
139    /// Override "now". Format **RFC 3339**, e.g. 2025-06-24T09:00:00Z.
140    #[arg(value_name = "DATETIME", long, long_help = NOW_HELP)]
141    pub now: Option<String>,
142
143    /// Output as JSON instead of plain text.
144    #[arg(short, long)]
145    pub json: bool,
146
147    /// Suppress trailing newline.
148    #[arg(short = 'n', long = "no-newline")]
149    pub no_newline: bool,
150
151    /// Print verbose diagnostics to stderr (config, parse steps, timing).
152    #[arg(short = 'v', long)]
153    pub verbose: bool,
154
155    /// Skip unparseable lines in batch mode.
156    #[arg(long, long_help = SKIP_ERRORS_HELP)]
157    pub skip_errors: bool,
158
159    #[command(subcommand)]
160    pub subcmd: Option<SubCmd>,
161}
162
163/// Available subcommands for the `td` binary.
164#[non_exhaustive]
165#[derive(Debug, Subcommand)]
166pub enum SubCmd {
167    /// Manage configuration file.
168    Config {
169        #[command(subcommand)]
170        action: ConfigAction,
171    },
172    /// Generate shell completions.
173    Completions {
174        /// Shell to generate completions for.
175        shell: ShellType,
176    },
177    /// Compute the difference between two dates.
178    Diff(DiffArgs),
179    /// Convert a date between formats.
180    Convert(ConvertArgs),
181    /// Convert a datetime to a different timezone.
182    Tz(TzArgs),
183    /// Display calendar metadata for a date.
184    Info(InfoArgs),
185    /// Expand a date expression into a start/end range.
186    Range(RangeArgs),
187}
188
189/// Output format for diff results.
190#[derive(Debug, Clone, ValueEnum)]
191pub enum DiffOutput {
192    /// Human-readable duration (e.g., "2 months, 14 days")
193    Human,
194    /// Total seconds between dates
195    Seconds,
196    /// ISO 8601 duration format (e.g., P2M14D)
197    Iso,
198}
199
200/// Arguments for the `diff` subcommand.
201#[derive(Debug, clap::Args)]
202pub struct DiffArgs {
203    /// First date expression
204    pub date1: String,
205    /// Second date expression
206    pub date2: String,
207    /// Select which diff format to output
208    #[arg(short, long, value_enum, default_value = "human")]
209    pub output: DiffOutput,
210    /// Output as JSON
211    #[arg(short, long)]
212    pub json: bool,
213    /// Suppress trailing newline
214    #[arg(short = 'n', long = "no-newline")]
215    pub no_newline: bool,
216    /// Override "now" reference (RFC 3339)
217    #[arg(long)]
218    pub now: Option<String>,
219    /// Time-zone for resolution
220    #[arg(short, long)]
221    pub timezone: Option<String>,
222    /// Print verbose diagnostics to stderr
223    #[arg(short = 'v', long)]
224    pub verbose: bool,
225}
226
227/// Arguments for the `convert` subcommand.
228#[derive(Debug, clap::Args)]
229pub struct ConvertArgs {
230    /// Input date expression or formatted date string
231    pub input: String,
232    /// Input format (strptime pattern or preset name). Auto-detected if omitted.
233    #[arg(long)]
234    pub from: Option<String>,
235    /// Output format (strftime pattern, preset name, or builtin: iso8601, rfc3339, rfc2822, epoch, unix)
236    #[arg(long)]
237    pub to: String,
238    /// Output as JSON
239    #[arg(short, long)]
240    pub json: bool,
241    /// Suppress trailing newline
242    #[arg(short = 'n', long = "no-newline")]
243    pub no_newline: bool,
244    /// Override "now" reference (RFC 3339)
245    #[arg(long)]
246    pub now: Option<String>,
247    /// Time-zone for resolution
248    #[arg(short, long)]
249    pub timezone: Option<String>,
250    /// Print verbose diagnostics to stderr
251    #[arg(short = 'v', long)]
252    pub verbose: bool,
253}
254
255/// Arguments for the `tz` subcommand.
256#[derive(Debug, clap::Args)]
257pub struct TzArgs {
258    /// Input datetime expression
259    pub input: String,
260    /// Source timezone (auto-detected from system or input if omitted)
261    #[arg(long)]
262    pub from: Option<String>,
263    /// Target timezone (required, IANA name like "America/Sao_Paulo")
264    #[arg(long)]
265    pub to: String,
266    /// Output as JSON
267    #[arg(short, long)]
268    pub json: bool,
269    /// Suppress trailing newline
270    #[arg(short = 'n', long = "no-newline")]
271    pub no_newline: bool,
272    /// Override "now" reference (RFC 3339)
273    #[arg(long)]
274    pub now: Option<String>,
275    /// Print verbose diagnostics to stderr
276    #[arg(short = 'v', long)]
277    pub verbose: bool,
278}
279
280/// Arguments for the `info` subcommand.
281#[derive(Debug, clap::Args)]
282pub struct InfoArgs {
283    /// Date expression to inspect (defaults to "now")
284    #[arg(default_value = "now")]
285    pub input: String,
286    /// Output as JSON
287    #[arg(short, long)]
288    pub json: bool,
289    /// Suppress trailing newline
290    #[arg(short = 'n', long = "no-newline")]
291    pub no_newline: bool,
292    /// Override "now" reference (RFC 3339)
293    #[arg(long)]
294    pub now: Option<String>,
295    /// Time-zone for resolution
296    #[arg(short, long)]
297    pub timezone: Option<String>,
298    /// Print verbose diagnostics to stderr
299    #[arg(short = 'v', long)]
300    pub verbose: bool,
301}
302
303/// Arguments for the `range` subcommand.
304#[derive(Debug, clap::Args)]
305pub struct RangeArgs {
306    /// Date expression to expand as range
307    pub input: String,
308    /// Output format (strftime pattern or preset name)
309    #[arg(value_name = "FMT", short, long)]
310    pub format: Option<String>,
311    /// Time-zone to apply (IANA/Olson ID)
312    #[arg(value_name = "TZ", short, long)]
313    pub timezone: Option<String>,
314    /// Override "now" reference (RFC 3339)
315    #[arg(long)]
316    pub now: Option<String>,
317    /// Delimiter between start and end in plain-text output (default: newline).
318    #[arg(short = 'd', long, default_value = "\n")]
319    pub delimiter: String,
320    /// Output as JSON
321    #[arg(short, long)]
322    pub json: bool,
323    /// Suppress trailing newline
324    #[arg(short = 'n', long = "no-newline")]
325    pub no_newline: bool,
326    /// Print verbose diagnostics to stderr
327    #[arg(short = 'v', long)]
328    pub verbose: bool,
329}
330
331/// Subactions for `td config`.
332#[non_exhaustive]
333#[derive(Debug, Subcommand)]
334pub enum ConfigAction {
335    /// Print the path to the configuration file.
336    Path,
337    /// Display the effective configuration.
338    Show,
339    /// Open the configuration file in $EDITOR.
340    Edit,
341    /// List all available format presets.
342    Presets,
343}
344
345/// Supported shell types for completion generation.
346#[non_exhaustive]
347#[derive(Debug, Clone, ValueEnum)]
348pub enum ShellType {
349    Bash,
350    Zsh,
351    Fish,
352    Elvish,
353    Powershell,
354}