Skip to content

Commit 97290aa

Browse files
committed
Update V0.1.1
1 parent 8957f1c commit 97290aa

File tree

3 files changed

+99
-71
lines changed

3 files changed

+99
-71
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "keepc"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
edition = "2024"
55
description = "A utility to save and manage useful commands with descriptions"
66

README.md

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,43 @@
11
## keepc
2-
Keep Command or Keepc is a meta cli program inspired by [keep](https://github.com/OrkoHunter/keep). Keepc is coded in Rust.
2+
Keep Command or Keepc is a meta cli program designed to keep important commands that are difficult to remember. Keepc is coded in The Rust Programming Language.
3+
4+
## How to install
5+
Download the [keepc binary](https://github.com/nickcat325/keepc/releases) and move it to /home/$USER/.local/bin/keepc
36

47
## Keepc Commands
5-
new Add a new command
6-
list List all saved commands
7-
grep Search for commands matching a pattern
8-
rm Delete a saved command
9-
edit Edit commands in a text editor
10-
run Execute a saved command
11-
help Print this message or the help of the given subcommand(s)
8+
| Command | Description |
9+
| ------- | ----------- |
10+
| new | Add a new command |
11+
| list | List all saved commands |
12+
| grep | Search for commands matching a pattern |
13+
| rm | Delete a saved command |
14+
| edit | Edit commands in a text editor |
15+
| run | Execute a saved command |
16+
| help | Print the list of cammands or the help of the given subcommands |
17+
18+
## How to Build
19+
`git clone https://github.com/nickcat325/keepc.git`
20+
21+
`cd keepc`
22+
23+
`cargo build`
24+
25+
`cargo run`
26+
27+
The keepc binary will be located at keepc/target/debug/keepc.
28+
29+
## How to Contribute
30+
Create a pull request.
31+
32+
[Issue tracker](https://github.com/nickcat325/keepc/issues).
33+
34+
## TODO
35+
Add other ways to run existing commands. For example, list/ls, new/add, run/execute....
1236

1337
## License
14-
Distributed under the [GPL-3.0 License](LICENSE).
38+
Distributed under the [GPL-3.0 License](LICENSE).
39+
40+
## Credits
41+
[Rust](https://www.rust-lang.org/)
42+
43+
Inspired by [keep](https://github.com/OrkoHunter/keep/).

src/main.rs

Lines changed: 60 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@ use clap::{Parser, Subcommand};
33
use serde::{Deserialize, Serialize};
44
use std::collections::HashMap;
55
use std::fs::{self, File};
6-
use std::io::{Read, Write};
6+
use std::io::Write;
77
use std::path::PathBuf;
88
use std::process::{Command, Stdio};
99
use tempfile::NamedTempFile;
1010

11-
type CommandsMap = HashMap<String, String>;
12-
1311
#[derive(Serialize, Deserialize, Debug)]
1412
struct CommandStore {
15-
commands: CommandsMap,
13+
commands: HashMap<String, String>,
1614
}
1715

1816
impl 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

9088
fn 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

277258
fn 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

Comments
 (0)