1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::path::PathBuf;
4
5pub mod dto;
6pub mod extras;
7pub mod languages;
8pub mod output;
9
10pub use dto::{
12 CsharpDtoStyle, DtoConfig, ElixirDtoStyle, GoDtoStyle, JavaDtoStyle, NodeDtoStyle, PhpDtoStyle, PythonDtoStyle,
13 RDtoStyle, RubyDtoStyle,
14};
15pub use extras::{AdapterConfig, AdapterParam, AdapterPattern, Language};
16pub use languages::{
17 CSharpConfig, CustomModulesConfig, CustomRegistration, CustomRegistrationsConfig, ElixirConfig, FfiConfig,
18 GoConfig, JavaConfig, NodeConfig, PhpConfig, PythonConfig, RConfig, RubyConfig, StubsConfig, WasmConfig,
19};
20pub use output::{
21 ExcludeConfig, IncludeConfig, LintConfig, OutputConfig, ReadmeConfig, ScaffoldConfig, SyncConfig, TestConfig,
22 TextReplacement,
23};
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct AlefConfig {
28 #[serde(rename = "crate")]
29 pub crate_config: CrateConfig,
30 pub languages: Vec<Language>,
31 #[serde(default)]
32 pub exclude: ExcludeConfig,
33 #[serde(default)]
34 pub include: IncludeConfig,
35 #[serde(default)]
36 pub output: OutputConfig,
37 #[serde(default)]
38 pub python: Option<PythonConfig>,
39 #[serde(default)]
40 pub node: Option<NodeConfig>,
41 #[serde(default)]
42 pub ruby: Option<RubyConfig>,
43 #[serde(default)]
44 pub php: Option<PhpConfig>,
45 #[serde(default)]
46 pub elixir: Option<ElixirConfig>,
47 #[serde(default)]
48 pub wasm: Option<WasmConfig>,
49 #[serde(default)]
50 pub ffi: Option<FfiConfig>,
51 #[serde(default)]
52 pub go: Option<GoConfig>,
53 #[serde(default)]
54 pub java: Option<JavaConfig>,
55 #[serde(default)]
56 pub csharp: Option<CSharpConfig>,
57 #[serde(default)]
58 pub r: Option<RConfig>,
59 #[serde(default)]
60 pub scaffold: Option<ScaffoldConfig>,
61 #[serde(default)]
62 pub readme: Option<ReadmeConfig>,
63 #[serde(default)]
64 pub lint: Option<HashMap<String, LintConfig>>,
65 #[serde(default)]
66 pub test: Option<HashMap<String, TestConfig>>,
67 #[serde(default)]
68 pub custom_files: Option<HashMap<String, Vec<PathBuf>>>,
69 #[serde(default)]
70 pub adapters: Vec<AdapterConfig>,
71 #[serde(default)]
72 pub custom_modules: CustomModulesConfig,
73 #[serde(default)]
74 pub custom_registrations: CustomRegistrationsConfig,
75 #[serde(default)]
76 pub sync: Option<SyncConfig>,
77 #[serde(default)]
81 pub opaque_types: HashMap<String, String>,
82 #[serde(default)]
84 pub generate: GenerateConfig,
85 #[serde(default)]
87 pub generate_overrides: HashMap<String, GenerateConfig>,
88 #[serde(default)]
90 pub dto: DtoConfig,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct CrateConfig {
95 pub name: String,
96 pub sources: Vec<PathBuf>,
97 #[serde(default = "default_version_from")]
98 pub version_from: String,
99 #[serde(default)]
100 pub core_import: Option<String>,
101 #[serde(default)]
103 pub workspace_root: Option<PathBuf>,
104 #[serde(default)]
106 pub skip_core_import: bool,
107 #[serde(default)]
110 pub path_mappings: HashMap<String, String>,
111}
112
113fn default_version_from() -> String {
114 "Cargo.toml".to_string()
115}
116
117fn default_true() -> bool {
118 true
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct GenerateConfig {
126 #[serde(default = "default_true")]
128 pub bindings: bool,
129 #[serde(default = "default_true")]
131 pub errors: bool,
132 #[serde(default = "default_true")]
134 pub configs: bool,
135 #[serde(default = "default_true")]
137 pub async_wrappers: bool,
138 #[serde(default = "default_true")]
140 pub type_conversions: bool,
141 #[serde(default = "default_true")]
143 pub package_metadata: bool,
144 #[serde(default = "default_true")]
146 pub public_api: bool,
147}
148
149impl Default for GenerateConfig {
150 fn default() -> Self {
151 Self {
152 bindings: true,
153 errors: true,
154 configs: true,
155 async_wrappers: true,
156 type_conversions: true,
157 package_metadata: true,
158 public_api: true,
159 }
160 }
161}
162
163impl AlefConfig {
168 pub fn core_import(&self) -> String {
170 self.crate_config
171 .core_import
172 .clone()
173 .unwrap_or_else(|| self.crate_config.name.replace('-', "_"))
174 }
175
176 pub fn ffi_prefix(&self) -> String {
178 self.ffi
179 .as_ref()
180 .and_then(|f| f.prefix.as_ref())
181 .cloned()
182 .unwrap_or_else(|| self.crate_config.name.replace('-', "_"))
183 }
184
185 pub fn ffi_lib_name(&self) -> String {
193 if let Some(name) = self.ffi.as_ref().and_then(|f| f.lib_name.as_ref()) {
195 return name.clone();
196 }
197
198 if let Some(ffi_path) = self.output.ffi.as_ref() {
201 let path = std::path::Path::new(ffi_path);
202 let components: Vec<_> = path
205 .components()
206 .filter_map(|c| {
207 if let std::path::Component::Normal(s) = c {
208 s.to_str()
209 } else {
210 None
211 }
212 })
213 .collect();
214 let crate_dir = components
217 .iter()
218 .rev()
219 .find(|&&s| s != "src" && s != "lib" && s != "include")
220 .copied();
221 if let Some(dir) = crate_dir {
222 return dir.replace('-', "_");
223 }
224 }
225
226 format!("{}_ffi", self.ffi_prefix())
228 }
229
230 pub fn ffi_header_name(&self) -> String {
232 self.ffi
233 .as_ref()
234 .and_then(|f| f.header_name.as_ref())
235 .cloned()
236 .unwrap_or_else(|| format!("{}.h", self.ffi_prefix()))
237 }
238
239 pub fn python_module_name(&self) -> String {
241 self.python
242 .as_ref()
243 .and_then(|p| p.module_name.as_ref())
244 .cloned()
245 .unwrap_or_else(|| format!("_{}", self.crate_config.name.replace('-', "_")))
246 }
247
248 pub fn node_package_name(&self) -> String {
250 self.node
251 .as_ref()
252 .and_then(|n| n.package_name.as_ref())
253 .cloned()
254 .unwrap_or_else(|| self.crate_config.name.clone())
255 }
256
257 pub fn ruby_gem_name(&self) -> String {
259 self.ruby
260 .as_ref()
261 .and_then(|r| r.gem_name.as_ref())
262 .cloned()
263 .unwrap_or_else(|| self.crate_config.name.replace('-', "_"))
264 }
265
266 pub fn php_extension_name(&self) -> String {
268 self.php
269 .as_ref()
270 .and_then(|p| p.extension_name.as_ref())
271 .cloned()
272 .unwrap_or_else(|| self.crate_config.name.replace('-', "_"))
273 }
274
275 pub fn elixir_app_name(&self) -> String {
277 self.elixir
278 .as_ref()
279 .and_then(|e| e.app_name.as_ref())
280 .cloned()
281 .unwrap_or_else(|| self.crate_config.name.replace('-', "_"))
282 }
283
284 pub fn go_module(&self) -> String {
286 self.go
287 .as_ref()
288 .and_then(|g| g.module.as_ref())
289 .cloned()
290 .unwrap_or_else(|| format!("github.com/kreuzberg-dev/{}", self.crate_config.name))
291 }
292
293 pub fn java_package(&self) -> String {
295 self.java
296 .as_ref()
297 .and_then(|j| j.package.as_ref())
298 .cloned()
299 .unwrap_or_else(|| "dev.kreuzberg".to_string())
300 }
301
302 pub fn csharp_namespace(&self) -> String {
304 self.csharp
305 .as_ref()
306 .and_then(|c| c.namespace.as_ref())
307 .cloned()
308 .unwrap_or_else(|| {
309 use heck::ToPascalCase;
310 self.crate_config.name.to_pascal_case()
311 })
312 }
313
314 pub fn r_package_name(&self) -> String {
316 self.r
317 .as_ref()
318 .and_then(|r| r.package_name.as_ref())
319 .cloned()
320 .unwrap_or_else(|| self.crate_config.name.clone())
321 }
322
323 pub fn rewrite_path(&self, rust_path: &str) -> String {
326 let mut mappings: Vec<_> = self.crate_config.path_mappings.iter().collect();
328 mappings.sort_by(|a, b| b.0.len().cmp(&a.0.len()));
329
330 for (from, to) in &mappings {
331 if rust_path.starts_with(from.as_str()) {
332 return format!("{}{}", to, &rust_path[from.len()..]);
333 }
334 }
335 rust_path.to_string()
336 }
337}
338
339pub fn resolve_output_dir(config_path: Option<&PathBuf>, crate_name: &str, default: &str) -> String {
342 config_path
343 .map(|p| p.to_string_lossy().replace("{name}", crate_name))
344 .unwrap_or_else(|| default.replace("{name}", crate_name))
345}
346
347pub fn detect_serde_available(output_dir: &str) -> bool {
353 let src_path = std::path::Path::new(output_dir);
354 let mut dir = src_path;
356 loop {
357 let cargo_toml = dir.join("Cargo.toml");
358 if cargo_toml.exists() {
359 return cargo_toml_has_serde(&cargo_toml);
360 }
361 match dir.parent() {
362 Some(parent) if !parent.as_os_str().is_empty() => dir = parent,
363 _ => break,
364 }
365 }
366 false
367}
368
369fn cargo_toml_has_serde(path: &std::path::Path) -> bool {
375 let content = match std::fs::read_to_string(path) {
376 Ok(c) => c,
377 Err(_) => return false,
378 };
379
380 let has_serde_json = content.contains("serde_json");
381 let has_serde_dep = content.lines().any(|line| {
385 let trimmed = line.trim();
386 trimmed.starts_with("serde ")
388 || trimmed.starts_with("serde=")
389 || trimmed.starts_with("serde.")
390 || trimmed == "[dependencies.serde]"
391 });
392
393 has_serde_json && has_serde_dep
394}