use crate::error::ExtensionError;
use crate::validate::{
validate_excluded_platforms_str, validate_extension_name, validate_extension_version,
validate_spdx_license,
};
use super::model::DescriptionYml;
#[allow(clippy::too_many_lines)]
pub fn parse_description_yml(content: &str) -> Result<DescriptionYml, ExtensionError> {
let mut name = String::new();
let mut description = String::new();
let mut version = String::new();
let mut language = String::new();
let mut build = String::new();
let mut license = String::new();
let mut requires_toolchains = String::new();
let mut excluded_platforms = String::new();
let mut maintainers: Vec<String> = Vec::new();
let mut github = String::new();
let mut git_ref = String::new();
let mut in_maintainers = false;
for line in content.lines() {
if line.starts_with("extension:") {
in_maintainers = false;
continue;
}
if line.starts_with("repo:") {
in_maintainers = false;
continue;
}
if in_maintainers {
let trimmed = line.trim();
if let Some(name_val) = trimmed.strip_prefix("- ") {
let m = strip_inline_comment(name_val.trim()).to_string();
if !m.is_empty() {
maintainers.push(m);
}
} else if trimmed.starts_with('-') {
let m = strip_inline_comment(trimmed.trim_start_matches('-').trim()).to_string();
if !m.is_empty() {
maintainers.push(m);
}
} else if !trimmed.is_empty() && !trimmed.starts_with('#') {
in_maintainers = false;
}
if in_maintainers {
continue;
}
}
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
if let Some(val) = parse_kv(trimmed, "name:") {
name = val.to_string();
} else if let Some(val) = parse_kv(trimmed, "description:") {
description = val.to_string();
} else if let Some(val) = parse_kv(trimmed, "version:") {
version = val.to_string();
} else if let Some(val) = parse_kv(trimmed, "language:") {
language = val.to_string();
} else if let Some(val) = parse_kv(trimmed, "build:") {
build = val.to_string();
} else if let Some(val) = parse_kv(trimmed, "license:") {
license = val.to_string();
} else if let Some(val) = parse_kv(trimmed, "requires_toolchains:") {
requires_toolchains = val.to_string();
} else if let Some(val) = parse_kv(trimmed, "excluded_platforms:") {
excluded_platforms = val.trim_matches('"').to_string();
} else if let Some(val) = parse_kv(trimmed, "github:") {
github = val.to_string();
} else if let Some(val) = parse_kv(trimmed, "ref:") {
git_ref = val.to_string();
} else if trimmed == "maintainers:" {
in_maintainers = true;
}
}
if name.is_empty() {
return Err(ExtensionError::new(
"description.yml: missing required field 'extension.name'",
));
}
validate_extension_name(&name)
.map_err(|e| ExtensionError::new(format!("description.yml: extension.name: {e}")))?;
if description.is_empty() {
return Err(ExtensionError::new(
"description.yml: missing required field 'extension.description'",
));
}
if version.is_empty() {
return Err(ExtensionError::new(
"description.yml: missing required field 'extension.version'",
));
}
validate_extension_version(&version)
.map_err(|e| ExtensionError::new(format!("description.yml: extension.version: {e}")))?;
if language.is_empty() {
return Err(ExtensionError::new(
"description.yml: missing required field 'extension.language'",
));
}
if build.is_empty() {
return Err(ExtensionError::new(
"description.yml: missing required field 'extension.build'",
));
}
if license.is_empty() {
return Err(ExtensionError::new(
"description.yml: missing required field 'extension.license'",
));
}
validate_spdx_license(&license)
.map_err(|e| ExtensionError::new(format!("description.yml: extension.license: {e}")))?;
if requires_toolchains.is_empty() {
return Err(ExtensionError::new(
"description.yml: missing required field 'extension.requires_toolchains'",
));
}
if !excluded_platforms.is_empty() {
validate_excluded_platforms_str(&excluded_platforms).map_err(|e| {
ExtensionError::new(format!(
"description.yml: extension.excluded_platforms: {e}"
))
})?;
}
if maintainers.is_empty() {
return Err(ExtensionError::new(
"description.yml: 'extension.maintainers' must list at least one maintainer",
));
}
if github.is_empty() {
return Err(ExtensionError::new(
"description.yml: missing required field 'repo.github'",
));
}
if !github.contains('/') {
return Err(ExtensionError::new(format!(
"description.yml: 'repo.github' must be in 'owner/repo' format, got '{github}'"
)));
}
if git_ref.is_empty() {
return Err(ExtensionError::new(
"description.yml: missing required field 'repo.ref'",
));
}
Ok(DescriptionYml {
name,
description,
version,
language,
build,
license,
requires_toolchains,
excluded_platforms,
maintainers,
github,
git_ref,
})
}
pub(super) fn parse_kv<'a>(line: &'a str, key: &str) -> Option<&'a str> {
line.strip_prefix(key).map(|v| {
let v = v.trim();
if (v.starts_with('"') && v.ends_with('"')) || (v.starts_with('\'') && v.ends_with('\'')) {
return v;
}
v.find(" #").map_or(v, |pos| v[..pos].trim_end())
})
}
pub(super) fn strip_inline_comment(value: &str) -> &str {
value
.find(" #")
.map_or(value, |pos| value[..pos].trim_end())
}