From 87669550c954bae3ec5fee99fd1e1e6135a8dac0 Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Wed, 26 Mar 2025 20:17:59 +0100 Subject: [PATCH] Add `input-file` CLI argument and `sys.input-files` variable --- crates/typst-cli/src/args.rs | 21 +++++++++++++++++++++ crates/typst-cli/src/world.rs | 12 +++++++++++- crates/typst-library/src/foundations/mod.rs | 9 +++++++-- crates/typst-library/src/foundations/sys.rs | 3 ++- crates/typst-library/src/lib.rs | 14 +++++++++++--- docs/reference/groups.yml | 8 ++++++++ 6 files changed, 60 insertions(+), 7 deletions(-) diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index d6855d100..0c36e66a7 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -290,6 +290,15 @@ pub struct WorldArgs { )] pub inputs: Vec<(String, String)>, + /// Add a string key-path pairs where the content will visible through `sys.input-files`. + #[clap( + long = "input-file", + value_name = "key=value", + action = ArgAction::Append, + value_parser = ValueParser::new(parse_sys_input_file_pair), + )] + pub input_files: Vec<(String, Vec)>, + /// Common font arguments. #[clap(flatten)] pub font: FontArgs, @@ -577,6 +586,18 @@ fn parse_sys_input_pair(raw: &str) -> Result<(String, String), String> { Ok((key, val)) } +/// Parses key/path pairs split by the first equal sign. +/// +/// This functions works similar to `parse_sys_input_pair`, but the value is +/// interpreted as a path and the content of the file is read. +fn parse_sys_input_file_pair(raw: &str) -> Result<(String, Vec), String> { + let (key, value) = parse_sys_input_pair(raw)?; + match std::fs::read(&value) { + Ok(content) => Ok((key, content)), + Err(err) => Err(format!("could not read file `{value}`: {err}")), + } +} + /// Parses a UNIX timestamp according to fn parse_source_date_epoch(raw: &str) -> Result, String> { let timestamp: i64 = raw diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs index 12e80d273..89c8406ad 100644 --- a/crates/typst-cli/src/world.rs +++ b/crates/typst-cli/src/world.rs @@ -112,6 +112,12 @@ impl SystemWorld { .map(|(k, v)| (k.as_str().into(), v.as_str().into_value())) .collect(); + let input_files: Dict = world_args + .input_files + .iter() + .map(|(k, v)| (k.as_str().into(), Bytes::new(v.to_owned()).into_value())) + .collect(); + let features = process_args .features .iter() @@ -120,7 +126,11 @@ impl SystemWorld { }) .collect(); - Library::builder().with_inputs(inputs).with_features(features).build() + Library::builder() + .with_inputs(inputs) + .with_input_files(input_files) + .with_features(features) + .build() }; let fonts = Fonts::searcher() diff --git a/crates/typst-library/src/foundations/mod.rs b/crates/typst-library/src/foundations/mod.rs index 8e3aa060d..43bb4d5ee 100644 --- a/crates/typst-library/src/foundations/mod.rs +++ b/crates/typst-library/src/foundations/mod.rs @@ -86,7 +86,12 @@ use crate::routines::EvalMode; use crate::{Feature, Features}; /// Hook up all `foundations` definitions. -pub(super) fn define(global: &mut Scope, inputs: Dict, features: &Features) { +pub(super) fn define( + global: &mut Scope, + inputs: Dict, + input_files: Dict, + features: &Features, +) { global.start_category(crate::Category::Foundations); global.define_type::(); global.define_type::(); @@ -117,7 +122,7 @@ pub(super) fn define(global: &mut Scope, inputs: Dict, features: &Features) { global.define_func::(); } global.define("calc", calc::module()); - global.define("sys", sys::module(inputs)); + global.define("sys", sys::module(inputs, input_files)); global.reset_category(); } diff --git a/crates/typst-library/src/foundations/sys.rs b/crates/typst-library/src/foundations/sys.rs index 7c1281047..83a5c5dec 100644 --- a/crates/typst-library/src/foundations/sys.rs +++ b/crates/typst-library/src/foundations/sys.rs @@ -3,7 +3,7 @@ use crate::foundations::{Dict, Module, Scope, Version}; /// A module with system-related things. -pub fn module(inputs: Dict) -> Module { +pub fn module(inputs: Dict, input_files: Dict) -> Module { let mut scope = Scope::deduplicating(); scope.define( "version", @@ -14,5 +14,6 @@ pub fn module(inputs: Dict) -> Module { ]), ); scope.define("inputs", inputs); + scope.define("input-files", input_files); Module::new("sys", scope) } diff --git a/crates/typst-library/src/lib.rs b/crates/typst-library/src/lib.rs index c39024f71..83c0e03e1 100644 --- a/crates/typst-library/src/lib.rs +++ b/crates/typst-library/src/lib.rs @@ -174,6 +174,7 @@ impl Default for Library { #[derive(Debug, Clone, Default)] pub struct LibraryBuilder { inputs: Option, + input_files: Option, features: Features, } @@ -184,6 +185,12 @@ impl LibraryBuilder { self } + /// Configure the input files visible through `sys.input-files`. + pub fn with_input_files(mut self, input_files: Dict) -> Self { + self.input_files = Some(input_files); + self + } + /// Configure in-development features that should be enabled. /// /// No guarantees whatsover! @@ -196,7 +203,8 @@ impl LibraryBuilder { pub fn build(self) -> Library { let math = math::module(); let inputs = self.inputs.unwrap_or_default(); - let global = global(math.clone(), inputs, &self.features); + let input_files = self.input_files.unwrap_or_default(); + let global = global(math.clone(), inputs, input_files, &self.features); Library { global: global.clone(), math, @@ -278,10 +286,10 @@ impl Category { } /// Construct the module with global definitions. -fn global(math: Module, inputs: Dict, features: &Features) -> Module { +fn global(math: Module, inputs: Dict, input_files: Dict, features: &Features) -> Module { let mut global = Scope::deduplicating(); - self::foundations::define(&mut global, inputs, features); + self::foundations::define(&mut global, inputs, input_files, features); self::model::define(&mut global); self::text::define(&mut global); self::layout::define(&mut global); diff --git a/docs/reference/groups.yml b/docs/reference/groups.yml index 8fea3a1f2..d07ba74fa 100644 --- a/docs/reference/groups.yml +++ b/docs/reference/groups.yml @@ -151,6 +151,14 @@ The value is always of type [string]($str). More complex data may be parsed manually using functions like [`json.decode`]($json.decode). + - The `sys.input-files` [dictionary], which makes external files + available to the project. An input specified in the command line as + `--input key=path` becomes available under `sys.input-files.key` as + the content of the specified file. To include spaces in the path, it may + be enclosed with single or double quotes. + + The value is always of type [byte buffers]($bytes). + - name: sym title: General category: symbols