Query-System for metadata (#1812)

Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
Beiri22 2023-08-06 23:49:04 +02:00 committed by GitHub
parent 823fc5e5c4
commit 357bce56f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 423 additions and 52 deletions

67
Cargo.lock generated
View File

@ -450,7 +450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"hashbrown", "hashbrown 0.12.3",
"lock_api", "lock_api",
"once_cell", "once_cell",
"parking_lot_core", "parking_lot_core",
@ -531,6 +531,12 @@ dependencies = [
"log", "log",
] ]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.1" version = "0.3.1"
@ -738,6 +744,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
[[package]] [[package]]
name = "hayagriva" name = "hayagriva"
version = "0.3.0" version = "0.3.0"
@ -987,8 +999,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"hashbrown", "hashbrown 0.12.3",
"rayon", "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]] [[package]]
@ -1003,7 +1026,7 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
"dashmap", "dashmap",
"env_logger", "env_logger",
"indexmap", "indexmap 1.9.3",
"is-terminal", "is-terminal",
"itoa", "itoa",
"log", "log",
@ -1452,7 +1475,7 @@ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"filetime", "filetime",
"image", "image",
"indexmap", "indexmap 1.9.3",
"itertools", "itertools",
"libdeflater", "libdeflater",
"log", "log",
@ -1571,7 +1594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590"
dependencies = [ dependencies = [
"base64", "base64",
"indexmap", "indexmap 1.9.3",
"line-wrap", "line-wrap",
"quick-xml 0.28.2", "quick-xml 0.28.2",
"serde", "serde",
@ -2015,12 +2038,25 @@ version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
dependencies = [ dependencies = [
"indexmap", "indexmap 1.9.3",
"ryu", "ryu",
"serde", "serde",
"yaml-rust", "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]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.4" version = "0.1.4"
@ -2413,7 +2449,7 @@ version = "0.19.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d964908cec0d030b812013af25a0e57fddfadb1e066ecc6681d86253129d4f" checksum = "92d964908cec0d030b812013af25a0e57fddfadb1e066ecc6681d86253129d4f"
dependencies = [ dependencies = [
"indexmap", "indexmap 1.9.3",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
@ -2523,7 +2559,7 @@ dependencies = [
"fontdb", "fontdb",
"if_chain", "if_chain",
"image", "image",
"indexmap", "indexmap 1.9.3",
"log", "log",
"miniz_oxide", "miniz_oxide",
"oklab", "oklab",
@ -2573,6 +2609,9 @@ dependencies = [
"once_cell", "once_cell",
"open", "open",
"same-file", "same-file",
"serde",
"serde_json",
"serde_yaml 0.9.25",
"siphasher", "siphasher",
"tar", "tar",
"tempfile", "tempfile",
@ -2596,7 +2635,7 @@ dependencies = [
"once_cell", "once_cell",
"pulldown-cmark", "pulldown-cmark",
"serde", "serde",
"serde_yaml", "serde_yaml 0.8.26",
"syntect", "syntect",
"typed-arena", "typed-arena",
"typst", "typst",
@ -2629,7 +2668,7 @@ dependencies = [
"roxmltree", "roxmltree",
"rustybuzz", "rustybuzz",
"serde_json", "serde_json",
"serde_yaml", "serde_yaml 0.8.26",
"smallvec", "smallvec",
"syntect", "syntect",
"time", "time",
@ -2795,6 +2834,12 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "446c96c6dd42604779487f0a981060717156648c1706aa1f464677f03c6cc059" checksum = "446c96c6dd42604779487f0a981060717156648c1706aa1f464677f03c6cc059"
[[package]]
name = "unsafe-libyaml"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
[[package]] [[package]]
name = "unscanny" name = "unscanny"
version = "0.1.0" version = "0.1.0"
@ -3243,7 +3288,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a94fb32d2b438e3fddf901fbfe9eb87b34d63853ca6c6da5d2ab7e27031e0bae" checksum = "a94fb32d2b438e3fddf901fbfe9eb87b34d63853ca6c6da5d2ab7e27031e0bae"
dependencies = [ dependencies = [
"serde", "serde",
"serde_yaml", "serde_yaml 0.8.26",
] ]
[[package]] [[package]]

View File

@ -34,6 +34,9 @@ notify = "5"
once_cell = "1" once_cell = "1"
open = "4.0.2" open = "4.0.2"
same-file = "1" same-file = "1"
serde = "1"
serde_json = "1"
serde_yaml = "0.9"
siphasher = "0.3" siphasher = "0.3"
tar = "0.4" tar = "0.4"
tempfile = "3.5.0" tempfile = "3.5.0"

View File

@ -1,7 +1,7 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::path::PathBuf; use std::path::PathBuf;
use clap::{ArgAction, Parser, Subcommand, ValueEnum}; use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
/// The Typst compiler. /// The Typst compiler.
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
@ -29,6 +29,9 @@ pub enum Command {
#[command(visible_alias = "w")] #[command(visible_alias = "w")]
Watch(CompileCommand), Watch(CompileCommand),
/// Processes an input file to extract provided metadata
Query(QueryCommand),
/// Lists all discovered fonts in system and custom font paths /// Lists all discovered fonts in system and custom font paths
Fonts(FontsCommand), Fonts(FontsCommand),
} }
@ -36,12 +39,71 @@ pub enum Command {
/// Compiles the input file into a PDF file /// Compiles the input file into a PDF file
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
pub struct CompileCommand { pub struct CompileCommand {
/// Path to input Typst file /// Shared arguments.
pub input: PathBuf, #[clap(flatten)]
pub common: SharedArgs,
/// Path to output PDF file or PNG file(s) /// Path to output PDF file or PNG file(s)
pub output: Option<PathBuf>, 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 /// Configures the project root
#[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>,
@ -55,14 +117,6 @@ pub struct CompileCommand {
)] )]
pub font_paths: Vec<PathBuf>, 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 /// In which format to emit diagnostics
#[clap( #[clap(
long, long,
@ -70,19 +124,6 @@ pub struct CompileCommand {
value_parser = clap::value_parser!(DiagnosticFormat) value_parser = clap::value_parser!(DiagnosticFormat)
)] )]
pub diagnostic_format: 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 /// Lists all discovered fonts in system and custom font paths

View File

@ -21,7 +21,7 @@ type CodespanError = codespan_reporting::files::Error;
/// Execute a compilation command. /// Execute a compilation command.
pub fn compile(mut command: CompileCommand) -> StrResult<()> { 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)?; compile_once(&mut world, &mut command, false)?;
Ok(()) Ok(())
} }
@ -42,14 +42,12 @@ pub fn compile_once(
Status::Compiling.print(command).unwrap(); 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.reset();
world.source(world.main()).map_err(|err| err.to_string())?; world.source(world.main()).map_err(|err| err.to_string())?;
let mut tracer = Tracer::default(); let mut tracer = Tracer::default();
let result = typst::compile(world, &mut tracer); let result = typst::compile(world, &mut tracer);
let warnings = tracer.warnings(); let warnings = tracer.warnings();
match result { 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")?; .map_err(|_| "failed to print diagnostics")?;
if let Some(open) = command.open.take() { if let Some(open) = command.open.take() {
@ -84,8 +82,13 @@ pub fn compile_once(
Status::Error.print(command).unwrap(); Status::Error.print(command).unwrap();
} }
print_diagnostics(world, &errors, &warnings, command.diagnostic_format) print_diagnostics(
.map_err(|_| "failed to 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. /// Print diagnostic messages to the terminal.
fn print_diagnostics( pub fn print_diagnostics(
world: &SystemWorld, world: &SystemWorld,
errors: &[SourceDiagnostic], errors: &[SourceDiagnostic],
warnings: &[SourceDiagnostic], warnings: &[SourceDiagnostic],

View File

@ -2,6 +2,7 @@ mod args;
mod compile; mod compile;
mod fonts; mod fonts;
mod package; mod package;
mod query;
mod tracing; mod tracing;
mod watch; mod watch;
mod world; mod world;
@ -36,6 +37,7 @@ fn main() -> ExitCode {
let res = match arguments.command { let res = match arguments.command {
Command::Compile(command) => crate::compile::compile(command), Command::Compile(command) => crate::compile::compile(command),
Command::Watch(command) => crate::watch::watch(command), Command::Watch(command) => crate::watch::watch(command),
Command::Query(command) => crate::query::query(command),
Command::Fonts(command) => crate::fonts::fonts(command), Command::Fonts(command) => crate::fonts::fonts(command),
}; };

View 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}"))
}
}
}

View File

@ -17,7 +17,7 @@ use crate::world::SystemWorld;
/// Execute a watching compilation command. /// Execute a watching compilation command.
pub fn watch(mut command: CompileCommand) -> StrResult<()> { pub fn watch(mut command: CompileCommand) -> StrResult<()> {
// Create the world that serves sources, files, and fonts. // 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. // Perform initial compilation.
compile_once(&mut world, &mut command, true)?; compile_once(&mut world, &mut command, true)?;
@ -159,7 +159,7 @@ impl Status {
w.set_color(&color)?; w.set_color(&color)?;
write!(w, "watching")?; write!(w, "watching")?;
w.reset()?; w.reset()?;
writeln!(w, " {}", command.input.display())?; writeln!(w, " {}", command.common.input.display())?;
w.set_color(&color)?; w.set_color(&color)?;
write!(w, "writing to")?; write!(w, "writing to")?;

View File

@ -15,7 +15,7 @@ use typst::syntax::{FileId, Source};
use typst::util::PathExt; use typst::util::PathExt;
use typst::World; use typst::World;
use crate::args::CompileCommand; use crate::args::SharedArgs;
use crate::fonts::{FontSearcher, FontSlot}; use crate::fonts::{FontSearcher, FontSlot};
use crate::package::prepare_package; use crate::package::prepare_package;
@ -44,7 +44,7 @@ pub struct SystemWorld {
impl SystemWorld { impl SystemWorld {
/// Create a new system world. /// Create a new system world.
pub fn new(command: &CompileCommand) -> StrResult<Self> { pub fn new(command: &SharedArgs) -> StrResult<Self> {
let mut searcher = FontSearcher::new(); let mut searcher = FontSearcher::new();
searcher.search(&command.font_paths); searcher.search(&command.font_paths);

View 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
}
}

View File

@ -8,6 +8,7 @@ mod figure;
mod footnote; mod footnote;
mod heading; mod heading;
mod link; mod link;
mod metadata;
mod numbering; mod numbering;
mod outline; mod outline;
mod query; mod query;
@ -22,6 +23,7 @@ pub use self::figure::*;
pub use self::footnote::*; pub use self::footnote::*;
pub use self::heading::*; pub use self::heading::*;
pub use self::link::*; pub use self::link::*;
pub use self::metadata::*;
pub use self::numbering::*; pub use self::numbering::*;
pub use self::outline::*; pub use self::outline::*;
pub use self::query::*; pub use self::query::*;
@ -50,6 +52,7 @@ pub(super) fn define(global: &mut Scope) {
global.define("state", state_func()); global.define("state", state_func());
global.define("query", query_func()); global.define("query", query_func());
global.define("selector", selector_func()); global.define("selector", selector_func());
global.define("metadata", MetadataElem::func());
} }
/// The named with which an element is referenced. /// The named with which an element is referenced.

View File

@ -26,7 +26,7 @@ flate2 = "1"
fontdb = "0.13" fontdb = "0.13"
if_chain = "1" if_chain = "1"
image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] } 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" log = "0.4"
miniz_oxide = "0.7" miniz_oxide = "0.7"
oklab = "1" oklab = "1"

View File

@ -3,9 +3,11 @@ use std::fmt::{self, Debug, Formatter};
use std::ops::{Add, AddAssign}; use std::ops::{Add, AddAssign};
use ecow::{eco_format, EcoString, EcoVec}; use ecow::{eco_format, EcoString, EcoVec};
use serde::Serialize;
use super::{ops, Args, CastInfo, FromValue, Func, IntoValue, Reflect, Value, Vm}; use super::{ops, Args, CastInfo, FromValue, Func, IntoValue, Reflect, Value, Vm};
use crate::diag::{At, SourceResult, StrResult}; use crate::diag::{At, SourceResult, StrResult};
use crate::eval::ops::{add, mul};
use crate::syntax::Span; use crate::syntax::Span;
use crate::util::pretty_array_like; use crate::util::pretty_array_like;
@ -29,12 +31,11 @@ macro_rules! __array {
#[doc(inline)] #[doc(inline)]
pub use crate::__array as array; pub use crate::__array as array;
use crate::eval::ops::{add, mul};
#[doc(hidden)] #[doc(hidden)]
pub use ecow::eco_vec; pub use ecow::eco_vec;
/// A reference counted array with value semantics. /// A reference counted array with value semantics.
#[derive(Default, Clone, PartialEq, Hash)] #[derive(Default, Clone, PartialEq, Hash, Serialize)]
pub struct Array(EcoVec<Value>); pub struct Array(EcoVec<Value>);
impl Array { impl Array {

View File

@ -5,6 +5,7 @@ use std::sync::Arc;
use comemo::Prehashed; use comemo::Prehashed;
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use serde::{Serialize, Serializer};
use crate::diag::StrResult; 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. /// The out of bounds access error message.
#[cold] #[cold]
fn out_of_bounds(index: i64, len: usize) -> EcoString { fn out_of_bounds(index: i64, len: usize) -> EcoString {

View File

@ -4,6 +4,7 @@ use std::ops::{Add, AddAssign};
use std::sync::Arc; use std::sync::Arc;
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use serde::{Serialize, Serializer};
use super::{array, Array, Str, Value}; use super::{array, Array, Str, Value};
use crate::diag::StrResult; 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 { impl Extend<(Str, Value)> for Dict {
fn extend<T: IntoIterator<Item = (Str, Value)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (Str, Value)>>(&mut self, iter: T) {
Arc::make_mut(&mut self.0).extend(iter); Arc::make_mut(&mut self.0).extend(iter);

View File

@ -4,6 +4,7 @@ use std::hash::{Hash, Hasher};
use std::ops::{Add, AddAssign, Deref, Range}; use std::ops::{Add, AddAssign, Deref, Range};
use ecow::EcoString; use ecow::EcoString;
use serde::Serialize;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use super::{cast, dict, Args, Array, Dict, Func, IntoValue, Value, Vm}; 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; pub use ecow::eco_format;
/// An immutable reference counted string. /// 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); pub struct Str(EcoString);
impl Str { impl Str {

View File

@ -4,6 +4,7 @@ use std::fmt::{self, Debug, Display, Formatter, Write};
use std::sync::Arc; use std::sync::Arc;
use ecow::EcoString; use ecow::EcoString;
use serde::{Serialize, Serializer};
use crate::diag::{bail, StrResult}; 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 { impl List {
/// The characters that are covered by this list. /// The characters that are covered by this list.
fn variants(&self) -> Variants<'_> { fn variants(&self) -> Variants<'_> {

View File

@ -5,6 +5,7 @@ use std::hash::{Hash, Hasher};
use std::sync::Arc; use std::sync::Arc;
use ecow::eco_format; use ecow::eco_format;
use serde::{Serialize, Serializer};
use siphasher::sip128::{Hasher128, SipHasher13}; use siphasher::sip128::{Hasher128, SipHasher13};
use super::{ 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. /// A dynamic value.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
#[allow(clippy::derived_hash_with_manual_eq)] #[allow(clippy::derived_hash_with_manual_eq)]

View File

@ -1,10 +1,11 @@
use std::any::TypeId; use std::any::TypeId;
use std::fmt::{self, Debug, Formatter, Write}; use std::fmt::{self, Debug, Formatter, Write};
use std::iter::Sum; use std::iter::{self, Sum};
use std::ops::{Add, AddAssign}; use std::ops::{Add, AddAssign};
use comemo::Prehashed; use comemo::Prehashed;
use ecow::{eco_format, EcoString, EcoVec}; use ecow::{eco_format, EcoString, EcoVec};
use serde::{Serialize, Serializer};
use super::{ use super::{
element, Behave, Behaviour, ElemFunc, Element, Guard, Label, Locatable, Location, 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 { impl Attr {
fn child(&self) -> Option<&Content> { fn child(&self) -> Option<&Content> {
match self { match self {