diff --git a/cli/src/lib.rs b/cli/src/lib.rs index fe92304..c9812d1 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -5,6 +5,8 @@ pub mod project; pub mod render; pub mod theme; pub mod utils; +pub mod version; +use version::VersionFormat; use std::path::PathBuf; @@ -13,6 +15,14 @@ use clap::{ArgAction, Parser, Subcommand}; #[derive(Debug, Parser)] #[clap(name = "typst-book", version = "0.1.0")] pub struct Opts { + /// Print Version + #[arg(short = 'V', long, group = "version-dump")] + pub version: bool, + + /// Print Version in format + #[arg(long = "VV", alias = "version-fmt", group = "version-dump", default_value_t = VersionFormat::None)] + pub vv: VersionFormat, + #[clap(subcommand)] pub sub: Option, } @@ -99,3 +109,8 @@ pub struct ServeArgs { #[clap(long, default_value = "127.0.0.1:25520")] pub addr: String, } + +pub mod build_info { + /// The version of the typst-ts-core crate. + pub static VERSION: &str = env!("CARGO_PKG_VERSION"); +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 0b4e435..537d38d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -5,6 +5,7 @@ use typst_book_cli::{ error::prelude::*, project::Project, utils::{async_continue, create_dirs, make_absolute, write_file, UnwrapOrExit}, + version::intercept_version, BuildArgs, InitArgs, Opts, ServeArgs, Subcommands, }; use typst_ts_core::path::{unix_slash, PathClean}; @@ -29,6 +30,8 @@ fn main() { .filter_module("typst_library::", log::LevelFilter::Warn) .init(); + intercept_version(opts.version, opts.vv); + match opts.sub { Some(Subcommands::Init(args)) => { async_continue(async { init(args).await.unwrap_or_exit() }) diff --git a/cli/src/version.rs b/cli/src/version.rs new file mode 100644 index 0000000..dca8d5a --- /dev/null +++ b/cli/src/version.rs @@ -0,0 +1,144 @@ +use std::{fmt::Display, process::exit}; + +use crate::build_info::VERSION; +use clap::ValueEnum; + +/// Available version formats for `$program -VV` +#[derive(ValueEnum, Debug, Clone)] +#[value(rename_all = "kebab-case")] +pub enum VersionFormat { + None, + Short, + Features, + Full, + Json, + JsonPlain, +} + +impl Display for VersionFormat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_possible_value().unwrap().get_name()) + } +} + +/// Version information +#[derive(serde::Serialize, serde::Deserialize)] +struct VersionInfo { + name: &'static str, + version: &'static str, + features: Vec<&'static str>, + + program_semver: &'static str, + program_commit_hash: &'static str, + program_target_triple: &'static str, + program_profile: &'static str, + program_build_timestamp: &'static str, + + rustc_semver: &'static str, + rustc_commit_hash: &'static str, + rustc_host_triple: &'static str, + rustc_channel: &'static str, + rustc_llvm_version: &'static str, +} + +impl VersionInfo { + fn new() -> Self { + Self { + // todo: global app name + name: "typst-book", + version: VERSION, + features: env!("VERGEN_CARGO_FEATURES").split(',').collect::>(), + + program_semver: env!("VERGEN_GIT_SEMVER"), + program_commit_hash: env!("VERGEN_GIT_SHA"), + program_target_triple: env!("VERGEN_CARGO_TARGET_TRIPLE"), + program_profile: env!("VERGEN_CARGO_PROFILE"), + program_build_timestamp: env!("VERGEN_BUILD_TIMESTAMP"), + + rustc_semver: env!("VERGEN_RUSTC_SEMVER"), + rustc_commit_hash: env!("VERGEN_RUSTC_COMMIT_HASH"), + rustc_host_triple: env!("VERGEN_RUSTC_HOST_TRIPLE"), + rustc_channel: env!("VERGEN_RUSTC_CHANNEL"), + rustc_llvm_version: env!("VERGEN_RUSTC_LLVM_VERSION"), + } + } + + fn program_build(&self) -> String { + format!( + "{} with {} mode at {}", + self.program_target_triple, self.program_profile, self.program_build_timestamp + ) + } + + fn rustc_build(&self) -> String { + format!( + "{}-{} with LLVM {}", + self.rustc_host_triple, self.rustc_channel, self.rustc_llvm_version + ) + } +} + +impl Default for VersionInfo { + fn default() -> Self { + Self::new() + } +} + +/// Print version information and exit if `-VV` is present +pub fn intercept_version(v: bool, f: VersionFormat) { + let f = match f { + VersionFormat::None if v => VersionFormat::Short, + VersionFormat::None => return, + _ => f, + }; + let version_info = VersionInfo::new(); + match f { + VersionFormat::Full => print_full_version(version_info), + VersionFormat::Features => println!("{}", version_info.features.join(",")), + VersionFormat::Json => { + println!("{}", serde_json::to_string_pretty(&version_info).unwrap()) + } + VersionFormat::JsonPlain => println!("{}", serde_json::to_string(&version_info).unwrap()), + _ => print_short_version(version_info), + } + exit(0); +} + +fn print_full_version(vi: VersionInfo) { + let program_semver = vi.program_semver; + let program_commit_hash = vi.program_commit_hash; + let program_build = vi.program_build(); + + let rustc_semver = vi.rustc_semver; + let rustc_commit_hash = vi.rustc_commit_hash; + let rustc_build = vi.rustc_build(); + + print_short_version(vi); + println!( + r##" +program-ver: {program_semver} +program-rev: {program_commit_hash} +program-build: {program_build} + +rustc-ver: {rustc_semver} +rustc-rev: {rustc_commit_hash} +rustc-build: {rustc_build}"## + ); +} + +fn print_short_version(vi: VersionInfo) { + let name = vi.name; + let version = vi.version; + let features = vi + .features + .iter() + .copied() + .filter(|&s| s != "default" && !s.ends_with("_exporter")) + .collect::>() + .join(" "); + + println!( + r##"{name} version {version} +features: {features}"## + ); +}