diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-12-04 14:17:38 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-12-04 13:17:38 +0000 |
| commit | bf1c7db6fcee967ea802a19430c1e27444e16da1 (patch) | |
| tree | 17e9228d3844fa964e88ee88f6fa4b8a2ca9a3c1 /crates/typst-cli/src/args.rs | |
| parent | 884c02872ce9d7432c3b67a920011cf385ce70b2 (diff) | |
Clean up CLI argument parsing (#5521)
Diffstat (limited to 'crates/typst-cli/src/args.rs')
| -rw-r--r-- | crates/typst-cli/src/args.rs | 598 |
1 files changed, 316 insertions, 282 deletions
diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index d9ecc921..7faaf601 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -33,7 +33,7 @@ const AFTER_HELP: &str = color_print::cstr!("\ <s>Forum for questions:</> https://forum.typst.app/ "); -/// The Typst compiler +/// The Typst compiler. #[derive(Debug, Clone, Parser)] #[clap( name = "typst", @@ -44,24 +44,16 @@ const AFTER_HELP: &str = color_print::cstr!("\ max_term_width = 80, )] pub struct CliArguments { - /// The command to run + /// The command to run. #[command(subcommand)] pub command: Command, - /// Set when to use color. - /// auto = use color if a capable terminal is detected - #[clap( - long, - value_name = "WHEN", - require_equals = true, - num_args = 0..=1, - default_value = "auto", - default_missing_value = "always", - )] + /// Whether to use color. When set to `auto` if the terminal to supports it. + #[clap(long, default_value_t = ColorChoice::Auto, default_missing_value = "always")] pub color: ColorChoice, /// Path to a custom CA certificate to use when making network requests. - #[clap(long = "cert", env = "TYPST_CERT")] + #[clap(long, env = "TYPST_CERT")] pub cert: Option<PathBuf>, } @@ -69,109 +61,48 @@ pub struct CliArguments { #[derive(Debug, Clone, Subcommand)] #[command()] pub enum Command { - /// Compiles an input file into a supported output format + /// Compiles an input file into a supported output format. #[command(visible_alias = "c")] Compile(CompileCommand), - /// Watches an input file and recompiles on changes + /// Watches an input file and recompiles on changes. #[command(visible_alias = "w")] - Watch(CompileCommand), + Watch(WatchCommand), - /// Initializes a new project from a template + /// Initializes a new project from a template. Init(InitCommand), - /// Processes an input file to extract provided metadata + /// Processes an input file to extract provided metadata. Query(QueryCommand), - /// Lists all discovered fonts in system and custom font paths + /// Lists all discovered fonts in system and custom font paths. Fonts(FontsCommand), - /// Self update the Typst CLI + /// Self update the Typst CLI. #[cfg_attr(not(feature = "self-update"), clap(hide = true))] Update(UpdateCommand), } -/// Compiles an input file into a supported output format +/// Compiles an input file into a supported output format. #[derive(Debug, Clone, Parser)] pub struct CompileCommand { - /// Shared arguments + /// Arguments for compilation. #[clap(flatten)] - pub common: SharedArgs, - - /// Path to output file (PDF, PNG or SVG). Use `-` to write output to stdout. - /// - /// For output formats emitting one file per page (PNG & SVG), a page number template - /// must be present if the source document renders to multiple pages. Use `{p}` for page - /// numbers, `{0p}` for zero padded page numbers and `{t}` for page count. For example, - /// `page-{0p}-of-{t}.png` creates `page-01-of-10.png`, `page-02-of-10.png` and so on. - #[clap( - required_if_eq("input", "-"), - value_parser = make_output_value_parser(), - value_hint = ValueHint::FilePath, - )] - pub output: Option<Output>, - - /// Which pages to export. When unspecified, all document pages are exported. - /// - /// Pages to export are separated by commas, and can be either simple page - /// numbers (e.g. '2,5' to export only pages 2 and 5) or page ranges - /// (e.g. '2,3-6,8-' to export page 2, pages 3 to 6 (inclusive), page 8 and - /// any pages after it). - /// - /// Page numbers are one-indexed and correspond to real page numbers in the - /// document (therefore not being affected by the document's page counter). - #[arg(long = "pages", value_delimiter = ',')] - pub pages: Option<Vec<PageRangeArgument>>, - - /// Output a Makefile rule describing the current compilation - #[clap(long = "make-deps", value_name = "PATH")] - pub make_deps: Option<PathBuf>, - - /// The format of the output file, inferred from the extension by default - #[arg(long = "format", short = 'f')] - pub format: Option<OutputFormat>, - - /// Opens the output file with the default viewer or a specific program after - /// compilation - /// - /// Ignored if output is stdout. - #[arg(long = "open", value_name = "VIEWER")] - pub open: Option<Option<String>>, - - /// The PPI (pixels per inch) to use for PNG export - #[arg(long = "ppi", default_value_t = 144.0)] - pub ppi: f32, - - /// Produces performance timings of the compilation process (experimental) - /// - /// The resulting JSON file can be loaded into a tracing tool such as - /// https://ui.perfetto.dev. It does not contain any sensitive information - /// apart from file names and line numbers. - #[arg(long = "timings", value_name = "OUTPUT_JSON")] - pub timings: Option<Option<PathBuf>>, - - /// One (or multiple comma-separated) PDF standards that Typst will enforce - /// conformance with. - #[arg(long = "pdf-standard", value_delimiter = ',')] - pub pdf_standard: Vec<PdfStandard>, + pub args: CompileArgs, } -/// A PDF standard that Typst can enforce conformance with. -#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)] -#[allow(non_camel_case_types)] -pub enum PdfStandard { - /// PDF 1.7. - #[value(name = "1.7")] - V_1_7, - /// PDF/A-2b. - #[value(name = "a-2b")] - A_2b, +/// Compiles an input file into a supported output format. +#[derive(Debug, Clone, Parser)] +pub struct WatchCommand { + /// Arguments for compilation. + #[clap(flatten)] + pub args: CompileArgs, } -/// Initializes a new project from a template +/// Initializes a new project from a template. #[derive(Debug, Clone, Parser)] pub struct InitCommand { - /// The template to use, e.g. `@preview/charged-ieee` + /// The template to use, e.g. `@preview/charged-ieee`. /// /// You can specify the version by appending e.g. `:0.1.0`. If no version is /// specified, Typst will default to the latest version. @@ -179,34 +110,34 @@ pub struct InitCommand { /// Supports both local and published templates. pub template: String, - /// The project directory, defaults to the template's name + /// The project directory, defaults to the template's name. pub dir: Option<String>, - /// Arguments related to storage of packages in the system + /// Arguments related to storage of packages in the system. #[clap(flatten)] - pub package_storage_args: PackageStorageArgs, + pub package: PackageArgs, } -/// Processes an input file to extract provided metadata +/// Processes an input file to extract provided metadata. #[derive(Debug, Clone, Parser)] pub struct QueryCommand { - /// Shared arguments - #[clap(flatten)] - pub common: SharedArgs, + /// Path to input Typst file. Use `-` to read input from stdin. + #[clap(value_parser = input_value_parser(), value_hint = ValueHint::FilePath)] + pub input: Input, - /// Defines which elements to retrieve + /// Defines which elements to retrieve. pub selector: String, - /// Extracts just one field from all retrieved elements + /// Extracts just one field from all retrieved elements. #[clap(long = "field")] pub field: Option<String>, - /// Expects and retrieves exactly one element + /// Expects and retrieves exactly one element. #[clap(long = "one", default_value = "false")] pub one: bool, - /// The format to serialize in - #[clap(long = "format", default_value = "json")] + /// The format to serialize in. + #[clap(long = "format", default_value_t)] pub format: SerializationFormat, /// Whether to pretty-print the serialized output. @@ -214,38 +145,153 @@ pub struct QueryCommand { /// Only applies to JSON format. #[clap(long)] pub pretty: bool, + + /// World arguments. + #[clap(flatten)] + pub world: WorldArgs, + + /// Processing arguments. + #[clap(flatten)] + pub process: ProcessArgs, } -// Output file format for query command -#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)] -pub enum SerializationFormat { - Json, - Yaml, +/// Lists all discovered fonts in system and custom font paths. +#[derive(Debug, Clone, Parser)] +pub struct FontsCommand { + /// Common font arguments. + #[clap(flatten)] + pub font: FontArgs, + + /// Also lists style variants of each font family. + #[arg(long)] + pub variants: bool, +} + +/// Update the CLI using a pre-compiled binary from a Typst GitHub release. +#[derive(Debug, Clone, Parser)] +pub struct UpdateCommand { + /// Which version to update to (defaults to latest). + pub version: Option<Version>, + + /// Forces a downgrade to an older version (required for downgrading). + #[clap(long, default_value_t = false)] + pub force: bool, + + /// Reverts to the version from before the last update (only possible if + /// `typst update` has previously ran). + #[clap( + long, + default_value_t = false, + conflicts_with = "version", + conflicts_with = "force" + )] + pub revert: bool, + + /// Custom path to the backup file created on update and used by `--revert`, + /// defaults to system-dependent location + #[clap(long = "backup-path", env = "TYPST_UPDATE_BACKUP_PATH", value_name = "FILE")] + pub backup_path: Option<PathBuf>, } -/// Common arguments of compile, watch, and query. +/// Arguments for compilation and watching. #[derive(Debug, Clone, Args)] -pub struct SharedArgs { - /// Path to input Typst file. Use `-` to read input from stdin - #[clap(value_parser = make_input_value_parser(), value_hint = ValueHint::FilePath)] +pub struct CompileArgs { + /// Path to input Typst file. Use `-` to read input from stdin. + #[clap(value_parser = input_value_parser(), value_hint = ValueHint::FilePath)] pub input: Input, - /// Configures the project root (for absolute paths) + /// Path to output file (PDF, PNG, SVG, or HTML). Use `-` to write output to + /// stdout. + /// + /// For output formats emitting one file per page (PNG & SVG), a page number + /// template must be present if the source document renders to multiple + /// pages. Use `{p}` for page numbers, `{0p}` for zero padded page numbers + /// and `{t}` for page count. For example, `page-{0p}-of-{t}.png` creates + /// `page-01-of-10.png`, `page-02-of-10.png`, and so on. + #[clap( + required_if_eq("input", "-"), + value_parser = output_value_parser(), + value_hint = ValueHint::FilePath, + )] + pub output: Option<Output>, + + /// The format of the output file, inferred from the extension by default. + #[arg(long = "format", short = 'f')] + pub format: Option<OutputFormat>, + + /// World arguments. + #[clap(flatten)] + pub world: WorldArgs, + + /// Which pages to export. When unspecified, all pages are exported. + /// + /// Pages to export are separated by commas, and can be either simple page + /// numbers (e.g. '2,5' to export only pages 2 and 5) or page ranges (e.g. + /// '2,3-6,8-' to export page 2, pages 3 to 6 (inclusive), page 8 and any + /// pages after it). + /// + /// Page numbers are one-indexed and correspond to physical page numbers in + /// the document (therefore not being affected by the document's page + /// counter). + #[arg(long = "pages", value_delimiter = ',')] + pub pages: Option<Vec<Pages>>, + + /// One (or multiple comma-separated) PDF standards that Typst will enforce + /// conformance with. + #[arg(long = "pdf-standard", value_delimiter = ',')] + pub pdf_standard: Vec<PdfStandard>, + + /// The PPI (pixels per inch) to use for PNG export. + #[arg(long = "ppi", default_value_t = 144.0)] + pub ppi: f32, + + /// File path to which a Makefile with the current compilation's + /// dependencies will be written. + #[clap(long = "make-deps", value_name = "PATH")] + pub make_deps: Option<PathBuf>, + + /// Processing arguments. + #[clap(flatten)] + pub process: ProcessArgs, + + /// Opens the output file with the default viewer or a specific program + /// after compilation. Ignored if output is stdout. + #[arg(long = "open", value_name = "VIEWER")] + pub open: Option<Option<String>>, + + /// Produces performance timings of the compilation process. (experimental) + /// + /// The resulting JSON file can be loaded into a tracing tool such as + /// https://ui.perfetto.dev. It does not contain any sensitive information + /// apart from file names and line numbers. + #[arg(long = "timings", value_name = "OUTPUT_JSON")] + pub timings: Option<Option<PathBuf>>, +} + +/// Arguments for the construction of a world. Shared by compile, watch, and +/// query. +#[derive(Debug, Clone, Args)] +pub struct WorldArgs { + /// Configures the project root (for absolute paths). #[clap(long = "root", env = "TYPST_ROOT", value_name = "DIR")] pub root: Option<PathBuf>, - /// Add a string key-value pair visible through `sys.inputs` + /// Add a string key-value pair visible through `sys.inputs`. #[clap( long = "input", value_name = "key=value", action = ArgAction::Append, - value_parser = ValueParser::new(parse_input_pair), + value_parser = ValueParser::new(parse_sys_input_pair), )] pub inputs: Vec<(String, String)>, - /// Common font arguments + /// Common font arguments. #[clap(flatten)] - pub font_args: FontArgs, + pub font: FontArgs, + + /// Arguments related to storage of packages in the system. + #[clap(flatten)] + pub package: PackageArgs, /// The document's creation date formatted as a UNIX timestamp. /// @@ -257,21 +303,13 @@ pub struct SharedArgs { value_parser = parse_source_date_epoch, )] pub creation_timestamp: Option<DateTime<Utc>>, +} - /// The format to emit diagnostics in - #[clap( - long, - default_value_t = DiagnosticFormat::Human, - value_parser = clap::value_parser!(DiagnosticFormat) - )] - pub diagnostic_format: DiagnosticFormat, - - /// Arguments related to storage of packages in the system - #[clap(flatten)] - pub package_storage_args: PackageStorageArgs, - - /// Number of parallel jobs spawned during compilation, - /// defaults to number of CPUs. Setting it to 1 disables parallelism. +/// Arguments for configuration the process of compilation itself. +#[derive(Debug, Clone, Args)] +pub struct ProcessArgs { + /// Number of parallel jobs spawned during compilation. Defaults to number + /// of CPUs. Setting it to 1 disables parallelism. #[clap(long, short)] pub jobs: Option<usize>, @@ -279,22 +317,20 @@ pub struct SharedArgs { /// time. #[arg(long = "feature", value_delimiter = ',')] pub feature: Vec<Feature>, -} -/// An in-development feature that may be changed or removed at any time. -#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)] -pub enum Feature { - Html, + /// The format to emit diagnostics in. + #[clap(long, default_value_t)] + pub diagnostic_format: DiagnosticFormat, } /// Arguments related to where packages are stored in the system. #[derive(Debug, Clone, Args)] -pub struct PackageStorageArgs { - /// Custom path to local packages, defaults to system-dependent location +pub struct PackageArgs { + /// Custom path to local packages, defaults to system-dependent location. #[clap(long = "package-path", env = "TYPST_PACKAGE_PATH", value_name = "DIR")] pub package_path: Option<PathBuf>, - /// Custom path to package cache, defaults to system-dependent location + /// Custom path to package cache, defaults to system-dependent location. #[clap( long = "package-cache-path", env = "TYPST_PACKAGE_CACHE_PATH", @@ -303,13 +339,38 @@ pub struct PackageStorageArgs { pub package_cache_path: Option<PathBuf>, } -/// Parses a UNIX timestamp according to <https://reproducible-builds.org/specs/source-date-epoch/> -fn parse_source_date_epoch(raw: &str) -> Result<DateTime<Utc>, String> { - let timestamp: i64 = raw - .parse() - .map_err(|err| format!("timestamp must be decimal integer ({err})"))?; - DateTime::from_timestamp(timestamp, 0) - .ok_or_else(|| "timestamp out of range".to_string()) +/// Common arguments to customize available fonts +#[derive(Debug, Clone, Parser)] +pub struct FontArgs { + /// Adds additional directories that are recursively searched for fonts. + /// + /// If multiple paths are specified, they are separated by the system's path + /// separator (`:` on Unix-like systems and `;` on Windows). + #[clap( + long = "font-path", + env = "TYPST_FONT_PATHS", + value_name = "DIR", + value_delimiter = ENV_PATH_SEP, + )] + pub font_paths: Vec<PathBuf>, + + /// Ensures system fonts won't be searched, unless explicitly included via + /// `--font-path`. + #[arg(long)] + pub ignore_system_fonts: bool, +} + +macro_rules! display_possible_values { + ($ty:ty) => { + impl Display for $ty { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.to_possible_value() + .expect("no values are skipped") + .get_name() + .fmt(f) + } + } + }; } /// An input that is either stdin or a real path. @@ -321,6 +382,15 @@ pub enum Input { Path(PathBuf), } +impl Display for Input { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Input::Stdin => f.pad("stdin"), + Input::Path(path) => path.display().fmt(f), + } + } +} + /// An output that is either stdout or a real path. #[derive(Debug, Clone)] pub enum Output { @@ -339,64 +409,68 @@ impl Display for Output { } } -/// The clap value parser used by `SharedArgs.input` -fn make_input_value_parser() -> impl TypedValueParser<Value = Input> { - clap::builder::OsStringValueParser::new().try_map(|value| { - if value.is_empty() { - Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)) - } else if value == "-" { - Ok(Input::Stdin) - } else { - Ok(Input::Path(value.into())) - } - }) +/// Which format to use for the generated output file. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)] +pub enum OutputFormat { + Pdf, + Png, + Svg, + Html, } -/// The clap value parser used by `CompileCommand.output` -fn make_output_value_parser() -> impl TypedValueParser<Value = Output> { - clap::builder::OsStringValueParser::new().try_map(|value| { - // Empty value also handled by clap for `Option<Output>` - if value.is_empty() { - Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)) - } else if value == "-" { - Ok(Output::Stdout) - } else { - Ok(Output::Path(value.into())) - } - }) +display_possible_values!(OutputFormat); + +/// Which format to use for diagnostics. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)] +pub enum DiagnosticFormat { + #[default] + Human, + Short, } -/// Parses key/value pairs split by the first equal sign. -/// -/// This function will return an error if the argument contains no equals sign -/// or contains the key (before the equals sign) is empty. -fn parse_input_pair(raw: &str) -> Result<(String, String), String> { - let (key, val) = raw - .split_once('=') - .ok_or("input must be a key and a value separated by an equal sign")?; - let key = key.trim().to_owned(); - if key.is_empty() { - return Err("the key was missing or empty".to_owned()); - } - let val = val.trim().to_owned(); - Ok((key, val)) +display_possible_values!(DiagnosticFormat); + +/// An in-development feature that may be changed or removed at any time. +#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)] +pub enum Feature { + Html, } +display_possible_values!(Feature); + +/// A PDF standard that Typst can enforce conformance with. +#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)] +#[allow(non_camel_case_types)] +pub enum PdfStandard { + /// PDF 1.7. + #[value(name = "1.7")] + V_1_7, + /// PDF/A-2b. + #[value(name = "a-2b")] + A_2b, +} + +display_possible_values!(PdfStandard); + +// Output file format for query command +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, ValueEnum)] +pub enum SerializationFormat { + #[default] + Json, + Yaml, +} + +display_possible_values!(SerializationFormat); + /// Implements parsing of page ranges (`1-3`, `4`, `5-`, `-2`), used by the -/// `CompileCommand.pages` argument, through the `FromStr` trait instead of -/// a value parser, in order to generate better errors. +/// `CompileCommand.pages` argument, through the `FromStr` trait instead of a +/// value parser, in order to generate better errors. /// /// See also: https://github.com/clap-rs/clap/issues/5065 #[derive(Debug, Clone)] -pub struct PageRangeArgument(RangeInclusive<Option<NonZeroUsize>>); +pub struct Pages(pub RangeInclusive<Option<NonZeroUsize>>); -impl PageRangeArgument { - pub fn to_range(&self) -> RangeInclusive<Option<NonZeroUsize>> { - self.0.clone() - } -} - -impl FromStr for PageRangeArgument { +impl FromStr for Pages { type Err = &'static str; fn from_str(value: &str) -> Result<Self, Self::Err> { @@ -404,18 +478,18 @@ impl FromStr for PageRangeArgument { [] | [""] => Err("page export range must not be empty"), [single_page] => { let page_number = parse_page_number(single_page)?; - Ok(PageRangeArgument(Some(page_number)..=Some(page_number))) + Ok(Pages(Some(page_number)..=Some(page_number))) } ["", ""] => Err("page export range must have start or end"), - [start, ""] => Ok(PageRangeArgument(Some(parse_page_number(start)?)..=None)), - ["", end] => Ok(PageRangeArgument(None..=Some(parse_page_number(end)?))), + [start, ""] => Ok(Pages(Some(parse_page_number(start)?)..=None)), + ["", end] => Ok(Pages(None..=Some(parse_page_number(end)?))), [start, end] => { let start = parse_page_number(start)?; let end = parse_page_number(end)?; if start > end { Err("page export range must end at a page after the start") } else { - Ok(PageRangeArgument(Some(start)..=Some(end))) + Ok(Pages(Some(start)..=Some(end))) } } [_, _, _, ..] => Err("page export range must have a single hyphen"), @@ -423,6 +497,7 @@ impl FromStr for PageRangeArgument { } } +/// Parses a single page number. fn parse_page_number(value: &str) -> Result<NonZeroUsize, &'static str> { if value == "0" { Err("page numbers start at one") @@ -431,95 +506,54 @@ fn parse_page_number(value: &str) -> Result<NonZeroUsize, &'static str> { } } -/// Lists all discovered fonts in system and custom font paths -#[derive(Debug, Clone, Parser)] -pub struct FontsCommand { - /// Common font arguments - #[clap(flatten)] - pub font_args: FontArgs, - - /// Also lists style variants of each font family - #[arg(long)] - pub variants: bool, -} - -/// Common arguments to customize available fonts -#[derive(Debug, Clone, Parser)] -pub struct FontArgs { - /// Adds additional directories that are recursively searched for fonts - /// - /// If multiple paths are specified, they are separated by the system's path - /// separator (`:` on Unix-like systems and `;` on Windows). - #[clap( - long = "font-path", - env = "TYPST_FONT_PATHS", - value_name = "DIR", - value_delimiter = ENV_PATH_SEP, - )] - pub font_paths: Vec<PathBuf>, - - /// Ensures system fonts won't be searched, unless explicitly included via - /// `--font-path` - #[arg(long)] - pub ignore_system_fonts: bool, +/// The clap value parser used by `SharedArgs.input` +fn input_value_parser() -> impl TypedValueParser<Value = Input> { + clap::builder::OsStringValueParser::new().try_map(|value| { + if value.is_empty() { + Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)) + } else if value == "-" { + Ok(Input::Stdin) + } else { + Ok(Input::Path(value.into())) + } + }) } -/// Which format to use for diagnostics. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)] -pub enum DiagnosticFormat { - Human, - Short, +/// The clap value parser used by `CompileCommand.output` +fn output_value_parser() -> impl TypedValueParser<Value = Output> { + clap::builder::OsStringValueParser::new().try_map(|value| { + // Empty value also handled by clap for `Option<Output>` + if value.is_empty() { + Err(clap::Error::new(clap::error::ErrorKind::InvalidValue)) + } else if value == "-" { + Ok(Output::Stdout) + } else { + Ok(Output::Path(value.into())) + } + }) } -impl Display for DiagnosticFormat { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - self.to_possible_value() - .expect("no values are skipped") - .get_name() - .fmt(f) +/// Parses key/value pairs split by the first equal sign. +/// +/// This function will return an error if the argument contains no equals sign +/// or contains the key (before the equals sign) is empty. +fn parse_sys_input_pair(raw: &str) -> Result<(String, String), String> { + let (key, val) = raw + .split_once('=') + .ok_or("input must be a key and a value separated by an equal sign")?; + let key = key.trim().to_owned(); + if key.is_empty() { + return Err("the key was missing or empty".to_owned()); } + let val = val.trim().to_owned(); + Ok((key, val)) } -/// Update the CLI using a pre-compiled binary from a Typst GitHub release. -#[derive(Debug, Clone, Parser)] -pub struct UpdateCommand { - /// Which version to update to (defaults to latest) - pub version: Option<Version>, - - /// Forces a downgrade to an older version (required for downgrading) - #[clap(long, default_value_t = false)] - pub force: bool, - - /// Reverts to the version from before the last update (only possible if - /// `typst update` has previously ran) - #[clap( - long, - default_value_t = false, - conflicts_with = "version", - conflicts_with = "force" - )] - pub revert: bool, - - /// Custom path to the backup file created on update and used by `--revert`, - /// defaults to system-dependent location - #[clap(long = "backup-path", env = "TYPST_UPDATE_BACKUP_PATH", value_name = "FILE")] - pub backup_path: Option<PathBuf>, -} - -/// Which format to use for the generated output file. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)] -pub enum OutputFormat { - Pdf, - Png, - Svg, - Html, -} - -impl Display for OutputFormat { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - self.to_possible_value() - .expect("no values are skipped") - .get_name() - .fmt(f) - } +/// Parses a UNIX timestamp according to <https://reproducible-builds.org/specs/source-date-epoch/> +fn parse_source_date_epoch(raw: &str) -> Result<DateTime<Utc>, String> { + let timestamp: i64 = raw + .parse() + .map_err(|err| format!("timestamp must be decimal integer ({err})"))?; + DateTime::from_timestamp(timestamp, 0) + .ok_or_else(|| "timestamp out of range".to_string()) } |
