1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use serde_json::Value as JsonValue;
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
8pub struct ExcludeConfig {
9 #[serde(default)]
10 pub types: Vec<String>,
11 #[serde(default)]
12 pub functions: Vec<String>,
13 #[serde(default)]
15 pub methods: Vec<String>,
16}
17
18#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
19pub struct IncludeConfig {
20 #[serde(default)]
21 pub types: Vec<String>,
22 #[serde(default)]
23 pub functions: Vec<String>,
24}
25
26#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
27pub struct OutputConfig {
28 pub python: Option<PathBuf>,
29 pub node: Option<PathBuf>,
30 pub ruby: Option<PathBuf>,
31 pub php: Option<PathBuf>,
32 pub elixir: Option<PathBuf>,
33 pub wasm: Option<PathBuf>,
34 pub ffi: Option<PathBuf>,
35 pub go: Option<PathBuf>,
36 pub java: Option<PathBuf>,
37 pub kotlin: Option<PathBuf>,
38 pub kotlin_android: Option<PathBuf>,
39 pub dart: Option<PathBuf>,
40 pub swift: Option<PathBuf>,
41 pub gleam: Option<PathBuf>,
42 pub csharp: Option<PathBuf>,
43 pub r: Option<PathBuf>,
44 pub zig: Option<PathBuf>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
48pub struct ScaffoldConfig {
49 pub description: Option<String>,
50 pub license: Option<String>,
51 pub repository: Option<String>,
52 pub homepage: Option<String>,
53 #[serde(default)]
54 pub authors: Vec<String>,
55 #[serde(default)]
56 pub keywords: Vec<String>,
57 #[serde(default)]
59 pub generated_header: Option<GeneratedHeaderConfig>,
60 #[serde(default)]
62 pub precommit: Option<PrecommitConfig>,
63 pub cargo: Option<ScaffoldCargo>,
67}
68
69#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
70pub struct GeneratedHeaderConfig {
71 #[serde(default)]
73 pub issues_url: Option<String>,
74 #[serde(default)]
76 pub regenerate_command: Option<String>,
77 #[serde(default)]
79 pub verify_command: Option<String>,
80}
81
82#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
83pub struct PrecommitConfig {
84 #[serde(default)]
86 pub include_shared_hooks: Option<bool>,
87 #[serde(default)]
89 pub shared_hooks_repo: Option<String>,
90 #[serde(default)]
92 pub shared_hooks_rev: Option<String>,
93 #[serde(default)]
95 pub include_alef_hooks: Option<bool>,
96 #[serde(default)]
98 pub alef_hooks_repo: Option<String>,
99 #[serde(default)]
101 pub alef_hooks_rev: Option<String>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
110pub struct ScaffoldCargo {
111 #[serde(default)]
115 pub targets: ScaffoldCargoTargets,
116 #[serde(default = "default_build_jobs")]
119 pub build_jobs: u32,
120 #[serde(default)]
123 pub env: HashMap<String, ScaffoldCargoEnvValue>,
124}
125
126impl Default for ScaffoldCargo {
127 fn default() -> Self {
128 Self {
129 targets: ScaffoldCargoTargets::default(),
130 build_jobs: default_build_jobs(),
131 env: HashMap::new(),
132 }
133 }
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
138pub struct ScaffoldCargoTargets {
139 #[serde(default = "default_true")]
140 pub macos_dynamic_lookup: bool,
141 #[serde(default = "default_true")]
142 pub x86_64_pc_windows_msvc: bool,
143 #[serde(default = "default_true")]
144 pub i686_pc_windows_msvc: bool,
145 #[serde(default = "default_true")]
146 pub aarch64_unknown_linux_gnu: bool,
147 #[serde(default = "default_true")]
148 pub x86_64_unknown_linux_musl: bool,
149 #[serde(default = "default_true")]
150 pub wasm32_unknown_unknown: bool,
151}
152
153impl Default for ScaffoldCargoTargets {
154 fn default() -> Self {
155 Self {
156 macos_dynamic_lookup: true,
157 x86_64_pc_windows_msvc: true,
158 i686_pc_windows_msvc: true,
159 aarch64_unknown_linux_gnu: true,
160 x86_64_unknown_linux_musl: true,
161 wasm32_unknown_unknown: true,
162 }
163 }
164}
165
166fn default_true() -> bool {
167 true
168}
169
170fn default_build_jobs() -> u32 {
171 4
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
177#[serde(untagged)]
178pub enum ScaffoldCargoEnvValue {
179 Plain(String),
180 Structured {
181 value: String,
182 #[serde(default)]
183 relative: bool,
184 },
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
188pub struct ReadmeConfig {
189 pub template_dir: Option<PathBuf>,
190 pub snippets_dir: Option<PathBuf>,
191 pub config: Option<PathBuf>,
193 pub output_pattern: Option<String>,
194 pub discord_url: Option<String>,
196 pub banner_url: Option<String>,
198 #[serde(default)]
202 pub languages: HashMap<String, JsonValue>,
203 #[serde(default)]
206 pub targets: HashMap<String, JsonValue>,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
213#[serde(untagged)]
214pub enum StringOrVec {
215 Single(String),
216 Multiple(Vec<String>),
217}
218
219impl StringOrVec {
220 pub fn commands(&self) -> Vec<&str> {
222 match self {
223 StringOrVec::Single(s) => vec![s.as_str()],
224 StringOrVec::Multiple(v) => v.iter().map(String::as_str).collect(),
225 }
226 }
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
230pub struct LintConfig {
231 pub precondition: Option<String>,
233 pub before: Option<StringOrVec>,
235 pub format: Option<StringOrVec>,
236 pub check: Option<StringOrVec>,
237 pub typecheck: Option<StringOrVec>,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
241pub struct UpdateConfig {
242 pub precondition: Option<String>,
244 pub before: Option<StringOrVec>,
246 pub update: Option<StringOrVec>,
248 pub upgrade: Option<StringOrVec>,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
253pub struct TestAppRunConfig {
254 pub precondition: Option<String>,
256 pub before: Option<StringOrVec>,
258 pub run: Option<StringOrVec>,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, JsonSchema)]
264pub struct TestConfig {
265 pub precondition: Option<String>,
267 pub before: Option<StringOrVec>,
269 pub command: Option<StringOrVec>,
271 pub e2e: Option<StringOrVec>,
273 pub coverage: Option<StringOrVec>,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
278pub struct SetupConfig {
279 pub precondition: Option<String>,
281 pub before: Option<StringOrVec>,
283 pub install: Option<StringOrVec>,
285 #[serde(default = "default_setup_timeout")]
287 pub timeout_seconds: u64,
288 #[serde(default)]
296 pub workdir: Option<PathBuf>,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
300pub struct CleanConfig {
301 pub precondition: Option<String>,
303 pub before: Option<StringOrVec>,
305 pub clean: Option<StringOrVec>,
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
310pub struct BuildCommandConfig {
311 pub precondition: Option<String>,
313 pub before: Option<StringOrVec>,
315 pub build: Option<StringOrVec>,
317 pub build_release: Option<StringOrVec>,
319}
320
321impl BuildCommandConfig {
322 pub fn merge_overlay(mut self, other: &Self) -> Self {
328 if other.precondition.is_some() {
329 self.precondition = other.precondition.clone();
330 }
331 if other.before.is_some() {
332 self.before = other.before.clone();
333 }
334 if other.build.is_some() {
335 self.build = other.build.clone();
336 }
337 if other.build_release.is_some() {
338 self.build_release = other.build_release.clone();
339 }
340 self
341 }
342}
343
344fn default_setup_timeout() -> u64 {
345 1800
346}
347
348#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
360pub struct OutputTemplate {
361 pub python: Option<String>,
362 pub node: Option<String>,
363 pub ruby: Option<String>,
364 pub php: Option<String>,
365 pub elixir: Option<String>,
366 pub wasm: Option<String>,
367 pub ffi: Option<String>,
368 pub go: Option<String>,
369 pub java: Option<String>,
370 pub kotlin: Option<String>,
371 pub kotlin_android: Option<String>,
372 pub dart: Option<String>,
373 pub swift: Option<String>,
374 pub gleam: Option<String>,
375 pub csharp: Option<String>,
376 pub r: Option<String>,
377 pub zig: Option<String>,
378}
379
380impl OutputTemplate {
381 pub fn resolve(&self, crate_name: &str, lang: &str, multi_crate: bool) -> PathBuf {
397 validate_output_segment(crate_name, "crate_name");
398 validate_output_segment(lang, "lang");
399
400 let path = if let Some(template) = self.entry(lang) {
401 PathBuf::from(template.replace("{crate}", crate_name).replace("{lang}", lang))
402 } else if multi_crate {
403 PathBuf::from(format!("packages/{lang}/{crate_name}"))
404 } else {
405 match lang {
406 "python" => PathBuf::from("packages/python"),
407 "node" => PathBuf::from("packages/node"),
408 "ruby" => PathBuf::from("packages/ruby"),
409 "php" => PathBuf::from("packages/php"),
410 "elixir" => PathBuf::from("packages/elixir"),
411 other => PathBuf::from(format!("packages/{other}")),
412 }
413 };
414
415 validate_output_path(&path);
416 path
417 }
418
419 pub fn entry(&self, lang: &str) -> Option<&str> {
421 match lang {
422 "python" => self.python.as_deref(),
423 "node" => self.node.as_deref(),
424 "ruby" => self.ruby.as_deref(),
425 "php" => self.php.as_deref(),
426 "elixir" => self.elixir.as_deref(),
427 "wasm" => self.wasm.as_deref(),
428 "ffi" => self.ffi.as_deref(),
429 "go" => self.go.as_deref(),
430 "java" => self.java.as_deref(),
431 "kotlin" => self.kotlin.as_deref(),
432 "kotlin_android" => self.kotlin_android.as_deref(),
433 "dart" => self.dart.as_deref(),
434 "swift" => self.swift.as_deref(),
435 "gleam" => self.gleam.as_deref(),
436 "csharp" => self.csharp.as_deref(),
437 "r" => self.r.as_deref(),
438 "zig" => self.zig.as_deref(),
439 _ => None,
440 }
441 }
442}
443
444fn validate_output_segment(segment: &str, label: &str) {
451 if segment.contains('\0') {
452 panic!("invalid {label}: NUL byte is not allowed in output path segments (got {segment:?})");
453 }
454 if segment.contains('/') || segment.contains('\\') {
455 panic!("invalid {label}: path separators are not allowed in output path segments (got {segment:?})");
456 }
457}
458
459fn validate_output_path(path: &std::path::Path) {
465 use std::path::Component;
466 for component in path.components() {
467 match component {
468 Component::ParentDir => {
469 panic!(
470 "resolved output path `{}` contains `..` and would escape the project root",
471 path.display()
472 );
473 }
474 Component::RootDir | Component::Prefix(_) => {
475 panic!(
476 "resolved output path `{}` is absolute and would escape the project root",
477 path.display()
478 );
479 }
480 _ => {}
481 }
482 }
483}
484
485#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
487pub struct TextReplacement {
488 pub path: String,
490 pub search: String,
492 pub replace: String,
494}
495
496#[cfg(test)]
497mod tests;
498
499#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
501pub struct SyncConfig {
502 #[serde(default)]
504 pub extra_paths: Vec<String>,
505 #[serde(default)]
507 pub text_replacements: Vec<TextReplacement>,
508}
509
510#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
515#[serde(deny_unknown_fields)]
516pub struct CitationAuthor {
517 #[serde(default, alias = "family-names")]
519 pub family_names: Option<String>,
520 #[serde(default, alias = "given-names")]
522 pub given_names: Option<String>,
523 #[serde(default)]
525 pub name: Option<String>,
526 #[serde(default)]
528 pub email: Option<String>,
529 #[serde(default)]
531 pub orcid: Option<String>,
532}
533
534#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
545#[serde(deny_unknown_fields)]
546pub struct CitationConfig {
547 pub title: String,
549 #[serde(rename = "abstract")]
551 pub abstract_: String,
552 pub authors: Vec<CitationAuthor>,
555 #[serde(default = "default_citation_message")]
557 pub message: String,
558 #[serde(rename = "repository-code", alias = "repository_code")]
560 pub repository_code: String,
561 #[serde(default)]
563 pub url: Option<String>,
564 #[serde(default)]
567 pub license: Option<String>,
568 #[serde(default, rename = "date-released", alias = "date_released")]
575 pub date_released: Option<String>,
576 #[serde(default)]
578 pub doi: Option<String>,
579}
580
581fn default_citation_message() -> String {
582 "If you use this software, please cite it using the metadata below.".to_string()
583}