@@ -3,16 +3,14 @@ use clap::{Parser, Subcommand};
33use serde:: { Deserialize , Serialize } ;
44use std:: collections:: HashMap ;
55use std:: fs:: { self , File } ;
6- use std:: io:: { Read , Write } ;
6+ use std:: io:: Write ;
77use std:: path:: PathBuf ;
88use std:: process:: { Command , Stdio } ;
99use tempfile:: NamedTempFile ;
1010
11- type CommandsMap = HashMap < String , String > ;
12-
1311#[ derive( Serialize , Deserialize , Debug ) ]
1412struct CommandStore {
15- commands : CommandsMap ,
13+ commands : HashMap < String , String > ,
1614}
1715
1816impl CommandStore {
@@ -36,11 +34,11 @@ impl CommandStore {
3634 fn save ( & self , path : & PathBuf ) -> Result < ( ) > {
3735 // Ensure parent directory exists
3836 if let Some ( parent) = path. parent ( ) {
39- fs:: create_dir_all ( parent) . context ( "Failed to create directory for commands file " ) ?;
37+ fs:: create_dir_all ( parent) . context ( "Failed to create directory" ) ?;
4038 }
4139
4240 let file = File :: create ( path) . context ( "Failed to create commands file" ) ?;
43- serde_json:: to_writer_pretty ( file, self ) . context ( "Failed to write commands to file " ) ?;
41+ serde_json:: to_writer_pretty ( file, self ) . context ( "Failed to write commands" ) ?;
4442 Ok ( ( ) )
4543 }
4644}
@@ -88,7 +86,7 @@ enum Commands {
8886
8987
9088fn get_commands_file ( ) -> Result < PathBuf > {
91- let mut path = dirs:: config_dir ( ) . context ( "Could not determine home directory" ) ?;
89+ let mut path = dirs:: config_dir ( ) . context ( "Could not determine config directory" ) ?;
9290 path. push ( "keepc" ) ;
9391 Ok ( path. join ( "commands.json" ) )
9492}
@@ -116,7 +114,7 @@ fn new_command(command: Option<String>, description: Option<String>) -> Result<(
116114 return Err ( anyhow:: anyhow!( "Command cannot be empty" ) ) ;
117115 }
118116
119- // Get description from user if not provided
117+ // Get description from user if provided
120118 let description = match description {
121119 Some ( desc) => desc,
122120 None => {
@@ -191,7 +189,6 @@ fn delete_command(command: String) -> Result<()> {
191189 println ! ( "No commands found matching '{}'" , command) ;
192190 } ,
193191 _ => {
194- // Multiple matches, ask user which one to delete
195192 println ! ( "Found {} matching commands:" , matching_commands. len( ) ) ;
196193 for ( i, cmd) in matching_commands. iter ( ) . enumerate ( ) {
197194 println ! ( "[{}] \x1b [34m{}\x1b [0m: {}" ,
@@ -224,24 +221,14 @@ fn edit_commands() -> Result<()> {
224221 let path = get_commands_file ( ) ?;
225222 let mut store = CommandStore :: load ( & path) ?;
226223
227- // Create a temporary file
224+ // Create and write commands a temporary file
228225 let mut temp_file = NamedTempFile :: new ( ) . context ( "Failed to create temporary file" ) ?;
229-
230- // Write commands to the temporary file
231226 for ( cmd, desc) in & store. commands {
232- writeln ! ( temp_file, "{}:::{}" , cmd, desc) . context ( "Failed to write to temporary file" ) ?;
227+ writeln ! ( temp_file, "{}:::{}" , cmd, desc) . context ( "Failed to write to temp file" ) ?;
233228 }
234-
235- // Get the path to the temporary file
236229 let temp_path = temp_file. path ( ) . to_owned ( ) ;
237-
238- // Close the file to ensure all data is written
239- temp_file. flush ( ) . context ( "Failed to flush temporary file" ) ?;
240-
241- // Determine which editor to use
230+ temp_file. flush ( ) . context ( "Failed to flush temp file" ) ?;
242231 let editor = std:: env:: var ( "EDITOR" ) . unwrap_or_else ( |_| "nano" . to_string ( ) ) ;
243-
244- // Open the editor
245232 let status = Command :: new ( & editor)
246233 . arg ( & temp_path)
247234 . status ( )
@@ -250,23 +237,17 @@ fn edit_commands() -> Result<()> {
250237 if !status. success ( ) {
251238 return Err ( anyhow:: anyhow!( "Editor exited with non-zero status" ) ) ;
252239 }
253-
254- // Read the edited file
255240 let mut content = String :: new ( ) ;
256- File :: open ( & temp_path)
257- . context ( "Failed to open temporary file after editing" ) ?
258- . read_to_string ( & mut content)
259- . context ( "Failed to read temporary file after editing" ) ?;
260-
261- // Parse the edited content
241+ std:: io:: Read :: read_to_string (
242+ & mut File :: open ( & temp_path) . context ( "Failed to open temporary file after editing" ) ?,
243+ & mut content
244+ ) . context ( "Failed to read temporary file after editing" ) ?;
262245 let mut new_commands = HashMap :: new ( ) ;
263246 for line in content. lines ( ) {
264247 if let Some ( ( cmd, desc) ) = line. split_once ( ":::" ) {
265248 new_commands. insert ( cmd. trim ( ) . to_string ( ) , desc. trim ( ) . to_string ( ) ) ;
266249 }
267250 }
268-
269- // Update and save the commands
270251 store. commands = new_commands;
271252 store. save ( & path) ?;
272253
@@ -275,39 +256,57 @@ fn edit_commands() -> Result<()> {
275256}
276257
277258fn execute_command ( command : String ) -> Result < ( ) > {
259+ use std:: io:: { self , BufRead } ;
260+
278261 let path = get_commands_file ( ) ?;
279262 let store = CommandStore :: load ( & path) ?;
280263
281- if let Some ( _cmd) = store. commands . get ( & command) {
282- println ! ( "Executing: {}" , command) ;
283-
284- // Determine the shell to use
285- let shell = if cfg ! ( target_os = "windows" ) {
286- "cmd"
287- } else {
288- "sh"
289- } ;
290-
291- let shell_arg = if cfg ! ( target_os = "windows" ) {
292- "/C"
293- } else {
294- "-c"
295- } ;
296-
297- let status = Command :: new ( shell)
298- . arg ( shell_arg)
299- . arg ( & command)
300- . stdin ( Stdio :: inherit ( ) )
301- . stdout ( Stdio :: inherit ( ) )
302- . stderr ( Stdio :: inherit ( ) )
303- . status ( )
304- . context ( format ! ( "Failed to execute command: {}" , command) ) ?;
305-
306- if !status. success ( ) {
307- return Err ( anyhow:: anyhow!( "Command exited with non-zero status" ) ) ;
264+ // Find all commands that match the pattern
265+ let pattern = command. to_lowercase ( ) ;
266+ let matching_commands: Vec < String > = store. commands . keys ( )
267+ . filter ( |cmd| cmd. to_lowercase ( ) . contains ( & pattern) )
268+ . cloned ( )
269+ . collect ( ) ;
270+ match matching_commands. len ( ) {
271+ 0 => {
272+ println ! ( "No commands found matching '{}'" , command) ;
273+ } ,
274+ _ => {
275+ println ! ( "Found {} matching commands:" , matching_commands. len( ) ) ;
276+ for ( i, cmd) in matching_commands. iter ( ) . enumerate ( ) {
277+ println ! ( "[{}] \x1b [34m{}\x1b [0m: {}" ,
278+ i + 1 ,
279+ cmd,
280+ store. commands. get( cmd) . unwrap_or( & String :: new( ) ) ) ;
281+ }
282+
283+ print ! ( "Enter a number to execute: " ) ;
284+ io:: stdout ( ) . flush ( ) ?;
285+
286+ let stdin = io:: stdin ( ) ;
287+ let mut line = String :: new ( ) ;
288+ stdin. lock ( ) . read_line ( & mut line) ?;
289+
290+ if let Ok ( choice) = line. trim ( ) . parse :: < usize > ( ) {
291+ if choice <= matching_commands. len ( ) {
292+ let cmd_to_execute = & matching_commands[ choice - 1 ] ;
293+ println ! ( "Executing: {}" , cmd_to_execute) ;
294+ let ( shell, shell_arg) = if cfg ! ( target_os = "windows" ) {
295+ ( "cmd" , "/C" )
296+ } else {
297+ ( "sh" , "-c" )
298+ } ;
299+ let _status = Command :: new ( shell)
300+ . arg ( shell_arg)
301+ . arg ( & cmd_to_execute)
302+ . stdin ( Stdio :: inherit ( ) )
303+ . stdout ( Stdio :: inherit ( ) )
304+ . stderr ( Stdio :: inherit ( ) )
305+ . status ( )
306+ . context ( format ! ( "Failed to execute: {}" , cmd_to_execute) ) ?;
307+ }
308+ } ;
308309 }
309- } else {
310- println ! ( "Command not found: {}" , command) ;
311310 }
312311 Ok ( ( ) )
313312}
0 commit comments