Single-commit clean baseline after security scrub of niche-tells, project codenames, internal jargon, and contributor-email leaks. Contents: - 100 Rust crates (_primitives/_rust/) - 37 agent manifests (_manifests/) + generated specs (_generated/) - 67 user-invocable skills (skills/) - 33 hooks (hooks/) - Composition blocks (_blocks/) - Documentation (docs/, README.md) - TS adapter packages (_ts_packages/) - Assembler (_assembler/) - Roles (_roles/) - Templates (_templates/) - Forgejo CI (.forgejo/) Author: Denis Parfionovich <info@greendragon.info> License: see LICENSE.
133 lines
4.6 KiB
Rust
133 lines
4.6 KiB
Rust
//! skeleton — generate Rust impl-skeleton files from a TraitKind.
|
|
//!
|
|
//! Public entry-point: `render_skeleton`. Static trait metadata lives in
|
|
//! `skeleton_table` to keep this file ≤200 LOC.
|
|
//!
|
|
//! Constructor Pattern: one responsibility, ≤200 LOC, ≤30 LOC per fn.
|
|
|
|
use crate::skeleton_table::trait_meta;
|
|
use crate::trait_patterns::TraitKind;
|
|
|
|
/// Generate a Rust impl-skeleton for `module_name` implementing `target_trait`.
|
|
///
|
|
/// Output is a String containing valid-looking (but unimplemented) Rust source
|
|
/// with one `unimplemented!()` body per trait method and a TODO comment per
|
|
/// method describing expected behaviour.
|
|
pub fn render_skeleton(module_name: &str, target_trait: TraitKind) -> String {
|
|
let meta = trait_meta(target_trait);
|
|
let type_name = module_name_to_type(module_name);
|
|
let mut out = String::with_capacity(2048);
|
|
write_header(&mut out, module_name, target_trait, meta.trait_name);
|
|
write_imports(&mut out, meta.use_imports);
|
|
write_struct_stub(&mut out, &type_name);
|
|
write_dna_impl(&mut out, &type_name);
|
|
write_trait_impl(&mut out, meta, &type_name, module_name);
|
|
out
|
|
}
|
|
|
|
fn write_header(out: &mut String, module_name: &str, kind: TraitKind, trait_name: &str) {
|
|
out.push_str(&format!(
|
|
"// AUTO-GENERATED skeleton for {module_name} \u{2192} {kind:?}\n\
|
|
// TODO: replace `unimplemented!()` with real impl. Verify the trait\n\
|
|
// signature matches your kei-runtime-core version.\n\n\
|
|
// Trait: {trait_name}\n\n"
|
|
));
|
|
}
|
|
|
|
fn write_imports(out: &mut String, imports: &str) {
|
|
out.push_str(imports);
|
|
out.push('\n');
|
|
}
|
|
|
|
fn write_struct_stub(out: &mut String, type_name: &str) {
|
|
out.push_str(&format!(
|
|
"pub struct {type_name}; // TODO: rename + add fields\n\n"
|
|
));
|
|
}
|
|
|
|
fn write_dna_impl(out: &mut String, type_name: &str) {
|
|
out.push_str(&format!("impl kei_runtime_core::dna::HasDna for {type_name} {{\n"));
|
|
out.push_str(&format!(" fn dna(&self) -> &kei_runtime_core::dna::Dna {{\n"));
|
|
out.push_str(&format!(" unimplemented!(\"HasDna::dna for {type_name}\")\n"));
|
|
out.push_str(" }\n");
|
|
out.push_str(&format!(" fn parent_dna(&self) -> Option<&kei_runtime_core::dna::Dna> {{\n"));
|
|
out.push_str(&format!(" unimplemented!(\"HasDna::parent_dna for {type_name}\")\n"));
|
|
out.push_str(" }\n");
|
|
out.push_str("}\n\n");
|
|
}
|
|
|
|
fn write_trait_impl(
|
|
out: &mut String,
|
|
meta: &crate::skeleton_table::TraitMeta,
|
|
type_name: &str,
|
|
module_name: &str,
|
|
) {
|
|
out.push_str(&format!(
|
|
"#[async_trait::async_trait]\nimpl {} for {} {{\n",
|
|
meta.trait_name, type_name
|
|
));
|
|
for m in meta.methods {
|
|
out.push_str(&format!(
|
|
" // TODO: {}\n {}\n unimplemented!(\"{}::{} for {}\")\n }}\n\n",
|
|
m.todo_hint, m.sig, meta.trait_name, m.name, module_name
|
|
));
|
|
}
|
|
out.push_str("}\n");
|
|
}
|
|
|
|
/// Convert kebab-case module name to PascalCase with `Foreign` prefix.
|
|
///
|
|
/// `kei-foreign-store` → `ForeignKeiForeignStore`
|
|
pub fn module_name_to_type(module_name: &str) -> String {
|
|
let pascal: String = module_name
|
|
.split('-')
|
|
.map(capitalize_first)
|
|
.collect();
|
|
format!("Foreign{pascal}")
|
|
}
|
|
|
|
fn capitalize_first(s: &str) -> String {
|
|
let mut chars = s.chars();
|
|
match chars.next() {
|
|
None => String::new(),
|
|
Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn type_name_conversion_kebab() {
|
|
assert_eq!(module_name_to_type("kei-foreign-store"), "ForeignKeiForeignStore");
|
|
assert_eq!(module_name_to_type("my-backend"), "ForeignMyBackend");
|
|
assert_eq!(module_name_to_type("foo"), "ForeignFoo");
|
|
}
|
|
|
|
#[test]
|
|
fn render_contains_impl_keyword() {
|
|
let out = render_skeleton("kei-backend-daytona", TraitKind::ComputeProvider);
|
|
assert!(out.contains("impl"), "missing impl keyword");
|
|
}
|
|
|
|
#[test]
|
|
fn render_contains_unimplemented() {
|
|
let out = render_skeleton("kei-backend-daytona", TraitKind::ComputeProvider);
|
|
assert!(out.contains("unimplemented!("), "missing unimplemented!()");
|
|
}
|
|
|
|
#[test]
|
|
fn render_contains_async_fn() {
|
|
let out = render_skeleton("kei-backend-daytona", TraitKind::ComputeProvider);
|
|
assert!(out.contains("async fn"), "missing async fn");
|
|
}
|
|
|
|
#[test]
|
|
fn all_kinds_render_without_panic() {
|
|
for &kind in TraitKind::all() {
|
|
let out = render_skeleton("test-module", kind);
|
|
assert!(!out.is_empty(), "empty output for {kind:?}");
|
|
}
|
|
}
|
|
}
|