diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-06-28 10:35:44 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-06-28 10:36:53 +0200 |
| commit | 04bffc4f12ff7dd85d25f5fd65506440287f879c (patch) | |
| tree | 311a78d174f95c6e4733e3eb17a40f78744c3854 /cli/src | |
| parent | 7b92bd7c340d9f9c094ed2fa57912049317d9b20 (diff) | |
Reintroduce `--root`
Diffstat (limited to 'cli/src')
| -rw-r--r-- | cli/src/args.rs | 4 | ||||
| -rw-r--r-- | cli/src/main.rs | 112 |
2 files changed, 73 insertions, 43 deletions
diff --git a/cli/src/args.rs b/cli/src/args.rs index 8c16e14e..6b9f3df8 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -11,6 +11,10 @@ pub struct CliArguments { #[command(subcommand)] pub command: Command, + /// Configure the project root + #[clap(long = "root", env = "TYPST_ROOT", value_name = "DIR")] + pub root: Option<PathBuf>, + /// Add additional directories to search for fonts #[clap( long = "font-path", diff --git a/cli/src/main.rs b/cli/src/main.rs index 52889a7f..fe86caec 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -93,6 +93,8 @@ fn typst_version() -> &'static str { /// A summary of the input arguments relevant to compilation. struct CompileSettings { + /// The project's root directory. + root: Option<PathBuf>, /// The path to the input file. input: PathBuf, /// The path to the output file. @@ -115,8 +117,9 @@ impl CompileSettings { fn new( input: PathBuf, output: Option<PathBuf>, - watch: bool, + root: Option<PathBuf>, font_paths: Vec<PathBuf>, + watch: bool, open: Option<Option<String>>, ppi: Option<f32>, diagnostic_format: DiagnosticFormat, @@ -126,6 +129,7 @@ impl CompileSettings { None => input.with_extension("pdf"), }; Self { + root, input, output, watch, @@ -149,7 +153,16 @@ impl CompileSettings { _ => unreachable!(), }; - Self::new(input, output, watch, args.font_paths, open, ppi, diagnostic_format) + Self::new( + input, + output, + args.root, + args.font_paths, + watch, + open, + ppi, + diagnostic_format, + ) } } @@ -179,22 +192,22 @@ impl FontsSettings { } /// Execute a compilation command. -fn compile(mut command: CompileSettings) -> StrResult<()> { +fn compile(mut settings: CompileSettings) -> StrResult<()> { // Create the world that serves sources, files, and fonts. - let mut world = SystemWorld::new(&command.input, &command.font_paths); + let mut world = SystemWorld::new(&settings)?; // Perform initial compilation. - let ok = compile_once(&mut world, &command)?; + let ok = compile_once(&mut world, &settings)?; // Open the file if requested, this must be done on the first **successful** // compilation. if ok { - if let Some(open) = command.open.take() { - open_file(open.as_deref(), &command.output)?; + if let Some(open) = settings.open.take() { + open_file(open.as_deref(), &settings.output)?; } } - if !command.watch { + if !settings.watch { return Ok(()); } @@ -219,7 +232,7 @@ fn compile(mut command: CompileSettings) -> StrResult<()> { if event .paths .iter() - .all(|path| is_same_file(path, &command.output).unwrap_or(false)) + .all(|path| is_same_file(path, &settings.output).unwrap_or(false)) { continue; } @@ -232,7 +245,7 @@ fn compile(mut command: CompileSettings) -> StrResult<()> { let dependencies = world.dependencies(); // Recompile. - let ok = compile_once(&mut world, &command)?; + let ok = compile_once(&mut world, &settings)?; comemo::evict(10); // Adjust the watching. @@ -241,8 +254,8 @@ fn compile(mut command: CompileSettings) -> StrResult<()> { // Open the file if requested, this must be done on the first // **successful** compilation if ok { - if let Some(open) = command.open.take() { - open_file(open.as_deref(), &command.output)?; + if let Some(open) = settings.open.take() { + open_file(open.as_deref(), &settings.output)?; } } } @@ -253,11 +266,11 @@ fn compile(mut command: CompileSettings) -> StrResult<()> { /// /// Returns whether it compiled without errors. #[tracing::instrument(skip_all)] -fn compile_once(world: &mut SystemWorld, command: &CompileSettings) -> StrResult<bool> { +fn compile_once(world: &mut SystemWorld, settings: &CompileSettings) -> StrResult<bool> { tracing::info!("Starting compilation"); let start = std::time::Instant::now(); - status(command, Status::Compiling).unwrap(); + status(settings, Status::Compiling).unwrap(); // Reset everything and ensure that the main file is still present. world.reset(); @@ -269,8 +282,8 @@ fn compile_once(world: &mut SystemWorld, command: &CompileSettings) -> StrResult match result { // Export the PDF / PNG. Ok(document) => { - export(&document, command)?; - status(command, Status::Success(duration)).unwrap(); + export(&document, settings)?; + status(settings, Status::Success(duration)).unwrap(); tracing::info!("Compilation succeeded in {duration:?}"); Ok(true) } @@ -278,8 +291,8 @@ fn compile_once(world: &mut SystemWorld, command: &CompileSettings) -> StrResult // Print diagnostics. Err(errors) => { set_failed(); - status(command, Status::Error).unwrap(); - print_diagnostics(world, *errors, command.diagnostic_format) + status(settings, Status::Error).unwrap(); + print_diagnostics(world, *errors, settings.diagnostic_format) .map_err(|_| "failed to print diagnostics")?; tracing::info!("Compilation failed after {duration:?}"); Ok(false) @@ -288,11 +301,11 @@ fn compile_once(world: &mut SystemWorld, command: &CompileSettings) -> StrResult } /// Export into the target format. -fn export(document: &Document, command: &CompileSettings) -> StrResult<()> { - match command.output.extension() { +fn export(document: &Document, settings: &CompileSettings) -> StrResult<()> { + match settings.output.extension() { Some(ext) if ext.eq_ignore_ascii_case("png") => { // Determine whether we have a `{n}` numbering. - let string = command.output.to_str().unwrap_or_default(); + let string = settings.output.to_str().unwrap_or_default(); let numbered = string.contains("{n}"); if !numbered && document.pages.len() > 1 { bail!("cannot export multiple PNGs without `{{n}}` in output path"); @@ -302,7 +315,7 @@ fn export(document: &Document, command: &CompileSettings) -> StrResult<()> { // first page should be numbered "001" if there are between 100 and // 999 pages. let width = 1 + document.pages.len().checked_ilog10().unwrap_or(0) as usize; - let ppi = command.ppi.unwrap_or(2.0); + let ppi = settings.ppi.unwrap_or(2.0); let mut storage; for (i, frame) in document.pages.iter().enumerate() { @@ -311,14 +324,15 @@ fn export(document: &Document, command: &CompileSettings) -> StrResult<()> { storage = string.replace("{n}", &format!("{:0width$}", i + 1)); Path::new(&storage) } else { - command.output.as_path() + settings.output.as_path() }; pixmap.save_png(path).map_err(|_| "failed to write PNG file")?; } } _ => { let buffer = typst::export::pdf(document); - fs::write(&command.output, buffer).map_err(|_| "failed to write PDF file")?; + fs::write(&settings.output, buffer) + .map_err(|_| "failed to write PDF file")?; } } Ok(()) @@ -326,14 +340,14 @@ fn export(document: &Document, command: &CompileSettings) -> StrResult<()> { /// Clear the terminal and render the status message. #[tracing::instrument(skip_all)] -fn status(command: &CompileSettings, status: Status) -> io::Result<()> { - if !command.watch { +fn status(settings: &CompileSettings, status: Status) -> io::Result<()> { + if !settings.watch { return Ok(()); } let esc = 27 as char; - let input = command.input.display(); - let output = command.output.display(); + let input = settings.input.display(); + let output = settings.output.display(); let time = chrono::offset::Local::now(); let timestamp = time.format("%H:%M:%S"); let message = status.message(); @@ -506,31 +520,43 @@ struct PathSlot { } impl SystemWorld { - fn new(input: &Path, font_paths: &[PathBuf]) -> Self { + fn new(settings: &CompileSettings) -> StrResult<Self> { let mut searcher = FontSearcher::new(); - searcher.search(font_paths); - - let root = input - .canonicalize() - .ok() - .as_ref() - .and_then(|path| path.parent()) - .unwrap_or(Path::new(".")) - .to_owned(); + searcher.search(&settings.font_paths); + + // Resolve the system-global input path. + let system_input = settings.input.canonicalize().map_err(|_| { + eco_format!("input file not found (searched at {})", settings.input.display()) + })?; + + // Resolve the system-global root directory. + let root = { + let path = settings + .root + .as_deref() + .or_else(|| system_input.parent()) + .unwrap_or(Path::new(".")); + path.canonicalize().map_err(|_| { + eco_format!("root directory not found (searched at {})", path.display()) + })? + }; - let file = input.file_name().unwrap_or(input.as_os_str()); - let main = FileId::new(None, Path::new(file)); + // Resolve the input path within the project. + let project_input = system_input + .strip_prefix(&root) + .map(|path| Path::new("/").join(path)) + .map_err(|_| "input file must be contained in project root")?; - Self { + Ok(Self { root, - main, + main: FileId::new(None, &project_input), library: Prehashed::new(typst_library::build()), book: Prehashed::new(searcher.book), fonts: searcher.fonts, hashes: RefCell::default(), paths: RefCell::default(), today: OnceCell::new(), - } + }) } } |
