Key/Value data from CLI (#2894)

This commit is contained in:
Laurenz 2023-12-18 12:18:41 +01:00 committed by GitHub
parent 356bdeba18
commit 22ba6825db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 94 additions and 23 deletions

View File

@ -1,6 +1,7 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::path::PathBuf; use std::path::PathBuf;
use clap::builder::ValueParser;
use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum}; use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
use semver::Version; use semver::Version;
@ -116,6 +117,15 @@ pub struct SharedArgs {
#[clap(long = "root", env = "TYPST_ROOT", value_name = "DIR")] #[clap(long = "root", env = "TYPST_ROOT", value_name = "DIR")]
pub root: Option<PathBuf>, pub root: Option<PathBuf>,
/// Add a string key-value pair visible through `sys.inputs`
#[clap(
long = "input",
value_name = "key=value",
action = ArgAction::Append,
value_parser = ValueParser::new(parse_input_pair),
)]
pub inputs: Vec<(String, String)>,
/// Adds additional directories to search for fonts /// Adds additional directories to search for fonts
#[clap( #[clap(
long = "font-path", long = "font-path",
@ -134,6 +144,22 @@ pub struct SharedArgs {
pub diagnostic_format: DiagnosticFormat, pub diagnostic_format: DiagnosticFormat,
} }
/// Parses key/value pairs split by the first equal sign.
///
/// This function will return an error if the argument contains no equals sign
/// or contains the key (before the equals sign) is empty.
fn parse_input_pair(raw: &str) -> Result<(String, String), String> {
let (key, val) = raw
.split_once('=')
.ok_or("input must be a key and a value separated by an equal sign")?;
let key = key.trim().to_owned();
if key.is_empty() {
return Err("the key was missing or empty".to_owned());
}
let val = val.trim().to_owned();
Ok((key, val))
}
/// Lists all discovered fonts in system and custom font paths /// Lists all discovered fonts in system and custom font paths
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
pub struct FontsCommand { pub struct FontsCommand {

View File

@ -7,7 +7,7 @@ use chrono::{DateTime, Datelike, Local};
use comemo::Prehashed; use comemo::Prehashed;
use ecow::eco_format; use ecow::eco_format;
use typst::diag::{FileError, FileResult, StrResult}; use typst::diag::{FileError, FileResult, StrResult};
use typst::foundations::{Bytes, Datetime}; use typst::foundations::{Bytes, Datetime, Dict, IntoValue};
use typst::syntax::{FileId, Source, VirtualPath}; use typst::syntax::{FileId, Source, VirtualPath};
use typst::text::{Font, FontBook}; use typst::text::{Font, FontBook};
use typst::{Library, World}; use typst::{Library, World};
@ -68,14 +68,25 @@ impl SystemWorld {
// Resolve the virtual path of the main file within the project root. // Resolve the virtual path of the main file within the project root.
let main_path = VirtualPath::within_root(&input, &root) let main_path = VirtualPath::within_root(&input, &root)
.ok_or("input file must be contained in project root")?; .ok_or("source file must be contained in project root")?;
let library = {
// Convert the input pairs to a dictionary.
let inputs: Dict = command
.inputs
.iter()
.map(|(k, v)| (k.as_str().into(), v.as_str().into_value()))
.collect();
Library::builder().with_inputs(inputs).build()
};
Ok(Self { Ok(Self {
workdir: std::env::current_dir().ok(), workdir: std::env::current_dir().ok(),
input, input,
root, root,
main: FileId::new(None, main_path), main: FileId::new(None, main_path),
library: Prehashed::new(Library::build()), library: Prehashed::new(library),
book: Prehashed::new(searcher.book), book: Prehashed::new(searcher.book),
fonts: searcher.fonts, fonts: searcher.fonts,
slots: RwLock::new(HashMap::new()), slots: RwLock::new(HashMap::new()),

View File

@ -55,7 +55,7 @@ static GROUPS: Lazy<Vec<GroupData>> = Lazy::new(|| {
}); });
static LIBRARY: Lazy<Prehashed<Library>> = Lazy::new(|| { static LIBRARY: Lazy<Prehashed<Library>> = Lazy::new(|| {
let mut lib = Library::build(); let mut lib = Library::default();
lib.styles lib.styles
.set(PageElem::set_width(Smart::Custom(Abs::pt(240.0).into()))); .set(PageElem::set_width(Smart::Custom(Abs::pt(240.0).into())));
lib.styles.set(PageElem::set_height(Smart::Auto)); lib.styles.set(PageElem::set_height(Smart::Auto));

View File

@ -83,7 +83,7 @@ use crate::syntax::Spanned;
pub static FOUNDATIONS: Category; pub static FOUNDATIONS: Category;
/// Hook up all `foundations` definitions. /// Hook up all `foundations` definitions.
pub(super) fn define(global: &mut Scope) { pub(super) fn define(global: &mut Scope, inputs: Dict) {
global.category(FOUNDATIONS); global.category(FOUNDATIONS);
global.define_type::<bool>(); global.define_type::<bool>();
global.define_type::<i64>(); global.define_type::<i64>();
@ -110,7 +110,7 @@ pub(super) fn define(global: &mut Scope) {
global.define_func::<eval>(); global.define_func::<eval>();
global.define_func::<style>(); global.define_func::<style>();
global.define_module(calc::module()); global.define_module(calc::module());
global.define_module(sys::module()); global.define_module(sys::module(inputs));
} }
/// Fails with an error. /// Fails with an error.

View File

@ -1,9 +1,9 @@
//! System-related things. //! System-related things.
use crate::foundations::{Module, Scope, Version}; use crate::foundations::{Dict, Module, Scope, Version};
/// A module with system-related things. /// A module with system-related things.
pub fn module() -> Module { pub fn module(inputs: Dict) -> Module {
let mut scope = Scope::deduplicating(); let mut scope = Scope::deduplicating();
scope.define( scope.define(
"version", "version",
@ -13,5 +13,6 @@ pub fn module() -> Module {
env!("CARGO_PKG_VERSION_PATCH").parse::<u32>().unwrap(), env!("CARGO_PKG_VERSION_PATCH").parse::<u32>().unwrap(),
]), ]),
); );
scope.define("inputs", inputs);
Module::new("sys", scope) Module::new("sys", scope)
} }

View File

@ -66,7 +66,7 @@ use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult};
use crate::engine::{Engine, Route}; use crate::engine::{Engine, Route};
use crate::eval::Tracer; use crate::eval::Tracer;
use crate::foundations::{ use crate::foundations::{
Array, Bytes, Content, Datetime, Module, Scope, StyleChain, Styles, Array, Bytes, Content, Datetime, Dict, Module, Scope, StyleChain, Styles,
}; };
use crate::introspection::{Introspector, Locator}; use crate::introspection::{Introspector, Locator};
use crate::layout::{Align, Dir, LayoutRoot}; use crate::layout::{Align, Dir, LayoutRoot};
@ -252,25 +252,48 @@ pub struct Library {
} }
impl Library { impl Library {
/// Construct the standard library. /// Create a new builder for a library.
pub fn build() -> Self { pub fn builder() -> LibraryBuilder {
let math = math::module(); LibraryBuilder::default()
let global = global(math.clone());
Self { global, math, styles: Styles::new() }
} }
} }
impl Default for Library { impl Default for Library {
/// Constructs the standard library with the default configuration.
fn default() -> Self { fn default() -> Self {
Self::build() Self::builder().build()
}
}
/// Configurable builder for the standard library.
///
/// This struct is created by [`Library::builder`].
#[derive(Debug, Clone, Default)]
pub struct LibraryBuilder {
inputs: Option<Dict>,
}
impl LibraryBuilder {
/// Configure the inputs visible through `sys.inputs`.
pub fn with_inputs(mut self, inputs: Dict) -> Self {
self.inputs = Some(inputs);
self
}
/// Consumes the builder and returns a `Library`.
pub fn build(self) -> Library {
let math = math::module();
let inputs = self.inputs.unwrap_or_default();
let global = global(math.clone(), inputs);
Library { global, math, styles: Styles::new() }
} }
} }
/// Construct the module with global definitions. /// Construct the module with global definitions.
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn global(math: Module) -> Module { fn global(math: Module, inputs: Dict) -> Module {
let mut global = Scope::deduplicating(); let mut global = Scope::deduplicating();
self::foundations::define(&mut global); self::foundations::define(&mut global, inputs);
self::model::define(&mut global); self::model::define(&mut global);
self::text::define(&mut global); self::text::define(&mut global);
global.reset_category(); global.reset_category();

View File

@ -129,9 +129,19 @@
details: | details: |
Module for system interactions. Module for system interactions.
Currently, this module defines a single item: The `sys.version` constant This module defines the following items:
(of type [`version`]($version)), that specifies the currently active
Typst compiler version. - The `sys.version` constant (of type [`version`]($version)) that specifies
the currently active Typst compiler version.
- The `sys.inputs` [dictionary]($dictionary), which makes external inputs
available to the project. An input specified in the command line as
`--input key=value` becomes available under `sys.inputs.key` as
`{"value"}`. To include spaces in the value, it may be enclosed with
single or double quotes.
The value is always of type [string]($str). More complex data
may be parsed manually using functions like [`json.decode`]($json.decode).
- name: sym - name: sym
title: General title: General

View File

@ -24,7 +24,7 @@ impl FuzzWorld {
let font = Font::new(FONT.into(), 0).unwrap(); let font = Font::new(FONT.into(), 0).unwrap();
let book = FontBook::from_fonts([&font]); let book = FontBook::from_fonts([&font]);
Self { Self {
library: Prehashed::new(Library::build()), library: Prehashed::new(Library::default()),
book: Prehashed::new(book), book: Prehashed::new(book),
font, font,
source: Source::detached(text), source: Source::detached(text),

View File

@ -91,7 +91,7 @@ impl BenchWorld {
let book = FontBook::from_fonts([&font]); let book = FontBook::from_fonts([&font]);
Self { Self {
library: Prehashed::new(Library::build()), library: Prehashed::new(Library::default()),
book: Prehashed::new(book), book: Prehashed::new(book),
font, font,
source: Source::detached(TEXT), source: Source::detached(TEXT),

View File

@ -192,7 +192,7 @@ fn library() -> Library {
// Set page width to 120pt with 10pt margins, so that the inner page is // Set page width to 120pt with 10pt margins, so that the inner page is
// exactly 100pt wide. Page height is unbounded and font size is 10pt so // exactly 100pt wide. Page height is unbounded and font size is 10pt so
// that it multiplies to nice round numbers. // that it multiplies to nice round numbers.
let mut lib = Library::build(); let mut lib = Library::default();
lib.styles lib.styles
.set(PageElem::set_width(Smart::Custom(Abs::pt(120.0).into()))); .set(PageElem::set_width(Smart::Custom(Abs::pt(120.0).into())));
lib.styles.set(PageElem::set_height(Smart::Auto)); lib.styles.set(PageElem::set_height(Smart::Auto));