typewriter_engine/lib.rs
1//! # typewriter-engine
2//!
3//! Shared parser/emitter engine used by both proc-macro and CLI flows.
4//!
5//! This crate provides the core functionality for the typewriter type synchronization SDK:
6//! - **Scanning**: Discovers `#[derive(TypeWriter)]` items in Rust source files
7//! - **Parsing**: Converts Rust AST (syn) into language-agnostic IR (Internal Representation)
8//! - **Rendering**: Generates type definitions for target languages (TypeScript, Python, Go, etc.)
9//! - **Drift Detection**: Compares expected generated output with actual on-disk files
10//!
11//! ## Architecture
12//!
13//! ```text
14//! ┌─────────────────┐ ┌─────────────┐ ┌─────────┐
15//! │ Rust Source │────▶│ Scanner │────▶│ Parser │
16//! │ (.rs files) │ │ (scan.rs) │ │(parser) │
17//! └─────────────────┘ └─────────────┘ └────┬────┘
18//! │
19//! ▼
20//! ┌─────────────────┐ ┌─────────────┐ ┌─────────┐
21//! │ Generated Files │◀────│ Writer │◀────│ IR │
22//! │ (.ts, .py, ...) │ │ (emit.rs) │ │ Types │
23//! └─────────────────┘ └─────────────┘ └─────────┘
24//! ```
25//!
26//! ## Modules
27//!
28//! - [`scan`] - Source file discovery and TypeWriter item detection
29//! - [`parser`] - syn DeriveInput → IR type conversion
30//! - [`emit`] - IR → target language type rendering and file writing
31//! - [`drift`] - Drift detection between expected and actual generated files
32//! - [`project`] - Project root and configuration discovery
33//!
34//! ## Usage
35//!
36//! ### CLI Usage
37//!
38//! ```rust,ignore
39//! use typewriter_engine::{scan, emit, project};
40//!
41//! // Discover project root and load config
42//! let project_root = project::discover_project_root(&std::env::current_dir()?)?;
43//! let config = project::load_config(&project_root)?;
44//!
45//! // Scan for TypeWriter definitions
46//! let specs = scan::scan_project(&project_root)?;
47//!
48//! // Render to all target languages
49//! let files = emit::render_specs_deduped(&specs, &project_root, &config, &[], false)?;
50//!
51//! // Write generated files
52//! emit::write_generated_files(&files)?;
53//! ```
54//!
55//! ### Library Usage
56//!
57//! ```rust,ignore
58//! use typewriter_engine::{TypeSpec, Language, TypeDef};
59//! use typewriter_core::ir::{StructDef, TypeDef};
60//!
61//! // Create a type spec manually
62//! let spec = TypeSpec {
63//! type_def: TypeDef::Struct(StructDef { ... }),
64//! targets: vec![Language::TypeScript, Language::Python],
65//! source_path: PathBuf::from("src/models.rs"),
66//! zod_schema: None,
67//! };
68//! ```
69
70pub mod drift;
71pub mod emit;
72pub mod parser;
73pub mod project;
74pub mod scan;
75
76use std::path::PathBuf;
77
78pub use typewriter_core::{config::TypewriterConfig, ir::Language, ir::TypeDef};
79
80/// Parsed TypeWriter definition discovered in a Rust source file.
81///
82/// This struct contains all information needed to generate type definitions
83/// for a single Rust struct or enum marked with `#[derive(TypeWriter)]`.
84#[derive(Debug, Clone)]
85pub struct TypeSpec {
86 /// The parsed IR type definition (struct or enum)
87 pub type_def: TypeDef,
88 /// Target languages to generate types for (from `#[sync_to(...)]`)
89 pub targets: Vec<Language>,
90 /// Path to the source file containing this type
91 pub source_path: PathBuf,
92 /// Optional Zod schema generation override (None = use config default)
93 pub zod_schema: Option<bool>,
94}
95
96/// Parse a comma-separated or list of language names (case-insensitive) into Language values.
97///
98/// # Supported Languages
99///
100/// | String | Language |
101/// |--------|----------|
102/// | `"typescript"`, `"ts"` | TypeScript |
103/// | `"python"`, `"py"` | Python |
104/// | `"go"`, `"golang"` | Go |
105/// | `"swift"` | Swift |
106/// | `"kotlin"`, `"kt"` | Kotlin |
107/// | `"graphql"`, `"gql"` | GraphQL SDL |
108/// | `"json_schema"`, `"jsonschema"` | JSON Schema |
109///
110/// # Errors
111///
112/// Returns an error if any language name is not recognized.
113///
114/// # Examples
115///
116/// ```
117/// use typewriter_engine::parse_languages;
118///
119/// let langs = parse_languages(&["typescript,python".to_string()]).unwrap();
120/// assert!(langs.contains(&typewriter_engine::Language::TypeScript));
121/// assert!(langs.contains(&typewriter_engine::Language::Python));
122/// ```
123pub fn parse_languages(values: &[String]) -> anyhow::Result<Vec<Language>> {
124 let mut langs = Vec::new();
125 for value in values {
126 for raw in value.split(',') {
127 let name = raw.trim();
128 if name.is_empty() {
129 continue;
130 }
131 let lang = Language::from_str(name).ok_or_else(|| {
132 anyhow::anyhow!(
133 "unknown language '{}'. Supported: typescript, python, go, swift, kotlin, graphql, json_schema",
134 name
135 )
136 })?;
137 if !langs.contains(&lang) {
138 langs.push(lang);
139 }
140 }
141 }
142 Ok(langs)
143}
144
145/// Returns a vector of all supported languages.
146///
147/// This is useful when you want to generate types for all supported languages
148/// without having to list them manually.
149///
150/// # Examples
151///
152/// ```
153/// use typewriter_engine::all_languages;
154///
155/// let all = all_languages();
156/// assert!(all.contains(&typewriter_engine::Language::TypeScript));
157/// assert!(all.contains(&typewriter_engine::Language::Python));
158/// assert_eq!(all.len(), 7);
159/// ```
160pub fn all_languages() -> Vec<Language> {
161 vec![
162 Language::TypeScript,
163 Language::Python,
164 Language::Go,
165 Language::Swift,
166 Language::Kotlin,
167 Language::GraphQL,
168 Language::JsonSchema,
169 ]
170}