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    /// Read the contents of the clipboard backup file
35    ReadClipboard,
36    /// Delete a block by name
37    Delete {
38        /// The name of the block to be deleted
39        name: String,
40    },
41    /// View (STDOUT) the contents of the block by name
42    View {
43        /// The name of the block to be used
44        name: String,
45    },
46    /// Copy the contents of a block to the system clipboard
47    Copy {
48        /// The name of the block to be used
49        name: String,
50    },
51}
52
53pub fn read_clipboard_backup() -> Result<()> {
54    let file_path = crate::get_clipboard_backup_file_path();
55    if !file_path.exists() {
56        println!("No clipboard backup file found at {}", file_path.display());
57        return Ok(());
58    }
59
60    let content = std::fs::read_to_string(&file_path)?;
61    if content.is_empty() {
62        println!("Clipboard backup file exists but is empty.");
63    } else {
64        println!("{}", content);
65    }
66    Ok(())
67}
68
69pub fn add_block(name: &str, content: &str) -> Result<()> {
70    let mut file = std::fs::OpenOptions::new()
71        .append(true)
72        .create(true)
73        .open(get_save_file_path())?;
74
75    writeln!(file, "# {}", name)?;
76    writeln!(file, "{}", content)?;
77    writeln!(file)?;
78
79    println!("Block '{}' added successfully.", name);
80    Ok(())
81}
82
83pub fn list_blocks() -> Result<()> {
84    let file = File::open(get_save_file_path())?;
85    let reader = BufReader::new(file);
86
87    for line in reader.lines() {
88        let line = line?;
89
90        if let Some(strip) = line.strip_prefix("# ") {
91            println!("{}", strip);
92        }
93    }
94
95    Ok(())
96}
97
98pub fn replace_from_backup() -> Result<()> {
99    let (backup_textareas, backup_textareas_titles) = load_textareas(get_save_backup_file_path())?;
100    save_textareas(
101        &backup_textareas,
102        &backup_textareas_titles,
103        get_save_file_path(),
104    )
105}
106
107pub fn view_block(name: &str) -> Result<()> {
108    let file = File::open(get_save_file_path())?;
109    let reader = BufReader::new(file);
110    let mut blocks = Vec::new();
111    let mut current_block = Vec::new();
112    let mut current_name = String::new();
113
114    for line in reader.lines() {
115        let line = line?;
116        if let Some(strip) = line.strip_prefix("# ") {
117            if !current_name.is_empty() {
118                blocks.push((current_name, current_block));
119                current_block = Vec::new();
120            }
121            current_name = strip.to_string();
122        } else {
123            current_block.push(line);
124        }
125    }
126
127    if !current_name.is_empty() {
128        blocks.push((current_name, current_block));
129    }
130
131    for (block_name, block_content) in blocks {
132        if block_name == name {
133            for line in block_content {
134                println!("{}", line);
135            }
136        }
137    }
138    Ok(())
139}
140
141pub fn copy_block(name: &str) -> Result<()> {
142    let file = File::open(get_save_file_path())?;
143    let reader = BufReader::new(file);
144    let mut blocks = Vec::new();
145    let mut current_block = Vec::new();
146    let mut current_name = String::new();
147    let mut matched_name: Option<String> = None;
148
149    for line in reader.lines() {
150        let line = line?;
151        if let Some(strip) = line.strip_prefix("# ") {
152            if !current_name.is_empty() {
153                blocks.push((current_name, current_block));
154                current_block = Vec::new();
155            }
156            current_name = strip.to_string();
157        } else {
158            current_block.push(line);
159        }
160    }
161
162    if !current_name.is_empty() {
163        blocks.push((current_name, current_block));
164    }
165
166    for (block_name, block_content) in blocks {
167        if block_name == name {
168            let result_ctx = EditorClipboard::new();
169
170            if result_ctx.is_err() {
171                bail!("Failed to create clipboard context for copy block");
172            }
173
174            let mut ctx = result_ctx.unwrap();
175
176            let is_success = ctx.set_contents(block_content.join("\n"));
177
178            if is_success.is_err() {
179                bail!(format!(
180                    "Failed to copy contents of block {} to system clipboard",
181                    block_name
182                ));
183            }
184            matched_name = Some(block_name);
185            break;
186        }
187    }
188    match matched_name {
189        Some(name) => println!("Successfully copied contents from block {}", name),
190        None => println!("Didn't find the block. Please try again. You can use `thoth list` to find the name of all blocks")
191    };
192
193    Ok(())
194}
195
196pub fn delete_block(name: &str) -> Result<()> {
197    let file = File::open(get_save_file_path())?;
198    let reader = BufReader::new(file);
199    let mut blocks = Vec::new();
200    let mut current_block = Vec::new();
201    let mut current_name = String::new();
202
203    for line in reader.lines() {
204        let line = line?;
205        if let Some(strip) = line.strip_prefix("# ") {
206            if !current_name.is_empty() {
207                blocks.push((current_name, current_block));
208                current_block = Vec::new();
209            }
210            current_name = strip.to_string();
211        } else {
212            current_block.push(line);
213        }
214    }
215
216    if !current_name.is_empty() {
217        blocks.push((current_name, current_block));
218    }
219
220    let mut file = File::create(get_save_file_path())?;
221    let mut deleted = false;
222
223    for (block_name, block_content) in blocks {
224        if block_name != name {
225            writeln!(file, "# {}", block_name)?;
226            for line in block_content {
227                writeln!(file, "{}", line)?;
228            }
229            writeln!(file)?;
230        } else {
231            deleted = true;
232        }
233    }
234
235    if deleted {
236        println!("Block '{}' deleted successfully.", name);
237    } else {
238        println!("Block '{}' not found.", name);
239    }
240
241    Ok(())
242}