diff options
| author | Beiri22 <beier1@hs-mittweida.de> | 2023-08-06 23:49:04 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-06 23:49:04 +0200 |
| commit | 357bce56f5ac701780e4fb967acce3f30ffcadf6 (patch) | |
| tree | f582efe3197de94e67f5b0b356a9963a6a04fd27 /crates | |
| parent | 823fc5e5c4cb5c8555d0bb64e6f4ab4f61bce0e2 (diff) | |
Query-System for metadata (#1812)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/typst-cli/Cargo.toml | 3 | ||||
| -rw-r--r-- | crates/typst-cli/src/args.rs | 89 | ||||
| -rw-r--r-- | crates/typst-cli/src/compile.rs | 19 | ||||
| -rw-r--r-- | crates/typst-cli/src/main.rs | 2 | ||||
| -rw-r--r-- | crates/typst-cli/src/query.rs | 114 | ||||
| -rw-r--r-- | crates/typst-cli/src/watch.rs | 4 | ||||
| -rw-r--r-- | crates/typst-cli/src/world.rs | 4 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/metadata.rs | 87 | ||||
| -rw-r--r-- | crates/typst-library/src/meta/mod.rs | 3 | ||||
| -rw-r--r-- | crates/typst/Cargo.toml | 2 | ||||
| -rw-r--r-- | crates/typst/src/eval/array.rs | 5 | ||||
| -rw-r--r-- | crates/typst/src/eval/bytes.rs | 14 | ||||
| -rw-r--r-- | crates/typst/src/eval/dict.rs | 10 | ||||
| -rw-r--r-- | crates/typst/src/eval/str.rs | 3 | ||||
| -rw-r--r-- | crates/typst/src/eval/symbol.rs | 10 | ||||
| -rw-r--r-- | crates/typst/src/eval/value.rs | 24 | ||||
| -rw-r--r-- | crates/typst/src/model/content.rs | 15 |
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 { |
