summaryrefslogtreecommitdiff
path: root/crates/typst-syntax
diff options
context:
space:
mode:
authortingerrr <me@tinger.dev>2024-07-10 11:44:13 +0200
committerGitHub <noreply@github.com>2024-07-10 09:44:13 +0000
commit3c22902d6cd99de6717fd2dad0a1fcca48039225 (patch)
tree02e0965000ef6f25b01a88408ddeb5472acbcd55 /crates/typst-syntax
parent3b382cbd4524484671018f1a66c085ede9f7dd19 (diff)
Add missing keys to manifest types (#4494)
Diffstat (limited to 'crates/typst-syntax')
-rw-r--r--crates/typst-syntax/Cargo.toml1
-rw-r--r--crates/typst-syntax/src/package.rs205
2 files changed, 199 insertions, 7 deletions
diff --git a/crates/typst-syntax/Cargo.toml b/crates/typst-syntax/Cargo.toml
index 816f0d34..e9c39992 100644
--- a/crates/typst-syntax/Cargo.toml
+++ b/crates/typst-syntax/Cargo.toml
@@ -17,6 +17,7 @@ typst-utils = { workspace = true }
ecow = { workspace = true }
once_cell = { workspace = true }
serde = { workspace = true }
+toml = { workspace = true }
unicode-ident = { workspace = true }
unicode-math-class = { workspace = true }
unicode-script = { workspace = true }
diff --git a/crates/typst-syntax/src/package.rs b/crates/typst-syntax/src/package.rs
index fb0031c8..c96aebe0 100644
--- a/crates/typst-syntax/src/package.rs
+++ b/crates/typst-syntax/src/package.rs
@@ -1,37 +1,101 @@
//! Package manifest parsing.
+use std::collections::BTreeMap;
use std::fmt::{self, Debug, Display, Formatter};
use std::str::FromStr;
use ecow::{eco_format, EcoString};
+use serde::de::IgnoredAny;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use unscanny::Scanner;
use crate::is_ident;
+/// A type alias for a map of key-value pairs used to collect unknown fields
+/// where values are completely discarded.
+pub type UnknownFields = BTreeMap<EcoString, IgnoredAny>;
+
/// A parsed package manifest.
-#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
+///
+/// The `unknown_fields` contains fields which were found but not expected.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PackageManifest {
/// Details about the package itself.
pub package: PackageInfo,
/// Details about the template, if the package is one.
- #[serde(skip_serializing_if = "Option::is_none")]
+ #[serde(default, skip_serializing_if = "Option::is_none")]
pub template: Option<TemplateInfo>,
+ /// The tools section for third-party configuration.
+ #[serde(default)]
+ pub tool: ToolInfo,
+ /// All parsed but unknown fields, this can be used for validation.
+ #[serde(flatten, skip_serializing)]
+ pub unknown_fields: UnknownFields,
+}
+
+/// The `[tool]` key in the manifest. This field can be used to retrieve
+/// 3rd-party tool configuration.
+///
+// # Examples
+/// ```
+/// # use serde::{Deserialize, Serialize};
+/// # use ecow::EcoString;
+/// # use typst_syntax::package::PackageManifest;
+/// #[derive(Debug, PartialEq, Serialize, Deserialize)]
+/// struct MyTool {
+/// key: EcoString,
+/// }
+///
+/// let mut manifest: PackageManifest = toml::from_str(r#"
+/// [package]
+/// name = "package"
+/// version = "0.1.0"
+/// entrypoint = "src/lib.typ"
+///
+/// [tool.my-tool]
+/// key = "value"
+/// "#)?;
+///
+/// let my_tool = manifest
+/// .tool
+/// .sections
+/// .remove("my-tool")
+/// .ok_or("tool.my-tool section missing")?;
+/// let my_tool = MyTool::deserialize(my_tool)?;
+///
+/// assert_eq!(my_tool, MyTool { key: "value".into() });
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
+pub struct ToolInfo {
+ /// Any fields parsed in the tool section.
+ #[serde(flatten)]
+ pub sections: BTreeMap<EcoString, toml::Table>,
}
/// The `[template]` key in the manifest.
-#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
+///
+/// The `unknown_fields` contains fields which were found but not expected.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TemplateInfo {
- /// The path of the starting point within the package.
+ /// The directory within the package that contains the files that should be
+ /// copied into the user's new project directory.
pub path: EcoString,
- /// The path of the entrypoint relative to the starting point's `path`.
+ /// A path relative to the template's path that points to the file serving
+ /// as the compilation target.
pub entrypoint: EcoString,
+ /// A path relative to the package's root that points to a PNG or lossless
+ /// WebP thumbnail for the template.
+ pub thumbnail: EcoString,
+ /// All parsed but unknown fields, this can be used for validation.
+ #[serde(flatten, skip_serializing)]
+ pub unknown_fields: UnknownFields,
}
/// The `[package]` key in the manifest.
///
-/// More fields are specified, but they are not relevant to the compiler.
-#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
+/// The `unknown_fields` contains fields which were found but not expected.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PackageInfo {
/// The name of the package within its namespace.
pub name: EcoString,
@@ -39,8 +103,42 @@ pub struct PackageInfo {
pub version: PackageVersion,
/// The path of the entrypoint into the package.
pub entrypoint: EcoString,
+ /// A list of the package's authors.
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub authors: Vec<EcoString>,
+ /// The package's license.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub license: Option<EcoString>,
+ /// A short description of the package.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub description: Option<EcoString>,
+ /// A link to the package's web presence.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub homepage: Option<EcoString>,
+ /// A link to the repository where this package is developed.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub repository: Option<EcoString>,
+ /// An array of search keywords for the package.
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub keywords: Vec<EcoString>,
+ /// An array with up to three of the predefined categories to help users
+ /// discover the package.
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub categories: Vec<EcoString>,
+ /// An array of disciplines defining the target audience for which the
+ /// package is useful.
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub disciplines: Vec<EcoString>,
/// The minimum required compiler version for the package.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
pub compiler: Option<VersionBound>,
+ /// An array of globs specifying files that should not be part of the
+ /// published bundle.
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub exclude: Vec<EcoString>,
+ /// All parsed but unknown fields, this can be used for validation.
+ #[serde(flatten, skip_serializing)]
+ pub unknown_fields: UnknownFields,
}
impl PackageManifest {
@@ -423,4 +521,97 @@ mod tests {
assert!(!v1_1_1.matches_lt(&VersionBound::from_str("1.1").unwrap()));
assert!(v1_1_1.matches_lt(&VersionBound::from_str("1.2").unwrap()));
}
+
+ #[test]
+ fn minimal_manifest() {
+ assert_eq!(
+ toml::from_str::<PackageManifest>(
+ r#"
+ [package]
+ name = "package"
+ version = "0.1.0"
+ entrypoint = "src/lib.typ"
+ "#
+ ),
+ Ok(PackageManifest {
+ package: PackageInfo {
+ name: "package".into(),
+ version: PackageVersion { major: 0, minor: 1, patch: 0 },
+ entrypoint: "src/lib.typ".into(),
+ authors: vec![],
+ license: None,
+ description: None,
+ homepage: None,
+ repository: None,
+ keywords: vec![],
+ categories: vec![],
+ disciplines: vec![],
+ compiler: None,
+ exclude: vec![],
+ unknown_fields: BTreeMap::new(),
+ },
+ template: None,
+ tool: ToolInfo { sections: BTreeMap::new() },
+ unknown_fields: BTreeMap::new(),
+ })
+ );
+ }
+
+ #[test]
+ fn tool_section() {
+ // NOTE: tool section must be table of tables, but we can't easily
+ // compare the error structurally
+ assert!(toml::from_str::<PackageManifest>(
+ r#"
+ [package]
+ name = "package"
+ version = "0.1.0"
+ entrypoint = "src/lib.typ"
+
+ [tool]
+ not-table = "str"
+ "#
+ )
+ .is_err());
+
+ #[derive(Debug, PartialEq, Serialize, Deserialize)]
+ struct MyTool {
+ key: EcoString,
+ }
+
+ let mut manifest: PackageManifest = toml::from_str(
+ r#"
+ [package]
+ name = "package"
+ version = "0.1.0"
+ entrypoint = "src/lib.typ"
+
+ [tool.my-tool]
+ key = "value"
+ "#,
+ )
+ .unwrap();
+
+ let my_tool = manifest.tool.sections.remove("my-tool").unwrap();
+ let my_tool = MyTool::deserialize(my_tool).unwrap();
+
+ assert_eq!(my_tool, MyTool { key: "value".into() });
+ }
+
+ #[test]
+ fn unknown_keys() {
+ let manifest: PackageManifest = toml::from_str(
+ r#"
+ [package]
+ name = "package"
+ version = "0.1.0"
+ entrypoint = "src/lib.typ"
+
+ [unknown]
+ "#,
+ )
+ .unwrap();
+
+ assert!(manifest.unknown_fields.contains_key("unknown"));
+ }
}