1use std::{collections::HashMap, fs, path::PathBuf};
2
3use kdl::{KdlDocument, KdlError, KdlNode};
4use miette::{Diagnostic, IntoDiagnostic, Report, Result};
5use thiserror::Error;
6
7#[derive(Debug)]
8pub enum PkgType {
9 SingleExecutable, Folder(PathBuf), }
12
13#[derive(Debug, Clone, PartialEq)]
14pub enum AttributeValue {
15 String(String),
16 Integer(i64),
17 Float(f64),
18 Boolean(bool),
19}
20
21#[derive(Debug, Clone, PartialEq)]
22pub struct PkgDeclaration {
23 pub name: String,
24 pub input: String,
25 pub attributes: HashMap<String, AttributeValue>,
26}
27
28#[derive(Debug)]
29pub struct Bridge {
30 pub name: String,
31 pub pkgs: Vec<PkgDeclaration>,
32}
33
34#[derive(Debug)]
35pub struct Input {
36 pub path: PathBuf,
37 pub bridges: Vec<Bridge>,
38}
39
40#[derive(Error, Debug, Diagnostic)]
41pub enum InputError {
42 #[error(transparent)]
43 #[diagnostic(code(input::io_error))]
44 IoError(#[from] std::io::Error),
45
46 #[error(transparent)]
47 #[diagnostic(code(input::parse_error))]
48 KdlError(#[from] KdlError),
49
50 #[error("Unsupported attribute type {0}")]
51 #[diagnostic(code(input::wrong_value))]
52 UnSupportedAttributeType(String),
53
54 #[error("Missing required field")]
55 #[diagnostic(code(input::missing_field))]
56 MissingField,
57
58 #[error("Invalid attribute format")]
59 #[diagnostic(code(input::invalid_attribute))]
60 InvalidAttribute,
61
62 #[error("Duplicate package declaration: {0}")]
63 #[diagnostic(code(input::duplicate_pkg))]
64 DuplicatePkgDeclaration(String),
65}
66
67fn detect_pkg_kdl_files(path: &PathBuf) -> Result<Vec<PathBuf>> {
68 let mut inputs_paths = Vec::new();
69 for entry in fs::read_dir(path).into_diagnostic()? {
70 let entry = entry.into_diagnostic()?;
71 let path = entry.path();
72
73 if path.is_file() {
74 if let Some(ext) = path.extension().and_then(|e| e.to_str())
75 && ext.eq_ignore_ascii_case("kdl")
76 && let Some(file_name) = path.file_name().and_then(|n| n.to_str())
77 && !file_name.starts_with('.')
78 {
79 inputs_paths.push(path);
80 }
81 } else if path.is_dir() {
82 inputs_paths.extend(detect_pkg_kdl_files(&path)?);
83 }
84 }
85 Ok(inputs_paths)
86}
87
88fn parse_inputs_kdl(inputs_paths: &[PathBuf]) -> Result<Vec<KdlDocument>> {
89 inputs_paths
90 .iter()
91 .map(|path| {
92 fs::read_to_string(path)
93 .into_diagnostic()?
94 .parse::<KdlDocument>()
95 .into_diagnostic()
96 })
97 .collect()
98}
99
100fn parse_attributes(node: &KdlNode) -> Result<HashMap<String, AttributeValue>, InputError> {
101 let mut attributes = HashMap::new();
102
103 for entry in node.entries().iter().skip(1) {
104 let name = entry.name().ok_or(InputError::MissingField)?;
106 let value = entry.value();
107
108 let attr_value = if value.is_string() {
109 AttributeValue::String(value.as_string().unwrap().to_string())
110 } else if value.is_integer() {
111 AttributeValue::Integer(value.as_integer().unwrap() as i64)
112 } else if value.is_bool() {
113 AttributeValue::Boolean(value.as_bool().unwrap())
114 } else if value.is_float() {
115 AttributeValue::Float(value.as_float().unwrap())
116 } else {
117 return Err(InputError::UnSupportedAttributeType(value.to_string()));
118 };
119
120 attributes.insert(name.to_string(), attr_value);
121 }
122
123 Ok(attributes)
124}
125
126fn parse_bridges(kdl_docs: &[KdlDocument]) -> Result<Vec<Bridge>> {
127 let mut bridges = Vec::<Bridge>::new();
128
129 for doc in kdl_docs {
130 for bridge_node in doc.nodes() {
131 let bridge_name = bridge_node.name().to_string();
132 let mut bridge = Bridge {
133 name: bridge_name.clone(),
134 pkgs: Vec::new(),
135 };
136
137 let children = bridge_node.children().ok_or(InputError::MissingField)?;
138
139 for pkg_decl_node in children.nodes() {
140 let input = pkg_decl_node
141 .entries()
142 .first()
143 .map(|entry| {
144 entry
145 .value()
146 .as_string()
147 .ok_or(InputError::InvalidAttribute)
148 .map(|s| s.to_string())
149 })
150 .unwrap_or_else(|| Ok(pkg_decl_node.name().to_string()))?;
151
152 let pkg_decl = PkgDeclaration {
153 name: pkg_decl_node.name().to_string(),
154 input,
155 attributes: parse_attributes(pkg_decl_node)?,
156 };
157
158 if bridges.iter().any(|b: &Bridge| {
159 b.pkgs
160 .iter()
161 .any(|p: &PkgDeclaration| p.name == pkg_decl.name)
162 }) {
163 return Err(Report::new(InputError::DuplicatePkgDeclaration(
164 pkg_decl.name.clone(),
165 )));
166 }
167
168 bridge.pkgs.push(pkg_decl);
169 }
170 if let Some(existing_bridge) = bridges.iter_mut().find(|b| b.name == bridge.name) {
171 existing_bridge.pkgs.extend(bridge.pkgs);
172 } else {
173 bridges.push(bridge);
174 }
175 }
176 }
177
178 Ok(bridges)
179}
180
181impl Input {
182 pub fn load(path: &PathBuf) -> Result<Self> {
183 let inputs_paths = detect_pkg_kdl_files(path)?;
184 let kdl_docs = parse_inputs_kdl(&inputs_paths)?;
185 let bridges = parse_bridges(&kdl_docs)?;
186
187 Ok(Self {
188 path: path.clone(),
189 bridges,
190 })
191 }
192}