cuenv/lib.rs
1// Rust 1.92 compiler bug: false positives for thiserror/miette derive macro fields
2// https://github.com/rust-lang/rust/issues/147648
3#![allow(unused_assignments)]
4
5//! cuenv - CUE-powered environment management library
6//!
7//! This crate provides a library-first architecture for cuenv, allowing external
8//! crates to extend functionality by registering custom providers.
9//!
10//! # Architecture
11//!
12//! cuenv uses a unified provider system where providers implement one or more
13//! capability traits:
14//!
15//! - [`SyncCapability`] - Sync files from CUE configuration
16//! - [`RuntimeCapability`] - Execute tasks (future)
17//! - [`SecretCapability`] - Resolve secrets (future)
18//!
19//! # Example: Custom CLI with Additional Providers
20//!
21//! ```ignore
22//! use cuenv::{Cuenv, SyncCapability};
23//!
24//! fn main() -> cuenv::Result<()> {
25//! Cuenv::builder()
26//! .with_defaults()
27//! .with_sync_provider(my_provider::CustomProvider::new())
28//! .build()
29//! .run()
30//! }
31//! ```
32//!
33//! # Example: Multi-Capability Provider
34//!
35//! A single provider can implement multiple capabilities:
36//!
37//! ```ignore
38//! use cuenv::{Provider, SyncCapability, RuntimeCapability};
39//!
40//! pub struct DaggerProvider;
41//!
42//! impl Provider for DaggerProvider {
43//! fn name(&self) -> &'static str { "dagger" }
44//! fn description(&self) -> &'static str { "Dagger-based sync and execution" }
45//! fn as_any(&self) -> &dyn std::any::Any { self }
46//! fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
47//! }
48//!
49//! // Implement both SyncCapability and RuntimeCapability...
50//! ```
51
52// expect_used is allowed for infallible operations like writing to strings
53#![allow(clippy::expect_used)]
54
55mod builder;
56/// CLI argument parsing and exit codes.
57pub mod cli;
58/// Command implementations (task, env, sync, etc.).
59pub mod commands;
60/// Shell completion generation.
61pub mod completions;
62/// Multi-process event coordination.
63pub mod coordinator;
64/// Event handling and routing.
65pub mod events;
66/// Performance measurement utilities.
67pub mod performance;
68/// Provider trait definitions.
69pub mod provider;
70/// Built-in provider implementations.
71pub mod providers;
72/// Provider registration and lookup.
73pub mod registry;
74/// Tracing and logging configuration.
75pub mod tracing;
76/// Terminal UI components.
77pub mod tui;
78
79// Re-export public API
80pub use builder::CuenvBuilder;
81pub use cuenv_core::Result;
82pub use provider::{Provider, RuntimeCapability, SecretCapability, SyncCapability};
83pub use registry::ProviderRegistry;
84
85use crate::cli::EXIT_OK;
86
87/// The main cuenv application.
88///
89/// Use [`Cuenv::builder()`] to create a new instance with custom providers,
90/// or [`Cuenv::with_defaults()`] for the standard configuration.
91pub struct Cuenv {
92 /// The provider registry containing all registered providers.
93 pub registry: ProviderRegistry,
94}
95
96impl Cuenv {
97 /// Create a new builder for configuring cuenv.
98 #[must_use]
99 pub fn builder() -> CuenvBuilder {
100 CuenvBuilder::new()
101 }
102
103 /// Create cuenv with default providers (ci, codegen, rules).
104 #[must_use]
105 pub fn with_defaults() -> Self {
106 Self::builder().with_defaults().build()
107 }
108
109 /// Build the `sync` subcommand dynamically from registered providers.
110 ///
111 /// Each sync provider contributes a subcommand via its `build_sync_command()` method.
112 ///
113 /// # Example
114 ///
115 /// ```ignore
116 /// let cuenv = Cuenv::with_defaults();
117 /// let sync_cmd = cuenv.build_sync_command();
118 /// // sync_cmd has subcommands: ci, codegen, rules
119 /// ```
120 #[must_use]
121 pub fn build_sync_command(&self) -> clap::Command {
122 use clap::{Arg, Command};
123
124 let mut sync_cmd = Command::new("sync")
125 .about("Sync generated files from CUE configuration")
126 .arg(
127 Arg::new("path")
128 .long("path")
129 .short('p')
130 .help("Path to directory containing CUE files")
131 .default_value("."),
132 )
133 .arg(
134 Arg::new("package")
135 .long("package")
136 .help("Name of the CUE package to evaluate")
137 .default_value("cuenv"),
138 )
139 .arg(
140 Arg::new("dry-run")
141 .long("dry-run")
142 .help("Show what would be generated without writing files")
143 .action(clap::ArgAction::SetTrue)
144 .global(true),
145 )
146 .arg(
147 Arg::new("check")
148 .long("check")
149 .help("Check if files are in sync without making changes")
150 .action(clap::ArgAction::SetTrue)
151 .global(true),
152 )
153 .arg(
154 Arg::new("all")
155 .long("all")
156 .short('A')
157 .help("Sync all projects in the workspace")
158 .action(clap::ArgAction::SetTrue)
159 .global(true),
160 );
161
162 // Add subcommands from registered sync providers
163 for provider in self.registry.sync_providers() {
164 sync_cmd = sync_cmd.subcommand(provider.build_sync_command());
165 }
166
167 sync_cmd
168 }
169
170 /// Run the cuenv CLI (placeholder).
171 ///
172 /// **Note**: This method is a placeholder for future library-driven CLI execution.
173 /// Currently, the full CLI logic remains in the binary (`main.rs`).
174 ///
175 /// For now, use the `cuenv` binary directly or the individual provider APIs
176 /// like [`build_sync_command()`](Self::build_sync_command).
177 ///
178 /// # Errors
179 ///
180 /// Returns an error if command execution fails.
181 #[doc(hidden)]
182 pub fn run(self) -> Result<()> {
183 // Placeholder: In the future, this will parse args using build_sync_command()
184 // and dispatch to providers. For now, return success as the binary handles CLI.
185 let exit_code = run_cli_with_registry(self);
186 if exit_code == EXIT_OK {
187 Ok(())
188 } else {
189 Err(cuenv_core::Error::configuration(format!(
190 "Command failed with exit code {exit_code}"
191 )))
192 }
193 }
194}
195
196/// Run the CLI with the given cuenv instance.
197///
198/// This is a placeholder that will be expanded to use the registry
199/// for dynamic CLI generation in the future.
200fn run_cli_with_registry(_cuenv: Cuenv) -> i32 {
201 // For now, this just returns OK
202 // The actual CLI logic remains in main.rs
203 // Future: Use cuenv.registry to build dynamic CLI
204 EXIT_OK
205}
206
207/// Exit code for SIGINT (128 + signal number 2)
208pub const EXIT_SIGINT: i32 = 130;
209
210/// LLM context content (llms.txt + CUE schemas concatenated at build time)
211pub const LLMS_CONTENT: &str = include_str!(concat!(env!("OUT_DIR"), "/llms-full.txt"));
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_cuenv_builder() {
219 let cuenv = Cuenv::builder().build();
220 // Registry should be empty when no providers are added
221 assert!(cuenv.registry.is_empty());
222 }
223
224 #[test]
225 fn test_cuenv_with_defaults() {
226 let cuenv = Cuenv::with_defaults();
227 // Should have default sync providers registered
228 assert_eq!(cuenv.registry.sync_provider_count(), 3);
229 }
230
231 #[test]
232 fn test_dynamic_sync_command() {
233 let cuenv = Cuenv::with_defaults();
234 let sync_cmd = cuenv.build_sync_command();
235
236 // Should have the expected subcommands from providers
237 let subcommands: Vec<_> = sync_cmd.get_subcommands().map(|c| c.get_name()).collect();
238 assert!(subcommands.contains(&"ci"), "Missing 'ci' subcommand");
239 assert!(
240 subcommands.contains(&"codegen"),
241 "Missing 'codegen' subcommand"
242 );
243 assert!(subcommands.contains(&"rules"), "Missing 'rules' subcommand");
244 }
245
246 #[test]
247 fn test_dynamic_sync_command_empty_registry() {
248 let cuenv = Cuenv::builder().build();
249 let sync_cmd = cuenv.build_sync_command();
250
251 // Should have no subcommands when registry is empty
252 let subcommand_count = sync_cmd.get_subcommands().count();
253 assert_eq!(subcommand_count, 0);
254 }
255
256 #[test]
257 fn test_run_with_empty_registry() {
258 let cuenv = Cuenv::builder().build();
259 let result = cuenv.run();
260 // Should succeed (placeholder returns OK)
261 assert!(result.is_ok());
262 }
263
264 #[test]
265 fn test_run_with_defaults() {
266 let cuenv = Cuenv::with_defaults();
267 let result = cuenv.run();
268 // Should succeed (placeholder returns OK)
269 assert!(result.is_ok());
270 }
271
272 #[test]
273 fn test_exit_sigint_constant() {
274 // SIGINT exit code is 128 + 2 = 130
275 assert_eq!(EXIT_SIGINT, 130);
276 }
277
278 #[test]
279 #[allow(clippy::const_is_empty)]
280 fn test_llms_content_not_empty() {
281 // LLM content should contain some text
282 assert!(!LLMS_CONTENT.is_empty());
283 }
284
285 #[test]
286 fn test_sync_command_has_path_arg() {
287 let cuenv = Cuenv::with_defaults();
288 let sync_cmd = cuenv.build_sync_command();
289
290 let args: Vec<_> = sync_cmd
291 .get_arguments()
292 .map(|a| a.get_id().as_str())
293 .collect();
294 assert!(args.contains(&"path"));
295 assert!(args.contains(&"package"));
296 assert!(args.contains(&"dry-run"));
297 assert!(args.contains(&"check"));
298 assert!(args.contains(&"all"));
299 }
300
301 #[test]
302 fn test_run_cli_with_registry_returns_ok() {
303 let exit_code = run_cli_with_registry(Cuenv::builder().build());
304 assert_eq!(exit_code, EXIT_OK);
305 }
306}