pub mod compose;
pub use compose::{
ComposeFile, ParseError, Position, Service, ServiceBuild, ServicePort, ServiceVolume,
parse_compose, parse_compose_with_positions,
};
use yaml_rust2::{Yaml, YamlLoader};
pub fn parse_yaml(content: &str) -> Result<Yaml, ParseError> {
let docs =
YamlLoader::load_from_str(content).map_err(|e| ParseError::YamlError(e.to_string()))?;
docs.into_iter().next().ok_or(ParseError::EmptyDocument)
}
pub fn find_line_for_key(source: &str, path: &[&str]) -> Option<u32> {
if path.is_empty() {
return Some(1);
}
let lines: Vec<&str> = source.lines().collect();
let mut current_indent = 0;
let mut path_idx = 0;
for (line_num, line) in lines.iter().enumerate() {
if line.trim().is_empty() || line.trim().starts_with('#') {
continue;
}
let indent = line.len() - line.trim_start().len();
let trimmed = line.trim();
let target_key = path[path_idx];
let key_pattern = format!("{}:", target_key);
if (trimmed.starts_with(&key_pattern) || trimmed == target_key)
&& (path_idx == 0 || indent > current_indent)
{
path_idx += 1;
current_indent = indent;
if path_idx == path.len() {
return Some((line_num + 1) as u32); }
}
}
None
}
pub fn find_line_for_service(source: &str, service_name: &str) -> Option<u32> {
find_line_for_key(source, &["services", service_name])
}
pub fn find_line_for_service_key(source: &str, service_name: &str, key: &str) -> Option<u32> {
find_line_for_key(source, &["services", service_name, key])
}
pub fn find_column_for_value(source: &str, line: u32, key: &str) -> u32 {
let lines: Vec<&str> = source.lines().collect();
if let Some(line_content) = lines.get((line - 1) as usize) {
if let Some(pos) = line_content.find(':') {
let after_colon = &line_content[pos + 1..];
let leading_ws = after_colon.len() - after_colon.trim_start().len();
return (pos + 2 + leading_ws) as u32;
}
if let Some(pos) = line_content.find(key) {
return (pos + 1) as u32;
}
}
1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_line_for_key() {
let yaml = r#"
services:
web:
image: nginx
ports:
- "80:80"
db:
image: postgres
"#;
assert_eq!(find_line_for_key(yaml, &["services"]), Some(2));
assert_eq!(find_line_for_key(yaml, &["services", "web"]), Some(3));
assert_eq!(
find_line_for_key(yaml, &["services", "web", "image"]),
Some(4)
);
assert_eq!(find_line_for_key(yaml, &["services", "db"]), Some(7));
}
#[test]
fn test_find_line_for_service() {
let yaml = r#"
services:
web:
image: nginx
db:
image: postgres
"#;
assert_eq!(find_line_for_service(yaml, "web"), Some(3));
assert_eq!(find_line_for_service(yaml, "db"), Some(5));
assert_eq!(find_line_for_service(yaml, "nonexistent"), None);
}
}