syncable_cli/analyzer/dclint/parser/
mod.rs1pub mod compose;
7
8pub use compose::{
9 ComposeFile, ParseError, Position, Service, ServiceBuild, ServicePort, ServiceVolume,
10 parse_compose, parse_compose_with_positions,
11};
12
13use yaml_rust2::{Yaml, YamlLoader};
14
15pub fn parse_yaml(content: &str) -> Result<Yaml, ParseError> {
17 let docs =
18 YamlLoader::load_from_str(content).map_err(|e| ParseError::YamlError(e.to_string()))?;
19
20 docs.into_iter().next().ok_or(ParseError::EmptyDocument)
21}
22
23pub fn find_line_for_key(source: &str, path: &[&str]) -> Option<u32> {
27 if path.is_empty() {
28 return Some(1);
29 }
30
31 let lines: Vec<&str> = source.lines().collect();
32 let mut current_indent = 0;
33 let mut path_idx = 0;
34
35 for (line_num, line) in lines.iter().enumerate() {
36 if line.trim().is_empty() || line.trim().starts_with('#') {
37 continue;
38 }
39
40 let indent = line.len() - line.trim_start().len();
41 let trimmed = line.trim();
42
43 let target_key = path[path_idx];
45 let key_pattern = format!("{}:", target_key);
46
47 if (trimmed.starts_with(&key_pattern) || trimmed == target_key)
48 && (path_idx == 0 || indent > current_indent)
49 {
50 path_idx += 1;
51 current_indent = indent;
52
53 if path_idx == path.len() {
54 return Some((line_num + 1) as u32); }
56 }
57 }
58
59 None
60}
61
62pub fn find_line_for_service(source: &str, service_name: &str) -> Option<u32> {
64 find_line_for_key(source, &["services", service_name])
65}
66
67pub fn find_line_for_service_key(source: &str, service_name: &str, key: &str) -> Option<u32> {
69 find_line_for_key(source, &["services", service_name, key])
70}
71
72pub fn find_column_for_value(source: &str, line: u32, key: &str) -> u32 {
74 let lines: Vec<&str> = source.lines().collect();
75 if let Some(line_content) = lines.get((line - 1) as usize) {
76 if let Some(pos) = line_content.find(':') {
77 let after_colon = &line_content[pos + 1..];
79 let leading_ws = after_colon.len() - after_colon.trim_start().len();
80 return (pos + 2 + leading_ws) as u32;
81 }
82 if let Some(pos) = line_content.find(key) {
84 return (pos + 1) as u32;
85 }
86 }
87 1
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_find_line_for_key() {
96 let yaml = r#"
97services:
98 web:
99 image: nginx
100 ports:
101 - "80:80"
102 db:
103 image: postgres
104"#;
105 assert_eq!(find_line_for_key(yaml, &["services"]), Some(2));
106 assert_eq!(find_line_for_key(yaml, &["services", "web"]), Some(3));
107 assert_eq!(
108 find_line_for_key(yaml, &["services", "web", "image"]),
109 Some(4)
110 );
111 assert_eq!(find_line_for_key(yaml, &["services", "db"]), Some(7));
112 }
113
114 #[test]
115 fn test_find_line_for_service() {
116 let yaml = r#"
117services:
118 web:
119 image: nginx
120 db:
121 image: postgres
122"#;
123 assert_eq!(find_line_for_service(yaml, "web"), Some(3));
124 assert_eq!(find_line_for_service(yaml, "db"), Some(5));
125 assert_eq!(find_line_for_service(yaml, "nonexistent"), None);
126 }
127}