mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Add missing keys to manifest types (#4494)
This commit is contained in:
parent
3b382cbd45
commit
3c22902d6c
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2836,6 +2836,7 @@ dependencies = [
|
|||||||
"ecow",
|
"ecow",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
|
"toml",
|
||||||
"typst-utils",
|
"typst-utils",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
"unicode-math-class",
|
"unicode-math-class",
|
||||||
|
@ -17,6 +17,7 @@ typst-utils = { workspace = true }
|
|||||||
ecow = { workspace = true }
|
ecow = { workspace = true }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
toml = { workspace = true }
|
||||||
unicode-ident = { workspace = true }
|
unicode-ident = { workspace = true }
|
||||||
unicode-math-class = { workspace = true }
|
unicode-math-class = { workspace = true }
|
||||||
unicode-script = { workspace = true }
|
unicode-script = { workspace = true }
|
||||||
|
@ -1,37 +1,101 @@
|
|||||||
//! Package manifest parsing.
|
//! Package manifest parsing.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{self, Debug, Display, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
|
use serde::de::IgnoredAny;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
|
|
||||||
use crate::is_ident;
|
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.
|
/// 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 {
|
pub struct PackageManifest {
|
||||||
/// Details about the package itself.
|
/// Details about the package itself.
|
||||||
pub package: PackageInfo,
|
pub package: PackageInfo,
|
||||||
/// Details about the template, if the package is one.
|
/// 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>,
|
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.
|
/// 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 {
|
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,
|
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,
|
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.
|
/// The `[package]` key in the manifest.
|
||||||
///
|
///
|
||||||
/// More fields are specified, but they are not relevant to the compiler.
|
/// The `unknown_fields` contains fields which were found but not expected.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct PackageInfo {
|
pub struct PackageInfo {
|
||||||
/// The name of the package within its namespace.
|
/// The name of the package within its namespace.
|
||||||
pub name: EcoString,
|
pub name: EcoString,
|
||||||
@ -39,8 +103,42 @@ pub struct PackageInfo {
|
|||||||
pub version: PackageVersion,
|
pub version: PackageVersion,
|
||||||
/// The path of the entrypoint into the package.
|
/// The path of the entrypoint into the package.
|
||||||
pub entrypoint: EcoString,
|
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.
|
/// The minimum required compiler version for the package.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub compiler: Option<VersionBound>,
|
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 {
|
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.1").unwrap()));
|
||||||
assert!(v1_1_1.matches_lt(&VersionBound::from_str("1.2").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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user