use std::collections::BTreeMap;
use serde::Deserialize;
use super::MANIFEST_FILE;
use super::files::PackError;
use super::pack::Pack;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum PackSide {
#[default]
Both,
Server,
Client,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct PackManifest {
pub id: String,
pub version: String,
#[serde(default)]
pub name: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub authors: Vec<String>,
#[serde(default)]
pub license: String,
#[serde(default)]
pub homepage: String,
#[serde(default)]
pub side: PackSide,
#[serde(default = "yes")]
pub join: bool,
#[serde(default)]
pub requires: BTreeMap<String, String>,
}
pub(super) const fn yes() -> bool {
true
}
impl PackManifest {
pub fn parse(text: &str) -> Result<Self, PackError> {
let manifest: Self =
toml::from_str(text).map_err(|e| PackError::of(MANIFEST_FILE, text, &e))?;
if !crate::abi::is_valid_id(&manifest.id) {
return Err(PackError::at(
MANIFEST_FILE,
text,
find(text, "id"),
"id is <author>.<name>, each of [a-z0-9-]",
));
}
Ok(manifest)
}
}
impl PackManifest {
pub fn check(&self, text: &str, pack: &Pack) -> Result<(), PackError> {
if self.join || !pack.reaches_players() {
return Ok(());
}
Err(PackError::at(
MANIFEST_FILE,
text,
find(text, "join"),
"join = false, but the pack adds or changes what players' games read, so server and players must run it alike; remove the line",
))
}
}
pub(super) fn find(text: &str, key: &str) -> usize {
let mut at = 0;
for line in text.split_inclusive('\n') {
if line.trim_start().starts_with(key) {
return at + line.len() - line.trim_start().len();
}
at += line.len();
}
0
}