Skip to main content

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}