summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorBeiri22 <beier1@hs-mittweida.de>2023-08-06 23:49:04 +0200
committerGitHub <noreply@github.com>2023-08-06 23:49:04 +0200
commit357bce56f5ac701780e4fb967acce3f30ffcadf6 (patch)
treef582efe3197de94e67f5b0b356a9963a6a04fd27 /crates
parent823fc5e5c4cb5c8555d0bb64e6f4ab4f61bce0e2 (diff)
Query-System for metadata (#1812)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
Diffstat (limited to 'crates')
-rw-r--r--crates/typst-cli/Cargo.toml3
-rw-r--r--crates/typst-cli/src/args.rs89
-rw-r--r--crates/typst-cli/src/compile.rs19
-rw-r--r--crates/typst-cli/src/main.rs2
-rw-r--r--crates/typst-cli/src/query.rs114
-rw-r--r--crates/typst-cli/src/watch.rs4
-rw-r--r--crates/typst-cli/src/world.rs4
-rw-r--r--crates/typst-library/src/meta/metadata.rs87
-rw-r--r--crates/typst-library/src/meta/mod.rs3
-rw-r--r--crates/typst/Cargo.toml2
-rw-r--r--crates/typst/src/eval/array.rs5
-rw-r--r--crates/typst/src/eval/bytes.rs14
-rw-r--r--crates/typst/src/eval/dict.rs10
-rw-r--r--crates/typst/src/eval/str.rs3
-rw-r--r--crates/typst/src/eval/symbol.rs10
-rw-r--r--crates/typst/src/eval/value.rs24
-rw-r--r--crates/typst/src/model/content.rs15
17 files changed, 367 insertions, 41 deletions
diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml
index e77cbc2a..808f07a7 100644
--- a/crates/typst-cli/Cargo.toml
+++ b/crates/typst-cli/Cargo.toml
@@ -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"
diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs
index 55cfe8ad..0d88df9b 100644
--- a/crates/typst-cli/src/args.rs
+++ b/crates/typst-cli/src/args.rs
@@ -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
diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs
index 2cce13e1..ca088a76 100644
--- a/crates/typst-cli/src/compile.rs
+++ b/crates/typst-cli/src/compile.rs
@@ -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],
diff --git a/crates/typst-cli/src/main.rs b/crates/typst-cli/src/main.rs
index 425d05fd..62f14566 100644
--- a/crates/typst-cli/src/main.rs
+++ b/crates/typst-cli/src/main.rs
@@ -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),
};
diff --git a/crates/typst-cli/src/query.rs b/crates/typst-cli/src/query.rs
new file mode 100644
index 00000000..faf4e01b
--- /dev/null
+++ b/crates/typst-cli/src/query.rs
@@ -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}"))
+ }
+ }
+}
diff --git a/crates/typst-cli/src/watch.rs b/crates/typst-cli/src/watch.rs
index 759d27ec..b320c651 100644
--- a/crates/typst-cli/src/watch.rs
+++ b/crates/typst-cli/src/watch.rs
@@ -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")?;
diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs
index 2c0ee7d0..fb8fc0c7 100644
--- a/crates/typst-cli/src/world.rs
+++ b/crates/typst-cli/src/world.rs
@@ -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);
diff --git a/crates/typst-library/src/meta/metadata.rs b/crates/typst-library/src/meta/metadata.rs
new file mode 100644
index 00000000..eed4c71f
--- /dev/null
+++ b/crates/typst-library/src/meta/metadata.rs
@@ -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
+ }
+}
diff --git a/crates/typst-library/src/meta/mod.rs b/crates/typst-library/src/meta/mod.rs
index dcac6379..bb6ac3d3 100644
--- a/crates/typst-library/src/meta/mod.rs
+++ b/crates/typst-library/src/meta/mod.rs
@@ -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.
diff --git a/crates/typst/Cargo.toml b/crates/typst/Cargo.toml
index d8f733bf..f16d3311 100644
--- a/crates/typst/Cargo.toml
+++ b/crates/typst/Cargo.toml
@@ -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"
diff --git a/crates/typst/src/eval/array.rs b/crates/typst/src/eval/array.rs
index adb3e858..d93b14e6 100644
--- a/crates/typst/src/eval/array.rs
+++ b/crates/typst/src/eval/array.rs
@@ -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 {
diff --git a/crates/typst/src/eval/bytes.rs b/crates/typst/src/eval/bytes.rs
index b24b289e..5921f485 100644
--- a/crates/typst/src/eval/bytes.rs
+++ b/crates/typst/src/eval/bytes.rs
@@ -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 {
diff --git a/crates/typst/src/eval/dict.rs b/crates/typst/src/eval/dict.rs
index 3b007c75..41d72d98 100644
--- a/crates/typst/src/eval/dict.rs
+++ b/crates/typst/src/eval/dict.rs
@@ -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);
diff --git a/crates/typst/src/eval/str.rs b/crates/typst/src/eval/str.rs
index 1d88b81b..21dc3b68 100644
--- a/crates/typst/src/eval/str.rs
+++ b/crates/typst/src/eval/str.rs
@@ -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 {
diff --git a/crates/typst/src/eval/symbol.rs b/crates/typst/src/eval/symbol.rs
index 0925202e..58cfd534 100644
--- a/crates/typst/src/eval/symbol.rs
+++ b/crates/typst/src/eval/symbol.rs
@@ -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<'_> {
diff --git a/crates/typst/src/eval/value.rs b/crates/typst/src/eval/value.rs
index 1894bac0..6a3fcd3c 100644
--- a/crates/typst/src/eval/value.rs
+++ b/crates/typst/src/eval/value.rs
@@ -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)]
diff --git a/crates/typst/src/model/content.rs b/crates/typst/src/model/content.rs
index 31106ea5..0c79d02c 100644
--- a/crates/typst/src/model/content.rs
+++ b/crates/typst/src/model/content.rs
@@ -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 {