use clap::{
Parser, Subcommand, ValueEnum,
builder::styling::{AnsiColor, Styles},
};
use color_print::cstr;
pub const STYLES: Styles = Styles::styled()
.header(AnsiColor::Green.on_default().bold())
.usage(AnsiColor::Green.on_default().bold())
.literal(AnsiColor::Blue.on_default().bold())
.placeholder(AnsiColor::Cyan.on_default());
pub const AFTER_LONG_HELP: &str = cstr!(
r#"
<green><bold>Environment Variables:</bold></green>
<bold><blue>TARDIS_FORMAT</blue></bold> Default output format or preset name.
<bold><blue>TARDIS_TIMEZONE</blue></bold> Default IANA time zone (e.g. America/Sao_Paulo).
<bold><blue>TARDIS_NOW</blue></bold> Override "now" (RFC 3339). Same as --now.
<green><bold>Configuration File:</bold></green>
<blue><bold>$XDG_CONFIG_HOME</bold>/tardis/config.toml</blue>
if XDG_CONFIG_HOME is unset:
• Linux: ~/.config/tardis/config.toml
• macOS: ~/Library/Application Support/tardis/config.toml
• Windows: %APPDATA%\tardis\config.toml
The file is created automatically on first run and contains commented
examples for every field.
<green><bold>Precedence:</bold></green>
CLI flags → env vars → config file
For more info, visit <underline>https://github.com/hvpaiva/tardis-cli</underline>
"#
);
pub const INPUT_HELP: &str = cstr!(
r#"
<bold>A natural-language expression</bold> like <underline>"next Friday at 9:30"</underline>.
If omitted and STDIN is a pipe, reads from it. If omitted in a terminal, defaults to <bold>"now"</bold>.
Supports <bold>@<<epoch>></bold> syntax for Unix timestamps (e.g. <bold>@1719244800</bold>).
Smart precision: seconds, milliseconds, microseconds, and nanoseconds auto-detected.
Supports arithmetic (<bold>"tomorrow + 3 hours"</bold>), periods (<bold>"this week"</bold>), and boundaries (<bold>"eod"</bold>, <bold>"sow"</bold>).
"#
);
const FORMAT_HELP: &str = cstr!(
r#"
<bold>Output format.</bold>
Accepts strftime patterns (e.g. <bold>"%Y‑%m‑%d"</bold>) or a named
preset defined in the config file.
Special values: <bold>"epoch"</bold> or <bold>"unix"</bold> output a Unix timestamp (seconds).
Reference:
<underline>https://github.com/hvpaiva/tardis-cli/blob/main/docs/FORMAT-SPECIFIERS.md</underline>
If not provided, tries to read from <bold><blue>TARDIS_FORMAT</blue></bold> and
falls back to the default format defined in the config file.
"#
);
pub const TIMEZONE_HELP: &str = cstr!(
r#"
<bold>Time‑zone to apply</bold> (IANA/Olson ID). If not provided, uses system local time.
Examples: <italic>"UTC", "America/Sao_Paulo", "Europe/London".</italic>
Reference:
<underline>https://www.iana.org/time-zones</underline>
If not provided, tries to read from <bold><blue>TARDIS_TIMEZONE</blue></bold> and
falls back to the default time zone defined in the config file.
"#
);
pub const NOW_HELP: &str = cstr!(
r#"
Override "now". Format <bold>RFC 3339</bold>, e.g. <italic>2025‑06‑24T09:00:00Z</italic>.
"#
);
const SKIP_ERRORS_HELP: &str = cstr!(
r#"
<bold>Skip unparseable lines in batch mode</bold> instead of aborting.
Errors are printed to stderr. Stdout emits an empty line for each
failed input to <bold>preserve line alignment</bold> with the original input.
Exit code is <bold>1</bold> if any line failed, <bold>0</bold> if all succeeded.
"#
);
pub const ABOUT_HELP: &str = cstr!(
r#"
<magenta>TARDIS — Time And Relative Date Input Simplifier</magenta>
Translates natural-language time expressions into formatted datetimes.
A lightweight CLI tool for converting human-readable date and time phrases
like <bold>"next Friday at 2:00"</bold> or <bold>"in 3 days"</bold> into machine-usable output.
"#
);
#[derive(Debug, Parser)]
#[command(
name = "td",
about,
long_about = ABOUT_HELP,
version,
color = clap::ColorChoice::Auto,
after_long_help = AFTER_LONG_HELP,
after_help = cstr!("For more information, visit <underline>https://github.com/hvpaiva/tardis-cli</underline>"),
styles = STYLES,
)]
pub struct Cli {
#[arg(help = INPUT_HELP)]
pub input: Option<String>,
#[arg(value_name = "FMT", short, long, long_help = FORMAT_HELP)]
pub format: Option<String>,
#[arg(value_name = "TZ", short, long, long_help = TIMEZONE_HELP)]
pub timezone: Option<String>,
#[arg(value_name = "DATETIME", long, long_help = NOW_HELP)]
pub now: Option<String>,
#[arg(short, long)]
pub json: bool,
#[arg(short = 'n', long = "no-newline")]
pub no_newline: bool,
#[arg(short = 'v', long)]
pub verbose: bool,
#[arg(long, long_help = SKIP_ERRORS_HELP)]
pub skip_errors: bool,
#[command(subcommand)]
pub subcmd: Option<SubCmd>,
}
#[non_exhaustive]
#[derive(Debug, Subcommand)]
pub enum SubCmd {
Config {
#[command(subcommand)]
action: ConfigAction,
},
Completions {
shell: ShellType,
},
Diff(DiffArgs),
Convert(ConvertArgs),
Tz(TzArgs),
Info(InfoArgs),
Range(RangeArgs),
}
#[derive(Debug, Clone, ValueEnum)]
pub enum DiffOutput {
Human,
Seconds,
Iso,
}
#[derive(Debug, clap::Args)]
pub struct DiffArgs {
pub date1: String,
pub date2: String,
#[arg(short, long, value_enum, default_value = "human")]
pub output: DiffOutput,
#[arg(short, long)]
pub json: bool,
#[arg(short = 'n', long = "no-newline")]
pub no_newline: bool,
#[arg(long)]
pub now: Option<String>,
#[arg(short, long)]
pub timezone: Option<String>,
#[arg(short = 'v', long)]
pub verbose: bool,
}
#[derive(Debug, clap::Args)]
pub struct ConvertArgs {
pub input: String,
#[arg(long)]
pub from: Option<String>,
#[arg(long)]
pub to: String,
#[arg(short, long)]
pub json: bool,
#[arg(short = 'n', long = "no-newline")]
pub no_newline: bool,
#[arg(long)]
pub now: Option<String>,
#[arg(short, long)]
pub timezone: Option<String>,
#[arg(short = 'v', long)]
pub verbose: bool,
}
#[derive(Debug, clap::Args)]
pub struct TzArgs {
pub input: String,
#[arg(long)]
pub from: Option<String>,
#[arg(long)]
pub to: String,
#[arg(short, long)]
pub json: bool,
#[arg(short = 'n', long = "no-newline")]
pub no_newline: bool,
#[arg(long)]
pub now: Option<String>,
#[arg(short = 'v', long)]
pub verbose: bool,
}
#[derive(Debug, clap::Args)]
pub struct InfoArgs {
#[arg(default_value = "now")]
pub input: String,
#[arg(short, long)]
pub json: bool,
#[arg(short = 'n', long = "no-newline")]
pub no_newline: bool,
#[arg(long)]
pub now: Option<String>,
#[arg(short, long)]
pub timezone: Option<String>,
#[arg(short = 'v', long)]
pub verbose: bool,
}
#[derive(Debug, clap::Args)]
pub struct RangeArgs {
pub input: String,
#[arg(value_name = "FMT", short, long)]
pub format: Option<String>,
#[arg(value_name = "TZ", short, long)]
pub timezone: Option<String>,
#[arg(long)]
pub now: Option<String>,
#[arg(short = 'd', long, default_value = "\n")]
pub delimiter: String,
#[arg(short, long)]
pub json: bool,
#[arg(short = 'n', long = "no-newline")]
pub no_newline: bool,
#[arg(short = 'v', long)]
pub verbose: bool,
}
#[non_exhaustive]
#[derive(Debug, Subcommand)]
pub enum ConfigAction {
Path,
Show,
Edit,
Presets,
}
#[non_exhaustive]
#[derive(Debug, Clone, ValueEnum)]
pub enum ShellType {
Bash,
Zsh,
Fish,
Elvish,
Powershell,
}