use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct ExcludeConfig {
#[serde(default)]
pub types: Vec<String>,
#[serde(default)]
pub functions: Vec<String>,
#[serde(default)]
pub methods: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct IncludeConfig {
#[serde(default)]
pub types: Vec<String>,
#[serde(default)]
pub functions: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct OutputConfig {
pub python: Option<PathBuf>,
pub node: Option<PathBuf>,
pub ruby: Option<PathBuf>,
pub php: Option<PathBuf>,
pub elixir: Option<PathBuf>,
pub wasm: Option<PathBuf>,
pub ffi: Option<PathBuf>,
pub go: Option<PathBuf>,
pub java: Option<PathBuf>,
pub kotlin: Option<PathBuf>,
pub kotlin_android: Option<PathBuf>,
pub dart: Option<PathBuf>,
pub swift: Option<PathBuf>,
pub gleam: Option<PathBuf>,
pub csharp: Option<PathBuf>,
pub r: Option<PathBuf>,
pub zig: Option<PathBuf>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ScaffoldConfig {
pub description: Option<String>,
pub license: Option<String>,
pub repository: Option<String>,
pub homepage: Option<String>,
#[serde(default)]
pub authors: Vec<String>,
#[serde(default)]
pub keywords: Vec<String>,
#[serde(default)]
pub generated_header: Option<GeneratedHeaderConfig>,
#[serde(default)]
pub precommit: Option<PrecommitConfig>,
pub cargo: Option<ScaffoldCargo>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct GeneratedHeaderConfig {
#[serde(default)]
pub issues_url: Option<String>,
#[serde(default)]
pub regenerate_command: Option<String>,
#[serde(default)]
pub verify_command: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct PrecommitConfig {
#[serde(default)]
pub include_shared_hooks: Option<bool>,
#[serde(default)]
pub shared_hooks_repo: Option<String>,
#[serde(default)]
pub shared_hooks_rev: Option<String>,
#[serde(default)]
pub include_alef_hooks: Option<bool>,
#[serde(default)]
pub alef_hooks_repo: Option<String>,
#[serde(default)]
pub alef_hooks_rev: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ScaffoldCargo {
#[serde(default)]
pub targets: ScaffoldCargoTargets,
#[serde(default = "default_build_jobs")]
pub build_jobs: u32,
#[serde(default)]
pub env: HashMap<String, ScaffoldCargoEnvValue>,
}
impl Default for ScaffoldCargo {
fn default() -> Self {
Self {
targets: ScaffoldCargoTargets::default(),
build_jobs: default_build_jobs(),
env: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ScaffoldCargoTargets {
#[serde(default = "default_true")]
pub macos_dynamic_lookup: bool,
#[serde(default = "default_true")]
pub x86_64_pc_windows_msvc: bool,
#[serde(default = "default_true")]
pub i686_pc_windows_msvc: bool,
#[serde(default = "default_true")]
pub aarch64_unknown_linux_gnu: bool,
#[serde(default = "default_true")]
pub x86_64_unknown_linux_musl: bool,
#[serde(default = "default_true")]
pub wasm32_unknown_unknown: bool,
}
impl Default for ScaffoldCargoTargets {
fn default() -> Self {
Self {
macos_dynamic_lookup: true,
x86_64_pc_windows_msvc: true,
i686_pc_windows_msvc: true,
aarch64_unknown_linux_gnu: true,
x86_64_unknown_linux_musl: true,
wasm32_unknown_unknown: true,
}
}
}
fn default_true() -> bool {
true
}
fn default_build_jobs() -> u32 {
4
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum ScaffoldCargoEnvValue {
Plain(String),
Structured {
value: String,
#[serde(default)]
relative: bool,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ReadmeConfig {
pub template_dir: Option<PathBuf>,
pub snippets_dir: Option<PathBuf>,
pub config: Option<PathBuf>,
pub output_pattern: Option<String>,
pub discord_url: Option<String>,
pub banner_url: Option<String>,
#[serde(default)]
pub languages: HashMap<String, JsonValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(untagged)]
pub enum StringOrVec {
Single(String),
Multiple(Vec<String>),
}
impl StringOrVec {
pub fn commands(&self) -> Vec<&str> {
match self {
StringOrVec::Single(s) => vec![s.as_str()],
StringOrVec::Multiple(v) => v.iter().map(String::as_str).collect(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct LintConfig {
pub precondition: Option<String>,
pub before: Option<StringOrVec>,
pub format: Option<StringOrVec>,
pub check: Option<StringOrVec>,
pub typecheck: Option<StringOrVec>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct UpdateConfig {
pub precondition: Option<String>,
pub before: Option<StringOrVec>,
pub update: Option<StringOrVec>,
pub upgrade: Option<StringOrVec>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct TestAppRunConfig {
pub precondition: Option<String>,
pub before: Option<StringOrVec>,
pub run: Option<StringOrVec>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, JsonSchema)]
pub struct TestConfig {
pub precondition: Option<String>,
pub before: Option<StringOrVec>,
pub command: Option<StringOrVec>,
pub e2e: Option<StringOrVec>,
pub coverage: Option<StringOrVec>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct SetupConfig {
pub precondition: Option<String>,
pub before: Option<StringOrVec>,
pub install: Option<StringOrVec>,
#[serde(default = "default_setup_timeout")]
pub timeout_seconds: u64,
#[serde(default)]
pub workdir: Option<PathBuf>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct CleanConfig {
pub precondition: Option<String>,
pub before: Option<StringOrVec>,
pub clean: Option<StringOrVec>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct BuildCommandConfig {
pub precondition: Option<String>,
pub before: Option<StringOrVec>,
pub build: Option<StringOrVec>,
pub build_release: Option<StringOrVec>,
}
impl BuildCommandConfig {
pub fn merge_overlay(mut self, other: &Self) -> Self {
if other.precondition.is_some() {
self.precondition = other.precondition.clone();
}
if other.before.is_some() {
self.before = other.before.clone();
}
if other.build.is_some() {
self.build = other.build.clone();
}
if other.build_release.is_some() {
self.build_release = other.build_release.clone();
}
self
}
}
fn default_setup_timeout() -> u64 {
1800
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct OutputTemplate {
pub python: Option<String>,
pub node: Option<String>,
pub ruby: Option<String>,
pub php: Option<String>,
pub elixir: Option<String>,
pub wasm: Option<String>,
pub ffi: Option<String>,
pub go: Option<String>,
pub java: Option<String>,
pub kotlin: Option<String>,
pub kotlin_android: Option<String>,
pub dart: Option<String>,
pub swift: Option<String>,
pub gleam: Option<String>,
pub csharp: Option<String>,
pub r: Option<String>,
pub zig: Option<String>,
}
impl OutputTemplate {
pub fn resolve(&self, crate_name: &str, lang: &str, multi_crate: bool) -> PathBuf {
validate_output_segment(crate_name, "crate_name");
validate_output_segment(lang, "lang");
let path = if let Some(template) = self.entry(lang) {
PathBuf::from(template.replace("{crate}", crate_name).replace("{lang}", lang))
} else if multi_crate {
PathBuf::from(format!("packages/{lang}/{crate_name}"))
} else {
match lang {
"python" => PathBuf::from("packages/python"),
"node" => PathBuf::from("packages/node"),
"ruby" => PathBuf::from("packages/ruby"),
"php" => PathBuf::from("packages/php"),
"elixir" => PathBuf::from("packages/elixir"),
other => PathBuf::from(format!("packages/{other}")),
}
};
validate_output_path(&path);
path
}
pub fn entry(&self, lang: &str) -> Option<&str> {
match lang {
"python" => self.python.as_deref(),
"node" => self.node.as_deref(),
"ruby" => self.ruby.as_deref(),
"php" => self.php.as_deref(),
"elixir" => self.elixir.as_deref(),
"wasm" => self.wasm.as_deref(),
"ffi" => self.ffi.as_deref(),
"go" => self.go.as_deref(),
"java" => self.java.as_deref(),
"kotlin" => self.kotlin.as_deref(),
"kotlin_android" => self.kotlin_android.as_deref(),
"dart" => self.dart.as_deref(),
"swift" => self.swift.as_deref(),
"gleam" => self.gleam.as_deref(),
"csharp" => self.csharp.as_deref(),
"r" => self.r.as_deref(),
"zig" => self.zig.as_deref(),
_ => None,
}
}
}
fn validate_output_segment(segment: &str, label: &str) {
if segment.contains('\0') {
panic!("invalid {label}: NUL byte is not allowed in output path segments (got {segment:?})");
}
if segment.contains('/') || segment.contains('\\') {
panic!("invalid {label}: path separators are not allowed in output path segments (got {segment:?})");
}
}
fn validate_output_path(path: &std::path::Path) {
use std::path::Component;
for component in path.components() {
match component {
Component::ParentDir => {
panic!(
"resolved output path `{}` contains `..` and would escape the project root",
path.display()
);
}
Component::RootDir | Component::Prefix(_) => {
panic!(
"resolved output path `{}` is absolute and would escape the project root",
path.display()
);
}
_ => {}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct TextReplacement {
pub path: String,
pub search: String,
pub replace: String,
}
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
pub struct SyncConfig {
#[serde(default)]
pub extra_paths: Vec<String>,
#[serde(default)]
pub text_replacements: Vec<TextReplacement>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct CitationAuthor {
#[serde(default, alias = "family-names")]
pub family_names: Option<String>,
#[serde(default, alias = "given-names")]
pub given_names: Option<String>,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub email: Option<String>,
#[serde(default)]
pub orcid: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct CitationConfig {
pub title: String,
#[serde(rename = "abstract")]
pub abstract_: String,
pub authors: Vec<CitationAuthor>,
#[serde(default = "default_citation_message")]
pub message: String,
#[serde(rename = "repository-code", alias = "repository_code")]
pub repository_code: String,
#[serde(default)]
pub url: Option<String>,
#[serde(default)]
pub license: Option<String>,
#[serde(default, rename = "date-released", alias = "date_released")]
pub date_released: Option<String>,
#[serde(default)]
pub doi: Option<String>,
}
fn default_citation_message() -> String {
"If you use this software, please cite it using the metadata below.".to_string()
}