thoth_cli/
cli.rs

1use crate::{get_save_backup_file_path, load_textareas, save_textareas, EditorClipboard};
2use anyhow::{bail, Result};
3use std::{
4    fs::File,
5    io::{BufRead, BufReader, Write},
6};
7
8use std::env;
9
10use clap::{Parser, Subcommand};
11
12use crate::get_save_file_path;
13#[derive(Parser)]
14#[command(author = env!("CARGO_PKG_AUTHORS"), version = env!("CARGO_PKG_VERSION"), about, long_about = None, rename_all = "snake_case")]
15pub struct Cli {
16    #[command(subcommand)]
17    pub command: Option<Commands>,
18}
19
20#[derive(Subcommand)]
21#[command(rename_all = "snake_case")]
22pub enum Commands {
23    /// Add a new block to the scratchpad
24    Add {
25        /// Name of the block to be added
26        name: String,
27        /// Contents to be associated with the named block
28        content: Option<String>,
29    },
30    /// List all of the blocks within your thoth scratchpad
31    List,
32    /// Load backup file as the main thoth markdown file
33    LoadBackup,
34    /// Delete a block by name
35    Delete {
36        /// The name of the block to be deleted
37        name: String,
38    },
39    /// View (STDOUT) the contents of the block by name
40    View {
41        /// The name of the block to be used
42        name: String,
43    },
44    /// Copy the contents of a block to the system clipboard
45    Copy {
46        /// The name of the block to be used
47        name: String,
48    },
49}
50
51pub fn add_block(name: &str, content: &str) -> Result<()> {
52    let mut file = std::fs::OpenOptions::new()
53        .append(true)
54        .create(true)
55        .open(get_save_file_path())?;
56
57    writeln!(file, "# {}", name)?;
58    writeln!(file, "{}", content)?;
59    writeln!(file)?;
60
61    println!("Block '{}' added successfully.", name);
62    Ok(())
63}
64
65pub fn list_blocks() -> Result<()> {
66    let file = File::open(get_save_file_path())?;
67    let reader = BufReader::new(file);
68
69    for line in reader.lines() {
70        let line = line?;
71
72        if let Some(strip) = line.strip_prefix("# ") {
73            println!("{}", strip);
74        }
75    }
76
77    Ok(())
78}
79
80pub fn replace_from_backup() -> Result<()> {
81    let (backup_textareas, backup_textareas_titles) = load_textareas(get_save_backup_file_path())?;
82    save_textareas(
83        &backup_textareas,
84        &backup_textareas_titles,
85        get_save_file_path(),
86    )
87}
88
89pub fn view_block(name: &str) -> Result<()> {
90    let file = File::open(get_save_file_path())?;
91    let reader = BufReader::new(file);
92    let mut blocks = Vec::new();
93    let mut current_block = Vec::new();
94    let mut current_name = String::new();
95
96    for line in reader.lines() {
97        let line = line?;
98        if let Some(strip) = line.strip_prefix("# ") {
99            if !current_name.is_empty() {
100                blocks.push((current_name, current_block));
101                current_block = Vec::new();
102            }
103            current_name = strip.to_string();
104        } else {
105            current_block.push(line);
106        }
107    }
108
109    if !current_name.is_empty() {
110        blocks.push((current_name, current_block));
111    }
112
113    for (block_name, block_content) in blocks {
114        if block_name == name {
115            for line in block_content {
116                println!("{}", line);
117            }
118        }
119    }
120    Ok(())
121}
122
123pub fn copy_block(name: &str) -> Result<()> {
124    let file = File::open(get_save_file_path())?;
125    let reader = BufReader::new(file);
126    let mut blocks = Vec::new();
127    let mut current_block = Vec::new();
128    let mut current_name = String::new();
129    let mut matched_name: Option<String> = None;
130
131    for line in reader.lines() {
132        let line = line?;
133        if let Some(strip) = line.strip_prefix("# ") {
134            if !current_name.is_empty() {
135                blocks.push((current_name, current_block));
136                current_block = Vec::new();
137            }
138            current_name = strip.to_string();
139        } else {
140            current_block.push(line);
141        }
142    }
143
144    if !current_name.is_empty() {
145        blocks.push((current_name, current_block));
146    }
147
148    for (block_name, block_content) in blocks {
149        if block_name == name {
150            let result_ctx = EditorClipboard::new();
151
152            if result_ctx.is_err() {
153                bail!("Failed to create clipboard context for copy block");
154            }
155
156            let mut ctx = result_ctx.unwrap();
157
158            let is_success = ctx.set_contents(block_content.join("\n"));
159
160            if is_success.is_err() {
161                bail!(format!(
162                    "Failed to copy contents of block {} to system clipboard",
163                    block_name
164                ));
165            }
166            matched_name = Some(block_name);
167            break;
168        }
169    }
170    match matched_name {
171        Some(name) => println!("Successfully copied contents from block {}", name),
172        None => println!("Didn't find the block. Please try again. You can use `thoth list` to find the name of all blocks")
173    };
174
175    Ok(())
176}
177
178pub fn delete_block(name: &str) -> Result<()> {
179    let file = File::open(get_save_file_path())?;
180    let reader = BufReader::new(file);
181    let mut blocks = Vec::new();
182    let mut current_block = Vec::new();
183    let mut current_name = String::new();
184
185    for line in reader.lines() {
186        let line = line?;
187        if let Some(strip) = line.strip_prefix("# ") {
188            if !current_name.is_empty() {
189                blocks.push((current_name, current_block));
190                current_block = Vec::new();
191            }
192            current_name = strip.to_string();
193        } else {
194            current_block.push(line);
195        }
196    }
197
198    if !current_name.is_empty() {
199        blocks.push((current_name, current_block));
200    }
201
202    let mut file = File::create(get_save_file_path())?;
203    let mut deleted = false;
204
205    for (block_name, block_content) in blocks {
206        if block_name != name {
207            writeln!(file, "# {}", block_name)?;
208            for line in block_content {
209                writeln!(file, "{}", line)?;
210            }
211            writeln!(file)?;
212        } else {
213            deleted = true;
214        }
215    }
216
217    if deleted {
218        println!("Block '{}' deleted successfully.", name);
219    } else {
220        println!("Block '{}' not found.", name);
221    }
222
223    Ok(())
224}