Add missing keys to manifest types (#4494)

This commit is contained in:
tingerrr 2024-07-10 11:44:13 +02:00 committed by GitHub
parent 3b382cbd45
commit 3c22902d6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 200 additions and 7 deletions

1
Cargo.lock generated
View File

@ -2836,6 +2836,7 @@ dependencies = [
"ecow",
"once_cell",
"serde",
"toml",
"typst-utils",
"unicode-ident",
"unicode-math-class",

View File

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

View File

@ -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"));
}
}