intelli_shell/service/
variable.rs1use std::{
2 collections::{BTreeMap, HashMap},
3 env,
4};
5
6use color_eyre::Result;
7use heck::ToShoutySnakeCase;
8use tracing::instrument;
9
10use super::IntelliShellService;
11use crate::{
12 errors::{InsertError, UpdateError},
13 model::{CommandPart, DynamicCommand, Variable, VariableSuggestion, VariableValue},
14 utils::{format_env_var, get_working_dir},
15};
16
17impl IntelliShellService {
18 #[instrument(skip_all)]
22 pub fn replace_command_variables(
23 &self,
24 command: String,
25 values: Vec<(String, Option<String>)>,
26 use_env: bool,
27 ) -> Result<String, Vec<String>> {
28 let values: HashMap<String, Option<String>> =
30 values.into_iter().map(|(n, v)| (n.to_shouty_snake_case(), v)).collect();
31
32 let mut output = String::new();
33 let mut missing = Vec::new();
34
35 let dynamic = DynamicCommand::parse(command);
37 for part in dynamic.parts {
39 match part {
40 CommandPart::VariableValue(_, _) => unreachable!(),
42 CommandPart::Text(t) => output.push_str(&t),
44 CommandPart::Variable(v) => {
46 let env_var_names = v.env_var_names(false);
47 let variable_value = env_var_names.iter().find_map(|env_var_name| values.get(env_var_name));
48 match (variable_value, use_env) {
49 (Some(Some(value)), _) => {
51 output.push_str(&v.apply_functions_to(value));
53 }
54 (Some(None), _) | (None, true) => {
56 let variable_value_env = env_var_names
58 .iter()
59 .find_map(|env_var_name| env::var(env_var_name).ok().map(|v| (env_var_name, v)));
60 match (variable_value_env, v.secret) {
61 (None, _) => missing.push(v.name),
63 (Some((_, env_value)), false) => {
65 output.push_str(&v.apply_functions_to(env_value));
66 }
67 (Some((env_var_name, _)), true) => {
69 output.push_str(&format_env_var(env_var_name));
71 }
72 }
73 }
74 _ => {
76 missing.push(v.name);
77 }
78 }
79 }
80 }
81 }
82
83 if !missing.is_empty() { Err(missing) } else { Ok(output) }
84 }
85
86 #[instrument(skip_all)]
88 pub async fn search_variable_suggestions(
89 &self,
90 root_cmd: &str,
91 variable: &Variable,
92 context: impl IntoIterator<Item = (String, String)>,
93 ) -> Result<Vec<VariableSuggestion>> {
94 tracing::info!("Searching for variable suggestions: [{root_cmd}] {}", variable.name);
95
96 let mut suggestions = Vec::new();
97
98 if variable.secret {
99 suggestions.push(VariableSuggestion::Secret);
101 for env_var_name in variable.env_var_names(true) {
103 if env::var(&env_var_name).is_ok() {
104 suggestions.push(VariableSuggestion::Environment {
105 env_var_name,
106 value: None,
107 });
108 }
109 }
110 } else {
111 suggestions.push(VariableSuggestion::New);
113
114 let context = BTreeMap::from_iter(context);
116 let mut existing_values = self
117 .storage
118 .find_variable_values(
119 root_cmd,
120 &variable.name,
121 get_working_dir(),
122 &context,
123 &self.tuning.variables,
124 )
125 .await?;
126
127 let previous_value = context.get(&variable.name).cloned();
129 if let Some(previous_value) = previous_value
130 && let Some(index) = existing_values.iter().position(|s| s.value == previous_value)
131 {
132 suggestions.push(VariableSuggestion::Existing(existing_values.remove(index)));
134 }
135
136 for env_var_name in variable.env_var_names(true) {
138 if let Ok(value) = env::var(&env_var_name)
139 && !value.trim().is_empty()
140 {
141 let value = variable.apply_functions_to(value);
142 if let Some(index) = existing_values.iter().position(|s| s.value == value) {
144 suggestions.push(VariableSuggestion::Existing(existing_values.remove(index)));
146 } else {
147 suggestions.push(VariableSuggestion::Environment {
149 env_var_name,
150 value: Some(value),
151 });
152 };
153 }
154 }
155 suggestions.extend(existing_values.into_iter().map(VariableSuggestion::Existing));
157 let options = variable
159 .options
160 .iter()
161 .filter(|o| {
162 !suggestions.iter().any(|s| match s {
163 VariableSuggestion::Environment { value: Some(value), .. } => value == *o,
164 VariableSuggestion::Existing(sv) => &sv.value == *o,
165 _ => false,
166 })
167 })
168 .map(|o| VariableSuggestion::Derived(o.to_owned()))
169 .collect::<Vec<_>>();
170 suggestions.extend(options);
171 }
172
173 Ok(suggestions)
174 }
175
176 #[instrument(skip_all)]
178 pub async fn insert_variable_value(&self, value: VariableValue) -> Result<VariableValue, InsertError> {
179 tracing::info!(
180 "Inserting a variable value for '{}' '{}': {}",
181 value.flat_root_cmd,
182 value.flat_variable,
183 value.value
184 );
185 self.storage.insert_variable_value(value).await
186 }
187
188 #[instrument(skip_all)]
190 pub async fn update_variable_value(&self, value: VariableValue) -> Result<VariableValue, UpdateError> {
191 tracing::info!(
192 "Updating variable value '{}': {}",
193 value.id.unwrap_or_default(),
194 value.value
195 );
196 self.storage.update_variable_value(value).await
197 }
198
199 #[instrument(skip_all)]
201 pub async fn increment_variable_value_usage(
202 &self,
203 value_id: i32,
204 context: impl IntoIterator<Item = (String, String)>,
205 ) -> Result<i32, UpdateError> {
206 tracing::info!("Increasing usage for variable value '{value_id}'");
207 let context = BTreeMap::from_iter(context);
208 self.storage
209 .increment_variable_value_usage(value_id, get_working_dir(), &context)
210 .await
211 }
212
213 #[instrument(skip_all)]
215 pub async fn delete_variable_value(&self, id: i32) -> Result<()> {
216 tracing::info!("Deleting variable value: {}", id);
217 self.storage.delete_variable_value(id).await
218 }
219}