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::path::PathBuf;
use clap::builder::ValueParser;
use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
use semver::Version;
@ -116,6 +117,15 @@ pub struct SharedArgs {
#[clap(long = "root", env = "TYPST_ROOT", value_name = "DIR")]
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
#[clap(
long = "font-path",
@ -134,6 +144,22 @@ pub struct SharedArgs {
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
#[derive(Debug, Clone, Parser)]
pub struct FontsCommand {

View File

@ -7,7 +7,7 @@ use chrono::{DateTime, Datelike, Local};
use comemo::Prehashed;
use ecow::eco_format;
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::text::{Font, FontBook};
use typst::{Library, World};
@ -68,14 +68,25 @@ impl SystemWorld {
// Resolve the virtual path of the main file within the project 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 {
workdir: std::env::current_dir().ok(),
input,
root,
main: FileId::new(None, main_path),
library: Prehashed::new(Library::build()),
library: Prehashed::new(library),
book: Prehashed::new(searcher.book),
fonts: searcher.fonts,
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(|| {
let mut lib = Library::build();
let mut lib = Library::default();
lib.styles
.set(PageElem::set_width(Smart::Custom(Abs::pt(240.0).into())));
lib.styles.set(PageElem::set_height(Smart::Auto));

View File

@ -83,7 +83,7 @@ use crate::syntax::Spanned;
pub static FOUNDATIONS: Category;
/// 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.define_type::<bool>();
global.define_type::<i64>();
@ -110,7 +110,7 @@ pub(super) fn define(global: &mut Scope) {
global.define_func::<eval>();
global.define_func::<style>();
global.define_module(calc::module());
global.define_module(sys::module());
global.define_module(sys::module(inputs));
}
/// Fails with an error.

View File

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

View File

@ -66,7 +66,7 @@ use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult};
use crate::engine::{Engine, Route};
use crate::eval::Tracer;
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::layout::{Align, Dir, LayoutRoot};
@ -252,25 +252,48 @@ pub struct Library {
}
impl Library {
/// Construct the standard library.
pub fn build() -> Self {
let math = math::module();
let global = global(math.clone());
Self { global, math, styles: Styles::new() }
/// Create a new builder for a library.
pub fn builder() -> LibraryBuilder {
LibraryBuilder::default()
}
}
impl Default for Library {
/// Constructs the standard library with the default configuration.
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.
#[tracing::instrument(skip_all)]
fn global(math: Module) -> Module {
fn global(math: Module, inputs: Dict) -> Module {
let mut global = Scope::deduplicating();
self::foundations::define(&mut global);
self::foundations::define(&mut global, inputs);
self::model::define(&mut global);
self::text::define(&mut global);
global.reset_category();

View File

@ -129,9 +129,19 @@
details: |
Module for system interactions.
Currently, this module defines a single item: The `sys.version` constant
(of type [`version`]($version)), that specifies the currently active
Typst compiler version.
This module defines the following items:
- 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
title: General

View File

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

View File

@ -91,7 +91,7 @@ impl BenchWorld {
let book = FontBook::from_fonts([&font]);
Self {
library: Prehashed::new(Library::build()),
library: Prehashed::new(Library::default()),
book: Prehashed::new(book),
font,
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
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
// that it multiplies to nice round numbers.
let mut lib = Library::build();
let mut lib = Library::default();
lib.styles
.set(PageElem::set_width(Smart::Custom(Abs::pt(120.0).into())));
lib.styles.set(PageElem::set_height(Smart::Auto));