1use crate::domain::validation::{validate_env_key, validate_name};
2use crate::domain::{
3 CommandDetails, CommandListEntry, CommandPayload, CommandSpec, CommandSummary, EvoPaths,
4};
5use crate::error::Result;
6use crate::infra::process::{preflight_command, run_command};
7use crate::infra::search::SearchIndex;
8use crate::infra::store::CommandStore;
9use std::collections::BTreeMap;
10use std::ffi::OsString;
11use std::process::ExitStatus;
12
13#[derive(Clone, Debug)]
14pub struct Evo {
15 paths: EvoPaths,
16 store: CommandStore,
17 search_index: SearchIndex,
18}
19
20impl Evo {
21 pub fn new(paths: EvoPaths) -> Self {
22 Self {
23 store: CommandStore::new(paths.commands_dir.clone()),
24 search_index: SearchIndex::new(paths.index_db.clone()),
25 paths,
26 }
27 }
28
29 pub fn paths(&self) -> &EvoPaths {
30 &self.paths
31 }
32
33 pub fn command_spec(&self, name: &str) -> CommandSpec {
34 self.paths.command_spec(name)
35 }
36
37 pub fn resolve_command(&self, name: &str) -> Result<CommandSpec> {
38 self.store.resolve_command(name)
39 }
40
41 pub fn create(&self, name: &str, payload: CommandPayload) -> Result<CommandSpec> {
42 let spec = self.store.create_command(name, payload)?;
43 self.search_index.rebuild(&self.store)?;
44 Ok(spec)
45 }
46
47 pub fn update(&self, name: &str, payload: CommandPayload) -> Result<CommandSpec> {
48 let spec = self.store.update_command(name, payload)?;
49 self.search_index.rebuild(&self.store)?;
50 Ok(spec)
51 }
52
53 pub fn list(&self) -> Result<Vec<CommandListEntry>> {
54 self.store.list_command_entries()
55 }
56
57 pub fn show(&self, name: &str) -> Result<CommandDetails> {
58 self.store.load_command_details(name)
59 }
60
61 pub fn remove(&self, name: &str) -> Result<()> {
62 self.store.remove_command(name)?;
63 self.search_index.rebuild(&self.store)
64 }
65
66 pub fn run(
67 &self,
68 name: &str,
69 args: &[OsString],
70 stdin_bytes: Option<Vec<u8>>,
71 ) -> Result<ExitStatus> {
72 let spec = self.store.resolve_command(name)?;
73 preflight_command(&self.store, &spec, name)?;
74 run_command(&self.store, &spec, name, args, stdin_bytes)
75 }
76
77 pub fn list_env_keys(&self, name: &str) -> Result<Vec<String>> {
78 let spec = self.store.resolve_command(name)?;
79 let env_values = self.store.load_env_values(&spec.env_path, name)?;
80 Ok(env_values.into_keys().collect())
81 }
82
83 pub fn set_env(&self, name: &str, key: String, value: String) -> Result<()> {
84 validate_env_key(&key)?;
85 let spec = self.store.resolve_command(name)?;
86 let mut env_values = self.store.load_env_values(&spec.env_path, name)?;
87 env_values.insert(key, value);
88 self.store.write_env_values(&spec.env_path, &env_values)
89 }
90
91 pub fn unset_env(&self, name: &str, keys: Vec<String>) -> Result<()> {
92 let spec = self.store.resolve_command(name)?;
93 let mut env_values = self.store.load_env_values(&spec.env_path, name)?;
94 for key in keys {
95 validate_env_key(&key)?;
96 env_values.remove(&key);
97 }
98 self.store.write_env_values(&spec.env_path, &env_values)
99 }
100
101 pub fn ensure_search_index(&self) -> Result<()> {
102 self.search_index.ensure(&self.store)
103 }
104
105 pub fn reindex(&self) -> Result<()> {
106 self.search_index.rebuild(&self.store)
107 }
108
109 pub fn search(&self, query: &str, limit: usize) -> Result<Vec<CommandSummary>> {
110 self.search_index.ensure(&self.store)?;
111 self.search_index.search(&self.store, query, limit)
112 }
113
114 pub fn list_summaries(&self) -> Result<Vec<CommandSummary>> {
115 self.store.list_command_summaries()
116 }
117
118 pub fn validate_name(&self, name: &str) -> Result<()> {
119 validate_name(name)
120 }
121
122 pub fn load_env_values(
123 &self,
124 spec: &CommandSpec,
125 command_name: &str,
126 ) -> Result<BTreeMap<String, String>> {
127 self.store.load_env_values(&spec.env_path, command_name)
128 }
129}
130
131impl Default for Evo {
132 fn default() -> Self {
133 Self::new(EvoPaths::default())
134 }
135}