Tidy up CLI

This commit is contained in:
Laurenz 2023-05-22 15:13:51 +02:00
parent 8aa0ae197d
commit 88553fe3c0
4 changed files with 129 additions and 122 deletions

View File

@ -7,23 +7,6 @@ use clap::{CommandFactory, ValueEnum};
use clap_complete::{generate_to, Shell}; use clap_complete::{generate_to, Shell};
use clap_mangen::Man; use clap_mangen::Man;
pub fn typst_version() -> String {
if let Some(version) = option_env!("TYPST_VERSION") {
return version.to_owned();
}
let pkg = env!("CARGO_PKG_VERSION");
let hash = Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.ok()
.filter(|output| output.status.success())
.and_then(|output| String::from_utf8(output.stdout.get(..8)?.into()).ok())
.unwrap_or_else(|| "unknown hash".into());
format!("{pkg} ({hash})")
}
#[path = "src/args.rs"] #[path = "src/args.rs"]
#[allow(dead_code)] #[allow(dead_code)]
mod args; mod args;
@ -57,3 +40,21 @@ fn main() {
} }
} }
} }
/// Also used by `args.rs`.
fn typst_version() -> String {
if let Some(version) = option_env!("TYPST_VERSION") {
return version.to_owned();
}
let pkg = env!("CARGO_PKG_VERSION");
let hash = Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.ok()
.filter(|output| output.status.success())
.and_then(|output| String::from_utf8(output.stdout.get(..8)?.into()).ok())
.unwrap_or_else(|| "unknown hash".into());
format!("{pkg} ({hash})")
}

View File

@ -18,7 +18,8 @@ pub struct CliArguments {
#[command(subcommand)] #[command(subcommand)]
pub command: Command, pub command: Command,
/// Sets the level of verbosity: 0 = none, 1 = warning & error, 2 = info, 3 = debug, 4 = trace /// Sets the level of logging verbosity:
/// -v = warning & error, -vv = info, -vvv = debug, -vvvv = trace
#[clap(short, long, action = ArgAction::Count)] #[clap(short, long, action = ArgAction::Count)]
pub verbosity: u8, pub verbosity: u8,
} }
@ -68,8 +69,7 @@ pub struct CompileCommand {
#[arg(long = "open")] #[arg(long = "open")]
pub open: Option<Option<String>>, pub open: Option<Option<String>>,
/// Produces a flamegraph of the compilation process and saves it to the /// Produces a flamegraph of the compilation process
/// given file or to `flamegraph.svg` in the current working directory.
#[arg(long = "flamegraph", value_name = "OUTPUT_SVG")] #[arg(long = "flamegraph", value_name = "OUTPUT_SVG")]
pub flamegraph: Option<Option<PathBuf>>, pub flamegraph: Option<Option<PathBuf>>,
} }

View File

@ -7,7 +7,6 @@ use std::fs::{self, File};
use std::hash::Hash; use std::hash::Hash;
use std::io::{self, Write}; use std::io::{self, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process;
use atty::Stream; use atty::Stream;
use clap::Parser; use clap::Parser;
@ -30,102 +29,18 @@ use typst::World;
use walkdir::WalkDir; use walkdir::WalkDir;
use crate::args::{CliArguments, Command, CompileCommand}; use crate::args::{CliArguments, Command, CompileCommand};
use crate::trace::init_tracing;
type CodespanResult<T> = Result<T, CodespanError>; type CodespanResult<T> = Result<T, CodespanError>;
type CodespanError = codespan_reporting::files::Error; type CodespanError = codespan_reporting::files::Error;
pub fn typst_version() -> &'static str {
env!("TYPST_VERSION")
}
/// A summary of the input arguments relevant to compilation.
struct CompileSettings {
/// The path to the input file.
input: PathBuf,
/// The path to the output file.
output: PathBuf,
/// Whether to watch the input files for changes.
watch: bool,
/// The root directory for absolute paths.
root: Option<PathBuf>,
/// The paths to search for fonts.
font_paths: Vec<PathBuf>,
/// The open command to use.
open: Option<Option<String>>,
}
impl CompileSettings {
/// Create a new compile settings from the field values.
pub fn new(
input: PathBuf,
output: Option<PathBuf>,
watch: bool,
root: Option<PathBuf>,
font_paths: Vec<PathBuf>,
open: Option<Option<String>>,
) -> Self {
let output = match output {
Some(path) => path,
None => input.with_extension("pdf"),
};
Self { input, output, watch, root, font_paths, open }
}
/// Create a new compile settings from the CLI arguments and a compile command.
///
/// # Panics
/// Panics if the command is not a compile or watch command.
pub fn with_arguments(args: CliArguments) -> Self {
let watch = matches!(args.command, Command::Watch(_));
let CompileCommand { input, output, open, .. } = match args.command {
Command::Compile(command) => command,
Command::Watch(command) => command,
_ => unreachable!(),
};
Self::new(input, output, watch, args.root, args.font_paths, open)
}
}
struct FontsSettings {
/// The font paths
font_paths: Vec<PathBuf>,
/// Whether to include font variants
variants: bool,
}
impl FontsSettings {
/// Create font settings from the field values.
pub fn new(font_paths: Vec<PathBuf>, variants: bool) -> Self {
Self { font_paths, variants }
}
/// Create a new font settings from the CLI arguments.
///
/// # Panics
/// Panics if the command is not a fonts command.
pub fn with_arguments(args: CliArguments) -> Self {
match args.command {
Command::Fonts(command) => Self::new(args.font_paths, command.variants),
_ => unreachable!(),
}
}
}
/// Entry point. /// Entry point.
fn main() { fn main() {
let arguments = CliArguments::parse(); let arguments = CliArguments::parse();
let _guard = match init_tracing(&arguments) { let _guard = match crate::trace::init_tracing(&arguments) {
Ok(guard) => guard, Ok(guard) => guard,
Err(err) => { Err(err) => {
eprintln!("failed to initialize tracing, reason: {}", err); eprintln!("failed to initialize tracing {}", err);
return; None
} }
}; };
@ -153,6 +68,90 @@ fn print_error(msg: &str) -> io::Result<()> {
writeln!(w, ": {msg}.") writeln!(w, ": {msg}.")
} }
/// Used by `args.rs`.
fn typst_version() -> &'static str {
env!("TYPST_VERSION")
}
/// A summary of the input arguments relevant to compilation.
struct CompileSettings {
/// The path to the input file.
input: PathBuf,
/// The path to the output file.
output: PathBuf,
/// Whether to watch the input files for changes.
watch: bool,
/// The root directory for absolute paths.
root: Option<PathBuf>,
/// The paths to search for fonts.
font_paths: Vec<PathBuf>,
/// The open command to use.
open: Option<Option<String>>,
}
impl CompileSettings {
/// Create a new compile settings from the field values.
fn new(
input: PathBuf,
output: Option<PathBuf>,
watch: bool,
root: Option<PathBuf>,
font_paths: Vec<PathBuf>,
open: Option<Option<String>>,
) -> Self {
let output = match output {
Some(path) => path,
None => input.with_extension("pdf"),
};
Self { input, output, watch, root, font_paths, open }
}
/// Create a new compile settings from the CLI arguments and a compile command.
///
/// # Panics
/// 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, .. } = match args.command {
Command::Compile(command) => command,
Command::Watch(command) => command,
_ => unreachable!(),
};
Self::new(input, output, watch, args.root, args.font_paths, open)
}
}
struct FontsSettings {
/// The font paths
font_paths: Vec<PathBuf>,
/// Whether to include font variants
variants: bool,
}
impl FontsSettings {
/// Create font settings from the field values.
fn new(font_paths: Vec<PathBuf>, variants: bool) -> Self {
Self { font_paths, variants }
}
/// Create a new font settings from the CLI arguments.
///
/// # Panics
/// Panics if the command is not a fonts command.
fn with_arguments(args: CliArguments) -> Self {
match args.command {
Command::Fonts(command) => Self::new(args.font_paths, command.variants),
_ => unreachable!(),
}
}
}
/// Execute a compilation command. /// Execute a compilation command.
fn compile(mut command: CompileSettings) -> StrResult<()> { fn compile(mut command: CompileSettings) -> StrResult<()> {
let root = if let Some(root) = &command.root { let root = if let Some(root) = &command.root {
@ -173,10 +172,11 @@ fn compile(mut command: CompileSettings) -> StrResult<()> {
let mut world = SystemWorld::new(root, &command.font_paths); let mut world = SystemWorld::new(root, &command.font_paths);
// Perform initial compilation. // Perform initial compilation.
let failed = compile_once(&mut world, &command)?; let ok = compile_once(&mut world, &command)?;
// open the file if requested, this must be done on the first **successful** compilation // Open the file if requested, this must be done on the first **successful**
if !failed { // compilation.
if ok {
if let Some(open) = command.open.take() { if let Some(open) = command.open.take() {
open_file(open.as_deref(), &command.output)?; open_file(open.as_deref(), &command.output)?;
} }
@ -184,8 +184,8 @@ fn compile(mut command: CompileSettings) -> StrResult<()> {
if !command.watch { if !command.watch {
// Return with non-zero exit code in case of error. // Return with non-zero exit code in case of error.
if failed { if !ok {
process::exit(1); std::process::exit(1);
} }
return Ok(()); return Ok(());
@ -223,18 +223,23 @@ fn compile(mut command: CompileSettings) -> StrResult<()> {
} }
if recompile { if recompile {
compile_once(&mut world, &command)?; let ok = compile_once(&mut world, &command)?;
comemo::evict(30); comemo::evict(30);
// open the file if requested, this must be done on the first **successful** compilation // Ipen the file if requested, this must be done on the first
if let Some(open) = command.open.take() { // **successful** compilation
open_file(open.as_deref(), &command.output)?; if ok {
if let Some(open) = command.open.take() {
open_file(open.as_deref(), &command.output)?;
}
} }
} }
} }
} }
/// Compile a single time. /// Compile a single time.
///
/// Returns whether it compiled without errors.
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn compile_once(world: &mut SystemWorld, command: &CompileSettings) -> StrResult<bool> { fn compile_once(world: &mut SystemWorld, command: &CompileSettings) -> StrResult<bool> {
tracing::info!("Starting compilation"); tracing::info!("Starting compilation");
@ -252,7 +257,7 @@ fn compile_once(world: &mut SystemWorld, command: &CompileSettings) -> StrResult
status(command, Status::Success).unwrap(); status(command, Status::Success).unwrap();
tracing::info!("Compilation succeeded"); tracing::info!("Compilation succeeded");
Ok(false) Ok(true)
} }
// Print diagnostics. // Print diagnostics.
@ -262,7 +267,7 @@ fn compile_once(world: &mut SystemWorld, command: &CompileSettings) -> StrResult
.map_err(|_| "failed to print diagnostics")?; .map_err(|_| "failed to print diagnostics")?;
tracing::info!("Compilation failed"); tracing::info!("Compilation failed");
Ok(true) Ok(false)
} }
} }
} }

View File

@ -127,9 +127,10 @@ pub fn init_tracing(args: &CliArguments) -> Result<Option<TracingGuard>, Error>
/// Returns the log level filter for the given verbosity level. /// Returns the log level filter for the given verbosity level.
fn level_filter(args: &CliArguments) -> LevelFilter { fn level_filter(args: &CliArguments) -> LevelFilter {
match args.verbosity { match args.verbosity {
0 => LevelFilter::WARN, 0 => LevelFilter::OFF,
1 => LevelFilter::INFO, 1 => LevelFilter::WARN,
2 => LevelFilter::DEBUG, 2 => LevelFilter::INFO,
3 => LevelFilter::DEBUG,
_ => LevelFilter::TRACE, _ => LevelFilter::TRACE,
} }
} }