use crate::error::YamlError;
use crate::parser::errors::directive_errors::DirectiveErrors;
use crate::utils::is_line_terminator;
pub fn parse_directives(
source: &mut dyn crate::io::traits::ISource,
) -> Result<DirectiveContext, YamlError> {
fn parse_line(source: &mut dyn crate::io::traits::ISource) -> String {
let mut line = String::new();
while let Some(c) = source.current() {
if is_line_terminator(c) {
break;
}
line.push(c);
source.next();
}
if let Some(c) = source.current() {
if is_line_terminator(c) {
source.next();
}
}
line
}
let mut directives = DirectiveContext::new();
crate::utils::skip_whitespace_and_comments(source);
while let Some('%') = source.current() {
let line = parse_line(source);
let parts: Vec<_> = line.trim().split_whitespace().collect();
if parts.is_empty() {
continue;
}
match parts[0] {
"%YAML" => {
if parts.len() < 2 {
return Err(DirectiveErrors::missing_yaml_version());
}
if parts.len() > 2 {
let third = parts[2];
if !third.starts_with('#') {
return Err(YamlError::new(
crate::error::ErrorKind::ParseError,
"Invalid %YAML directive: extra content after version is not allowed",
));
}
}
let version = parts[1];
let mut split = version.split('.');
let major = split
.next()
.and_then(|s| s.parse::<u8>().ok())
.ok_or_else(|| DirectiveErrors::invalid_yaml_major_version_generic())?;
let minor = split
.next()
.and_then(|s| s.parse::<u8>().ok())
.ok_or_else(|| DirectiveErrors::invalid_yaml_minor_version_generic())?;
directives.set_version(major, minor)?;
}
"%TAG" => {
if parts.len() < 3 {
#[cfg(debug_assertions)]
eprintln!("DEBUG: Malformed %TAG directive: parts = {:?}", parts);
return Err(DirectiveErrors::malformed_tag_directive());
}
let handle = parts[1].to_string();
let prefix = parts[2].to_string();
directives.add_tag_prefix(handle, prefix);
}
_ => {
}
}
crate::utils::skip_whitespace_and_comments(source);
}
Ok(directives)
}
#[cfg(feature = "std")]
use std::collections::HashMap;
#[cfg(feature = "std")]
use std::string::String;
#[cfg(not(feature = "std"))]
use alloc::collections::BTreeMap as HashMap;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[derive(Clone, Debug, Default)]
pub struct DirectiveContext {
pub yaml_version: Option<(u8, u8)>,
pub tag_prefixes: HashMap<String, String>,
}
impl DirectiveContext {
pub fn new() -> Self {
let mut tag_prefixes = HashMap::new();
tag_prefixes.insert("!!".to_string(), "tag:yaml.org,2002:".to_string());
tag_prefixes.insert("!".to_string(), "!".to_string());
Self {
yaml_version: None,
tag_prefixes,
}
}
pub fn set_version(&mut self, major: u8, minor: u8) -> Result<(), YamlError> {
if self.yaml_version.is_some() {
return Err(DirectiveErrors::duplicate_yaml_directive());
}
if major != 1 {
return Err(DirectiveErrors::invalid_yaml_major_version_num(major));
}
if minor > 2 {
#[cfg(feature = "std")]
eprintln!(
"Warning: Unrecognized YAML minor version {}. Proceeding as YAML 1.2.",
minor
);
}
self.yaml_version = Some((major, minor));
Ok(())
}
pub fn add_tag_prefix(&mut self, handle: String, prefix: String) {
self.tag_prefixes.insert(handle, prefix);
}
pub fn resolve_tag(&self, tag: &str) -> String {
if tag.starts_with("!!") {
let suffix = &tag[2..];
let default_prefix = self
.tag_prefixes
.get("!!")
.cloned()
.unwrap_or_else(|| "tag:yaml.org,2002:".to_string());
return alloc::format!("{}{}", default_prefix, suffix);
}
let mut best_match: Option<(&str, &str)> = None;
for (handle, prefix) in &self.tag_prefixes {
if tag.starts_with(handle.as_str()) {
if let Some((existing_handle, _)) = best_match {
if handle.len() > existing_handle.len() {
best_match = Some((handle, prefix));
}
} else {
best_match = Some((handle, prefix));
}
}
}
if let Some((handle, prefix)) = best_match {
let suffix = &tag[handle.len()..];
if prefix.starts_with("tag:") {
return alloc::format!("{}{}", prefix, suffix);
} else {
return alloc::format!("{}{}", prefix, suffix);
}
}
tag.to_string()
}
pub fn validate_tag_handle_usage(&self, tag: &str) -> Result<(), YamlError> {
if tag.starts_with("!<") {
return Ok(());
}
if tag.starts_with("!!") {
return Ok(());
}
if tag.starts_with('!') {
if let Some(idx) = tag[1..].find('!') {
let handle_end = 1 + idx; let handle = &tag[..=handle_end]; if !self.tag_prefixes.contains_key(handle) {
return Err(DirectiveErrors::undefined_tag_handle(handle));
}
}
}
Ok(())
}
pub fn is_yaml_11(&self) -> bool {
matches!(self.yaml_version, Some((1, 1)))
}
}