Query-System for metadata (#1812)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
parent
823fc5e5c4
commit
357bce56f5
67
Cargo.lock
generated
67
Cargo.lock
generated
@ -450,7 +450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"hashbrown",
|
||||
"hashbrown 0.12.3",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
@ -531,6 +531,12 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.1"
|
||||
@ -738,6 +744,12 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "hayagriva"
|
||||
version = "0.3.0"
|
||||
@ -987,8 +999,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
"hashbrown 0.12.3",
|
||||
"rayon",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1003,7 +1026,7 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
"dashmap",
|
||||
"env_logger",
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"is-terminal",
|
||||
"itoa",
|
||||
"log",
|
||||
@ -1452,7 +1475,7 @@ dependencies = [
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"image",
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"itertools",
|
||||
"libdeflater",
|
||||
"log",
|
||||
@ -1571,7 +1594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"line-wrap",
|
||||
"quick-xml 0.28.2",
|
||||
"serde",
|
||||
@ -2015,12 +2038,25 @@ version = "0.8.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"ryu",
|
||||
"serde",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574"
|
||||
dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
@ -2413,7 +2449,7 @@ version = "0.19.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d964908cec0d030b812013af25a0e57fddfadb1e066ecc6681d86253129d4f"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@ -2523,7 +2559,7 @@ dependencies = [
|
||||
"fontdb",
|
||||
"if_chain",
|
||||
"image",
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"log",
|
||||
"miniz_oxide",
|
||||
"oklab",
|
||||
@ -2573,6 +2609,9 @@ dependencies = [
|
||||
"once_cell",
|
||||
"open",
|
||||
"same-file",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml 0.9.25",
|
||||
"siphasher",
|
||||
"tar",
|
||||
"tempfile",
|
||||
@ -2596,7 +2635,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"pulldown-cmark",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"serde_yaml 0.8.26",
|
||||
"syntect",
|
||||
"typed-arena",
|
||||
"typst",
|
||||
@ -2629,7 +2668,7 @@ dependencies = [
|
||||
"roxmltree",
|
||||
"rustybuzz",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"serde_yaml 0.8.26",
|
||||
"smallvec",
|
||||
"syntect",
|
||||
"time",
|
||||
@ -2795,6 +2834,12 @@ version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446c96c6dd42604779487f0a981060717156648c1706aa1f464677f03c6cc059"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
|
||||
|
||||
[[package]]
|
||||
name = "unscanny"
|
||||
version = "0.1.0"
|
||||
@ -3243,7 +3288,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a94fb32d2b438e3fddf901fbfe9eb87b34d63853ca6c6da5d2ab7e27031e0bae"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"serde_yaml 0.8.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -34,6 +34,9 @@ notify = "5"
|
||||
once_cell = "1"
|
||||
open = "4.0.2"
|
||||
same-file = "1"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.9"
|
||||
siphasher = "0.3"
|
||||
tar = "0.4"
|
||||
tempfile = "3.5.0"
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{ArgAction, Parser, Subcommand, ValueEnum};
|
||||
use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
|
||||
|
||||
/// The Typst compiler.
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
@ -29,6 +29,9 @@ pub enum Command {
|
||||
#[command(visible_alias = "w")]
|
||||
Watch(CompileCommand),
|
||||
|
||||
/// Processes an input file to extract provided metadata
|
||||
Query(QueryCommand),
|
||||
|
||||
/// Lists all discovered fonts in system and custom font paths
|
||||
Fonts(FontsCommand),
|
||||
}
|
||||
@ -36,12 +39,71 @@ pub enum Command {
|
||||
/// Compiles the input file into a PDF file
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
pub struct CompileCommand {
|
||||
/// Path to input Typst file
|
||||
pub input: PathBuf,
|
||||
/// Shared arguments.
|
||||
#[clap(flatten)]
|
||||
pub common: SharedArgs,
|
||||
|
||||
/// Path to output PDF file or PNG file(s)
|
||||
pub output: Option<PathBuf>,
|
||||
|
||||
/// Opens the output file using the default viewer after compilation
|
||||
#[arg(long = "open")]
|
||||
pub open: Option<Option<String>>,
|
||||
|
||||
/// The PPI (pixels per inch) to use for PNG export
|
||||
#[arg(long = "ppi", default_value_t = 144.0)]
|
||||
pub ppi: f32,
|
||||
|
||||
/// Produces a flamegraph of the compilation process
|
||||
#[arg(long = "flamegraph", value_name = "OUTPUT_SVG")]
|
||||
pub flamegraph: Option<Option<PathBuf>>,
|
||||
}
|
||||
|
||||
impl CompileCommand {
|
||||
/// The output path.
|
||||
pub fn output(&self) -> PathBuf {
|
||||
self.output
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.common.input.with_extension("pdf"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes an input file to extract provided metadata
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
pub struct QueryCommand {
|
||||
/// Shared arguments.
|
||||
#[clap(flatten)]
|
||||
pub common: SharedArgs,
|
||||
|
||||
/// Define what elements to retrieve
|
||||
pub selector: String,
|
||||
|
||||
/// Extract just one field from all retrieved elements
|
||||
#[clap(long = "field")]
|
||||
pub field: Option<String>,
|
||||
|
||||
/// Expect and retrieve exactly one element
|
||||
#[clap(long = "one", default_value = "false")]
|
||||
pub one: bool,
|
||||
|
||||
/// The format to serialization in
|
||||
#[clap(long = "format", default_value = "json")]
|
||||
pub format: SerializationFormat,
|
||||
}
|
||||
|
||||
// Output file format for query command
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)]
|
||||
pub enum SerializationFormat {
|
||||
Json,
|
||||
Yaml,
|
||||
}
|
||||
|
||||
/// Common arguments of compile, watch, and query.
|
||||
#[derive(Debug, Clone, Args)]
|
||||
pub struct SharedArgs {
|
||||
/// Path to input Typst file
|
||||
pub input: PathBuf,
|
||||
|
||||
/// Configures the project root
|
||||
#[clap(long = "root", env = "TYPST_ROOT", value_name = "DIR")]
|
||||
pub root: Option<PathBuf>,
|
||||
@ -55,14 +117,6 @@ pub struct CompileCommand {
|
||||
)]
|
||||
pub font_paths: Vec<PathBuf>,
|
||||
|
||||
/// Opens the output file using the default viewer after compilation
|
||||
#[arg(long = "open")]
|
||||
pub open: Option<Option<String>>,
|
||||
|
||||
/// The PPI (pixels per inch) to use for PNG export
|
||||
#[arg(long = "ppi", default_value_t = 144.0)]
|
||||
pub ppi: f32,
|
||||
|
||||
/// In which format to emit diagnostics
|
||||
#[clap(
|
||||
long,
|
||||
@ -70,19 +124,6 @@ pub struct CompileCommand {
|
||||
value_parser = clap::value_parser!(DiagnosticFormat)
|
||||
)]
|
||||
pub diagnostic_format: DiagnosticFormat,
|
||||
|
||||
/// Produces a flamegraph of the compilation process
|
||||
#[arg(long = "flamegraph", value_name = "OUTPUT_SVG")]
|
||||
pub flamegraph: Option<Option<PathBuf>>,
|
||||
}
|
||||
|
||||
impl CompileCommand {
|
||||
/// The output path.
|
||||
pub fn output(&self) -> PathBuf {
|
||||
self.output
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.input.with_extension("pdf"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Lists all discovered fonts in system and custom font paths
|
||||
|
@ -21,7 +21,7 @@ type CodespanError = codespan_reporting::files::Error;
|
||||
|
||||
/// Execute a compilation command.
|
||||
pub fn compile(mut command: CompileCommand) -> StrResult<()> {
|
||||
let mut world = SystemWorld::new(&command)?;
|
||||
let mut world = SystemWorld::new(&command.common)?;
|
||||
compile_once(&mut world, &mut command, false)?;
|
||||
Ok(())
|
||||
}
|
||||
@ -42,14 +42,12 @@ pub fn compile_once(
|
||||
Status::Compiling.print(command).unwrap();
|
||||
}
|
||||
|
||||
// Reset everything and ensure that the main file is still present.
|
||||
// Reset everything and ensure that the main file is present.
|
||||
world.reset();
|
||||
world.source(world.main()).map_err(|err| err.to_string())?;
|
||||
|
||||
let mut tracer = Tracer::default();
|
||||
|
||||
let result = typst::compile(world, &mut tracer);
|
||||
|
||||
let warnings = tracer.warnings();
|
||||
|
||||
match result {
|
||||
@ -67,7 +65,7 @@ pub fn compile_once(
|
||||
}
|
||||
}
|
||||
|
||||
print_diagnostics(world, &[], &warnings, command.diagnostic_format)
|
||||
print_diagnostics(world, &[], &warnings, command.common.diagnostic_format)
|
||||
.map_err(|_| "failed to print diagnostics")?;
|
||||
|
||||
if let Some(open) = command.open.take() {
|
||||
@ -84,8 +82,13 @@ pub fn compile_once(
|
||||
Status::Error.print(command).unwrap();
|
||||
}
|
||||
|
||||
print_diagnostics(world, &errors, &warnings, command.diagnostic_format)
|
||||
.map_err(|_| "failed to print diagnostics")?;
|
||||
print_diagnostics(
|
||||
world,
|
||||
&errors,
|
||||
&warnings,
|
||||
command.common.diagnostic_format,
|
||||
)
|
||||
.map_err(|_| "failed to print diagnostics")?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,7 +155,7 @@ fn open_file(open: Option<&str>, path: &Path) -> StrResult<()> {
|
||||
}
|
||||
|
||||
/// Print diagnostic messages to the terminal.
|
||||
fn print_diagnostics(
|
||||
pub fn print_diagnostics(
|
||||
world: &SystemWorld,
|
||||
errors: &[SourceDiagnostic],
|
||||
warnings: &[SourceDiagnostic],
|
||||
|
@ -2,6 +2,7 @@ mod args;
|
||||
mod compile;
|
||||
mod fonts;
|
||||
mod package;
|
||||
mod query;
|
||||
mod tracing;
|
||||
mod watch;
|
||||
mod world;
|
||||
@ -36,6 +37,7 @@ fn main() -> ExitCode {
|
||||
let res = match arguments.command {
|
||||
Command::Compile(command) => crate::compile::compile(command),
|
||||
Command::Watch(command) => crate::watch::watch(command),
|
||||
Command::Query(command) => crate::query::query(command),
|
||||
Command::Fonts(command) => crate::fonts::fonts(command),
|
||||
};
|
||||
|
||||
|
114
crates/typst-cli/src/query.rs
Normal file
114
crates/typst-cli/src/query.rs
Normal file
@ -0,0 +1,114 @@
|
||||
use comemo::Track;
|
||||
use serde::Serialize;
|
||||
use typst::diag::{bail, StrResult};
|
||||
use typst::eval::{eval_string, EvalMode, Tracer};
|
||||
use typst::model::Introspector;
|
||||
use typst::World;
|
||||
use typst_library::prelude::*;
|
||||
|
||||
use crate::args::{QueryCommand, SerializationFormat};
|
||||
use crate::compile::print_diagnostics;
|
||||
use crate::set_failed;
|
||||
use crate::world::SystemWorld;
|
||||
|
||||
/// Execute a query command.
|
||||
pub fn query(command: QueryCommand) -> StrResult<()> {
|
||||
let mut world = SystemWorld::new(&command.common)?;
|
||||
tracing::info!("Starting querying");
|
||||
|
||||
// Reset everything and ensure that the main file is present.
|
||||
world.reset();
|
||||
world.source(world.main()).map_err(|err| err.to_string())?;
|
||||
|
||||
let mut tracer = Tracer::default();
|
||||
let result = typst::compile(&world, &mut tracer);
|
||||
let warnings = tracer.warnings();
|
||||
|
||||
match result {
|
||||
// Retrieve and print query results.
|
||||
Ok(document) => {
|
||||
let data = retrieve(&world, &command, &document)?;
|
||||
let serialized = format(data, &command)?;
|
||||
println!("{serialized}");
|
||||
print_diagnostics(&world, &[], &warnings, command.common.diagnostic_format)
|
||||
.map_err(|_| "failed to print diagnostics")?;
|
||||
}
|
||||
|
||||
// Print diagnostics.
|
||||
Err(errors) => {
|
||||
set_failed();
|
||||
print_diagnostics(
|
||||
&world,
|
||||
&errors,
|
||||
&warnings,
|
||||
command.common.diagnostic_format,
|
||||
)
|
||||
.map_err(|_| "failed to print diagnostics")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve the matches for the selector.
|
||||
fn retrieve(
|
||||
world: &dyn World,
|
||||
command: &QueryCommand,
|
||||
document: &Document,
|
||||
) -> StrResult<Vec<Content>> {
|
||||
let selector = eval_string(
|
||||
world.track(),
|
||||
&command.selector,
|
||||
Span::detached(),
|
||||
EvalMode::Code,
|
||||
Scope::default(),
|
||||
)
|
||||
.map_err(|errors| {
|
||||
let mut message = EcoString::from("failed to evaluate selector");
|
||||
for (i, error) in errors.into_iter().enumerate() {
|
||||
message.push_str(if i == 0 { ": " } else { ", " });
|
||||
message.push_str(&error.message);
|
||||
}
|
||||
message
|
||||
})?
|
||||
.cast::<LocatableSelector>()?;
|
||||
|
||||
Ok(Introspector::new(&document.pages)
|
||||
.query(&selector.0)
|
||||
.into_iter()
|
||||
.map(|x| x.into_inner())
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
/// Format the query result in the output format.
|
||||
fn format(elements: Vec<Content>, command: &QueryCommand) -> StrResult<String> {
|
||||
if command.one && elements.len() != 1 {
|
||||
bail!("expected exactly one element, found {}", elements.len())
|
||||
}
|
||||
|
||||
let mapped: Vec<_> = elements
|
||||
.into_iter()
|
||||
.filter_map(|c| match &command.field {
|
||||
Some(field) => c.field(field),
|
||||
_ => Some(c.into_value()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
if command.one {
|
||||
serialize(&mapped[0], command.format)
|
||||
} else {
|
||||
serialize(&mapped, command.format)
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize data to the output format.
|
||||
fn serialize(data: &impl Serialize, format: SerializationFormat) -> StrResult<String> {
|
||||
match format {
|
||||
SerializationFormat::Json => {
|
||||
serde_json::to_string_pretty(data).map_err(|e| eco_format!("{e}"))
|
||||
}
|
||||
SerializationFormat::Yaml => {
|
||||
serde_yaml::to_string(&data).map_err(|e| eco_format!("{e}"))
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ use crate::world::SystemWorld;
|
||||
/// Execute a watching compilation command.
|
||||
pub fn watch(mut command: CompileCommand) -> StrResult<()> {
|
||||
// Create the world that serves sources, files, and fonts.
|
||||
let mut world = SystemWorld::new(&command)?;
|
||||
let mut world = SystemWorld::new(&command.common)?;
|
||||
|
||||
// Perform initial compilation.
|
||||
compile_once(&mut world, &mut command, true)?;
|
||||
@ -159,7 +159,7 @@ impl Status {
|
||||
w.set_color(&color)?;
|
||||
write!(w, "watching")?;
|
||||
w.reset()?;
|
||||
writeln!(w, " {}", command.input.display())?;
|
||||
writeln!(w, " {}", command.common.input.display())?;
|
||||
|
||||
w.set_color(&color)?;
|
||||
write!(w, "writing to")?;
|
||||
|
@ -15,7 +15,7 @@ use typst::syntax::{FileId, Source};
|
||||
use typst::util::PathExt;
|
||||
use typst::World;
|
||||
|
||||
use crate::args::CompileCommand;
|
||||
use crate::args::SharedArgs;
|
||||
use crate::fonts::{FontSearcher, FontSlot};
|
||||
use crate::package::prepare_package;
|
||||
|
||||
@ -44,7 +44,7 @@ pub struct SystemWorld {
|
||||
|
||||
impl SystemWorld {
|
||||
/// Create a new system world.
|
||||
pub fn new(command: &CompileCommand) -> StrResult<Self> {
|
||||
pub fn new(command: &SharedArgs) -> StrResult<Self> {
|
||||
let mut searcher = FontSearcher::new();
|
||||
searcher.search(&command.font_paths);
|
||||
|
||||
|
87
crates/typst-library/src/meta/metadata.rs
Normal file
87
crates/typst-library/src/meta/metadata.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Exposes a value to the query system without producing visible content.
|
||||
///
|
||||
/// This element can be queried for with the [`query`]($func/query) function and
|
||||
/// the command line `typst query` command. Its purpose is to expose an
|
||||
/// arbitrary value to the introspection system. To identify a metadata value
|
||||
/// among others, you can attach a [`label`]($type/label) to it and query for
|
||||
/// that label.
|
||||
///
|
||||
/// ```typ
|
||||
/// #metadata("This is a note") <note>
|
||||
/// ```
|
||||
///
|
||||
/// ## Within Typst: `query` function { #within-typst }
|
||||
/// Metadata can be retrieved from with the [`query`]($func/query) function
|
||||
/// (like other elements):
|
||||
///
|
||||
/// ```example
|
||||
/// // Put metadata somewhere.
|
||||
/// #metadata("This is a note") <note>
|
||||
///
|
||||
/// // And find it from anywhere else.
|
||||
/// #locate(loc => {
|
||||
/// query(<note>, loc).first().value
|
||||
/// })
|
||||
/// ```
|
||||
///
|
||||
/// ## Outside of Typst: `typst query` command { #outside-of-typst }
|
||||
/// You can also retrieve the metadata from the command line with the
|
||||
/// `typst query` command. This command executes an arbitrary query on the
|
||||
/// document and returns the resulting elements in serialized form.
|
||||
///
|
||||
/// The `metadata` element is especially useful for command line queries because
|
||||
/// it allows you to expose arbitrary values to the outside world. However,
|
||||
/// `typst query` also works with other elements `metadata` and complex
|
||||
/// [selectors]($type/selector) like `{heading.where(level: 1)}`.
|
||||
///
|
||||
/// ```sh
|
||||
/// $ typst query example.typ "<note>"
|
||||
/// [
|
||||
/// {
|
||||
/// "func": "metadata",
|
||||
/// "value": "This is a note",
|
||||
/// "label": "<note>"
|
||||
/// }
|
||||
/// ]
|
||||
/// ```
|
||||
///
|
||||
/// Frequently, you're interested in only one specific field of the resulting
|
||||
/// elements. In the case of the `metadata` element, the `value` field is the
|
||||
/// interesting one. You can extract just this field with the `--field`
|
||||
/// argument.
|
||||
///
|
||||
/// ```sh
|
||||
/// $ typst query example.typ "<note>" --field value
|
||||
/// ["This is a note"]
|
||||
/// ```
|
||||
///
|
||||
/// If you are interested in just a single element, you can use the `--one`
|
||||
/// flag to extract just it.
|
||||
///
|
||||
/// ```sh
|
||||
/// $ typst query example.typ "<note>" --field value --one
|
||||
/// "This is a note"
|
||||
/// ```
|
||||
///
|
||||
/// Display: Metadata
|
||||
/// Category: meta
|
||||
#[element(Behave, Show, Locatable)]
|
||||
pub struct MetadataElem {
|
||||
/// The value to embed into the document.
|
||||
#[required]
|
||||
pub value: Value,
|
||||
}
|
||||
|
||||
impl Show for MetadataElem {
|
||||
fn show(&self, _vt: &mut Vt, _styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(Content::empty())
|
||||
}
|
||||
}
|
||||
|
||||
impl Behave for MetadataElem {
|
||||
fn behaviour(&self) -> Behaviour {
|
||||
Behaviour::Ignorant
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ mod figure;
|
||||
mod footnote;
|
||||
mod heading;
|
||||
mod link;
|
||||
mod metadata;
|
||||
mod numbering;
|
||||
mod outline;
|
||||
mod query;
|
||||
@ -22,6 +23,7 @@ pub use self::figure::*;
|
||||
pub use self::footnote::*;
|
||||
pub use self::heading::*;
|
||||
pub use self::link::*;
|
||||
pub use self::metadata::*;
|
||||
pub use self::numbering::*;
|
||||
pub use self::outline::*;
|
||||
pub use self::query::*;
|
||||
@ -50,6 +52,7 @@ pub(super) fn define(global: &mut Scope) {
|
||||
global.define("state", state_func());
|
||||
global.define("query", query_func());
|
||||
global.define("selector", selector_func());
|
||||
global.define("metadata", MetadataElem::func());
|
||||
}
|
||||
|
||||
/// The named with which an element is referenced.
|
||||
|
@ -26,7 +26,7 @@ flate2 = "1"
|
||||
fontdb = "0.13"
|
||||
if_chain = "1"
|
||||
image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] }
|
||||
indexmap = "1.9.3"
|
||||
indexmap = { version = "1.9.3", features = ["serde"] }
|
||||
log = "0.4"
|
||||
miniz_oxide = "0.7"
|
||||
oklab = "1"
|
||||
|
@ -3,9 +3,11 @@ use std::fmt::{self, Debug, Formatter};
|
||||
use std::ops::{Add, AddAssign};
|
||||
|
||||
use ecow::{eco_format, EcoString, EcoVec};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{ops, Args, CastInfo, FromValue, Func, IntoValue, Reflect, Value, Vm};
|
||||
use crate::diag::{At, SourceResult, StrResult};
|
||||
use crate::eval::ops::{add, mul};
|
||||
use crate::syntax::Span;
|
||||
use crate::util::pretty_array_like;
|
||||
|
||||
@ -29,12 +31,11 @@ macro_rules! __array {
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::__array as array;
|
||||
use crate::eval::ops::{add, mul};
|
||||
#[doc(hidden)]
|
||||
pub use ecow::eco_vec;
|
||||
|
||||
/// A reference counted array with value semantics.
|
||||
#[derive(Default, Clone, PartialEq, Hash)]
|
||||
#[derive(Default, Clone, PartialEq, Hash, Serialize)]
|
||||
pub struct Array(EcoVec<Value>);
|
||||
|
||||
impl Array {
|
||||
|
@ -5,6 +5,7 @@ use std::sync::Arc;
|
||||
|
||||
use comemo::Prehashed;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::diag::StrResult;
|
||||
|
||||
@ -95,6 +96,19 @@ impl Debug for Bytes {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Bytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if serializer.is_human_readable() {
|
||||
serializer.serialize_str(&eco_format!("{self:?}"))
|
||||
} else {
|
||||
serializer.serialize_bytes(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The out of bounds access error message.
|
||||
#[cold]
|
||||
fn out_of_bounds(index: i64, len: usize) -> EcoString {
|
||||
|
@ -4,6 +4,7 @@ use std::ops::{Add, AddAssign};
|
||||
use std::sync::Arc;
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use super::{array, Array, Str, Value};
|
||||
use crate::diag::StrResult;
|
||||
@ -188,6 +189,15 @@ impl Hash for Dict {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Dict {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<(Str, Value)> for Dict {
|
||||
fn extend<T: IntoIterator<Item = (Str, Value)>>(&mut self, iter: T) {
|
||||
Arc::make_mut(&mut self.0).extend(iter);
|
||||
|
@ -4,6 +4,7 @@ use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Add, AddAssign, Deref, Range};
|
||||
|
||||
use ecow::EcoString;
|
||||
use serde::Serialize;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use super::{cast, dict, Args, Array, Dict, Func, IntoValue, Value, Vm};
|
||||
@ -25,7 +26,7 @@ pub use crate::__format_str as format_str;
|
||||
pub use ecow::eco_format;
|
||||
|
||||
/// An immutable reference counted string.
|
||||
#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)]
|
||||
pub struct Str(EcoString);
|
||||
|
||||
impl Str {
|
||||
|
@ -4,6 +4,7 @@ use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use ecow::EcoString;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::diag::{bail, StrResult};
|
||||
|
||||
@ -135,6 +136,15 @@ impl Display for Symbol {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Symbol {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_char(self.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl List {
|
||||
/// The characters that are covered by this list.
|
||||
fn variants(&self) -> Variants<'_> {
|
||||
|
@ -5,6 +5,7 @@ use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
use ecow::eco_format;
|
||||
use serde::{Serialize, Serializer};
|
||||
use siphasher::sip128::{Hasher128, SipHasher13};
|
||||
|
||||
use super::{
|
||||
@ -250,6 +251,29 @@ impl Hash for Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Value {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::None => serializer.serialize_none(),
|
||||
Self::Bool(v) => serializer.serialize_bool(*v),
|
||||
Self::Int(v) => serializer.serialize_i64(*v),
|
||||
Self::Float(v) => serializer.serialize_f64(*v),
|
||||
Self::Str(v) => v.serialize(serializer),
|
||||
Self::Bytes(v) => v.serialize(serializer),
|
||||
Self::Symbol(v) => v.serialize(serializer),
|
||||
Self::Content(v) => v.serialize(serializer),
|
||||
Self::Array(v) => v.serialize(serializer),
|
||||
Self::Dict(v) => v.serialize(serializer),
|
||||
|
||||
// Fall back to repr() for other things.
|
||||
other => serializer.serialize_str(&other.repr()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic value.
|
||||
#[derive(Clone, Hash)]
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
|
@ -1,10 +1,11 @@
|
||||
use std::any::TypeId;
|
||||
use std::fmt::{self, Debug, Formatter, Write};
|
||||
use std::iter::Sum;
|
||||
use std::iter::{self, Sum};
|
||||
use std::ops::{Add, AddAssign};
|
||||
|
||||
use comemo::Prehashed;
|
||||
use ecow::{eco_format, EcoString, EcoVec};
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use super::{
|
||||
element, Behave, Behaviour, ElemFunc, Element, Guard, Label, Locatable, Location,
|
||||
@ -516,6 +517,18 @@ impl Sum for Content {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Content {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_map(
|
||||
iter::once((&"func".into(), self.func().name().into_value()))
|
||||
.chain(self.fields()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Attr {
|
||||
fn child(&self) -> Option<&Content> {
|
||||
match self {
|
||||
|
Loading…
x
Reference in New Issue
Block a user