diff --git a/cli/src/args.rs b/cli/src/args.rs index fbd5ec2da..d794347cf 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -1,11 +1,16 @@ +use std::fmt::{self, Display, Formatter}; use std::path::PathBuf; -use clap::{ArgAction, Parser, Subcommand}; +use clap::{ArgAction, Parser, Subcommand, ValueEnum}; /// typst creates PDF files from .typ files #[derive(Debug, Clone, Parser)] #[clap(name = "typst", version = crate::typst_version(), author)] pub struct CliArguments { + /// The typst command to run + #[command(subcommand)] + pub command: Command, + /// Add additional directories to search for fonts #[clap(long = "font-path", env = "TYPST_FONT_PATHS", value_name = "DIR", action = ArgAction::Append)] pub font_paths: Vec, @@ -14,16 +19,28 @@ pub struct CliArguments { #[clap(long = "root", env = "TYPST_ROOT", value_name = "DIR")] pub root: Option, - /// The typst command to run - #[command(subcommand)] - pub command: Command, - /// Sets the level of logging verbosity: /// -v = warning & error, -vv = info, -vvv = debug, -vvvv = trace #[clap(short, long, action = ArgAction::Count)] pub verbosity: u8, } +/// Which format to use for diagnostics. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)] +pub enum DiagnosticFormat { + Human, + Short, +} + +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) + } +} + /// What to do. #[derive(Debug, Clone, Subcommand)] #[command()] @@ -69,13 +86,21 @@ pub struct CompileCommand { #[arg(long = "open")] pub open: Option>, - /// Produces a flamegraph of the compilation process - #[arg(long = "flamegraph", value_name = "OUTPUT_SVG")] - pub flamegraph: Option>, - /// The PPI to use if exported as PNG #[arg(long = "ppi")] pub ppi: Option, + + /// In which format to emit diagnostics + #[clap( + long, + default_value_t = DiagnosticFormat::Human, + value_parser = clap::value_parser!(DiagnosticFormat) + )] + pub diagnostic_format: DiagnosticFormat, + + /// Produces a flamegraph of the compilation process + #[arg(long = "flamegraph", value_name = "OUTPUT_SVG")] + pub flamegraph: Option>, } /// List all discovered fonts in system and custom font paths diff --git a/cli/src/main.rs b/cli/src/main.rs index 8a3a753b0..b4f550e08 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -34,7 +34,7 @@ use typst::util::{Buffer, PathExt}; use typst::World; use walkdir::WalkDir; -use crate::args::{CliArguments, Command, CompileCommand}; +use crate::args::{CliArguments, Command, CompileCommand, DiagnosticFormat}; type CodespanResult = Result; type CodespanError = codespan_reporting::files::Error; @@ -111,12 +111,16 @@ struct CompileSettings { /// The open command to use. open: Option>, - /// The ppi to use for png export + /// The PPI to use for PNG export. ppi: Option, + + /// In which format to emit diagnostics + diagnostic_format: DiagnosticFormat, } impl CompileSettings { /// Create a new compile settings from the field values. + #[allow(clippy::too_many_arguments)] fn new( input: PathBuf, output: Option, @@ -125,12 +129,22 @@ impl CompileSettings { font_paths: Vec, open: Option>, ppi: Option, + diagnostic_format: DiagnosticFormat, ) -> Self { let output = match output { Some(path) => path, None => input.with_extension("pdf"), }; - Self { input, output, watch, root, font_paths, open, ppi } + Self { + input, + output, + watch, + root, + font_paths, + open, + diagnostic_format, + ppi, + } } /// Create a new compile settings from the CLI arguments and a compile command. @@ -139,12 +153,23 @@ impl CompileSettings { /// Panics if the command is not a compile or watch command. fn with_arguments(args: CliArguments) -> Self { let watch = matches!(args.command, Command::Watch(_)); - let CompileCommand { input, output, open, ppi, .. } = match args.command { - Command::Compile(command) => command, - Command::Watch(command) => command, - _ => unreachable!(), - }; - Self::new(input, output, watch, args.root, args.font_paths, open, ppi) + let CompileCommand { input, output, open, ppi, diagnostic_format, .. } = + match args.command { + Command::Compile(command) => command, + Command::Watch(command) => command, + _ => unreachable!(), + }; + + Self::new( + input, + output, + watch, + args.root, + args.font_paths, + open, + ppi, + diagnostic_format, + ) } } @@ -279,7 +304,7 @@ fn compile_once(world: &mut SystemWorld, command: &CompileSettings) -> StrResult Err(errors) => { set_failed(); status(command, Status::Error).unwrap(); - print_diagnostics(world, *errors) + print_diagnostics(world, *errors, command.diagnostic_format) .map_err(|_| "failed to print diagnostics")?; tracing::info!("Compilation failed"); Ok(false) @@ -400,9 +425,17 @@ impl Status { fn print_diagnostics( world: &SystemWorld, errors: Vec, + diagnostic_format: DiagnosticFormat, ) -> Result<(), codespan_reporting::files::Error> { - let mut w = StandardStream::stderr(ColorChoice::Auto); - let config = term::Config { tab_width: 2, ..Default::default() }; + let mut w = match diagnostic_format { + DiagnosticFormat::Human => color_stream(), + DiagnosticFormat::Short => StandardStream::stderr(ColorChoice::Never), + }; + + let mut config = term::Config { tab_width: 2, ..Default::default() }; + if diagnostic_format == DiagnosticFormat::Short { + config.display_style = term::DisplayStyle::Short; + } for error in errors { // The main diagnostic.