intelli_shell/model/
command.rs1use std::{collections::HashSet, fmt::Display};
2
3use chrono::{DateTime, Utc};
4use clap::ValueEnum;
5use enum_cycling::EnumCycle;
6use serde::Deserialize;
7use uuid::Uuid;
8
9use crate::utils::{extract_tags_from_description, flatten_str};
10
11pub const CATEGORY_USER: &str = "user";
13
14pub const CATEGORY_WORKSPACE: &str = "workspace";
16
17pub const SOURCE_USER: &str = "user";
19
20pub const SOURCE_TLDR: &str = "tldr";
22
23pub const SOURCE_IMPORT: &str = "import";
25
26pub const SOURCE_WORKSPACE: &str = "workspace";
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, ValueEnum, EnumCycle, strum::Display)]
30#[cfg_attr(test, derive(strum::EnumIter))]
31#[serde(rename_all = "snake_case")]
32#[strum(serialize_all = "snake_case")]
33pub enum SearchMode {
35 #[default]
37 Auto,
38 Fuzzy,
40 Regex,
42 Exact,
44 Relaxed,
49}
50
51#[derive(Default, Clone)]
53#[cfg_attr(debug_assertions, derive(Debug))]
54pub struct SearchCommandsFilter {
55 pub category: Option<Vec<String>>,
57 pub source: Option<String>,
59 pub tags: Option<Vec<String>>,
61 pub search_mode: SearchMode,
63 pub search_term: Option<String>,
68}
69impl SearchCommandsFilter {
70 pub fn cleaned(self) -> Self {
72 let SearchCommandsFilter {
73 category,
74 source,
75 tags,
76 search_mode,
77 search_term,
78 } = self;
79 Self {
80 category: category
81 .map(|v| {
82 let mut final_vec: Vec<String> = Vec::with_capacity(v.len());
83 let mut seen: HashSet<&str> = HashSet::with_capacity(v.len());
84 for t in &v {
85 let t = t.trim();
86 if !t.is_empty() && seen.insert(t) {
87 final_vec.push(t.to_string());
88 }
89 }
90 final_vec
91 })
92 .filter(|t| !t.is_empty()),
93 source: source.map(|t| t.trim().to_string()).filter(|s| !s.is_empty()),
94 tags: tags
95 .map(|v| {
96 let mut final_vec: Vec<String> = Vec::with_capacity(v.len());
97 let mut seen: HashSet<&str> = HashSet::with_capacity(v.len());
98 for t in &v {
99 let t = t.trim();
100 if !t.is_empty() && seen.insert(t) {
101 final_vec.push(t.to_string());
102 }
103 }
104 final_vec
105 })
106 .filter(|t| !t.is_empty()),
107 search_mode,
108 search_term: search_term.map(|t| t.trim().to_string()).filter(|t| !t.is_empty()),
109 }
110 }
111}
112
113#[derive(Clone)]
114#[cfg_attr(debug_assertions, derive(Debug))]
115#[cfg_attr(test, derive(Default))]
116pub struct Command {
117 pub id: Uuid,
119 pub category: String,
121 pub source: String,
123 pub alias: Option<String>,
125 pub cmd: String,
127 pub flat_cmd: String,
129 pub description: Option<String>,
131 pub flat_description: Option<String>,
133 pub tags: Option<Vec<String>>,
135 pub created_at: DateTime<Utc>,
137 pub updated_at: Option<DateTime<Utc>>,
139}
140
141impl Command {
142 pub fn new(category: impl Into<String>, source: impl Into<String>, cmd: impl Into<String>) -> Self {
144 let cmd = cmd.into();
145 Self {
146 id: Uuid::now_v7(),
147 category: category.into(),
148 source: source.into(),
149 alias: None,
150 flat_cmd: flatten_str(&cmd),
151 cmd,
152 description: None,
153 flat_description: None,
154 tags: None,
155 created_at: Utc::now(),
156 updated_at: None,
157 }
158 }
159
160 pub fn with_alias(mut self, alias: Option<String>) -> Self {
162 self.alias = alias.filter(|a| !a.trim().is_empty());
163 self
164 }
165
166 pub fn with_cmd(mut self, cmd: String) -> Self {
168 self.flat_cmd = flatten_str(&cmd);
169 self.cmd = cmd;
170 self
171 }
172
173 pub fn with_description(mut self, description: Option<String>) -> Self {
175 let description = description.filter(|d| !d.trim().is_empty());
176 self.tags = extract_tags_from_description(description.as_deref());
177 self.flat_description = description.as_ref().map(flatten_str);
178 self.description = description;
179 self
180 }
181
182 #[cfg(test)]
183 pub fn with_tags(mut self, tags: Option<Vec<String>>) -> Self {
185 self.tags = tags.filter(|t| !t.is_empty());
186 self
187 }
188}
189
190impl Display for Command {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 write!(f, "{}", self.cmd)
193 }
194}