fur_cli/commands/
thread.rs1use std::fs;
2use std::path::Path;
3use serde_json::{Value, json};
4use clap::Parser;
5
6#[derive(Parser)]
8pub struct ThreadArgs {
9 pub id: Option<String>,
11
12 #[arg(long)]
14 pub view: bool,
15}
16
17pub fn run_thread(args: ThreadArgs) {
19 let fur_dir = Path::new(".fur");
20 let index_path = fur_dir.join("index.json");
21
22 if !index_path.exists() {
23 eprintln!("🚨 .fur/ not found. Run `fur new` first.");
24 return;
25 }
26
27 let mut index: Value =
28 serde_json::from_str(&fs::read_to_string(&index_path).unwrap()).unwrap();
29
30 if args.view || args.id.is_none() {
34 println!("📇 Threads in .fur:");
35
36 let empty_vec: Vec<Value> = Vec::new();
37 let threads = index["threads"].as_array().unwrap_or(&empty_vec);
38 let active = index["active_thread"].as_str().unwrap_or("");
39
40 for tid in threads {
41 if let Some(tid_str) = tid.as_str() {
42 let thread_path = fur_dir.join("threads").join(format!("{}.json", tid_str));
43 if let Ok(content) = fs::read_to_string(thread_path) {
44 if let Ok(thread_json) = serde_json::from_str::<Value>(&content) {
45 let title = thread_json["title"].as_str().unwrap_or("Untitled");
46 let marker = if tid_str == active { "⭐ Active" } else { " " };
47 println!("{marker} {tid_str:.8} \"{title}\"");
48 }
49 }
50 }
51 }
52 return;
53 }
54
55 if let Some(tid) = args.id {
59 let empty_vec: Vec<Value> = Vec::new();
60 let threads: Vec<String> = index["threads"]
61 .as_array()
62 .unwrap_or(&empty_vec)
63 .iter()
64 .filter_map(|t| t.as_str().map(|s| s.to_string()))
65 .collect();
66
67 let mut found = threads.iter().find(|&s| s == &tid);
69
70 if found.is_none() {
72 let matches: Vec<&String> = threads
73 .iter()
74 .filter(|s| s.starts_with(&tid))
75 .collect();
76
77 if matches.len() == 1 {
78 found = Some(matches[0]);
79 } else if matches.len() > 1 {
80 eprintln!("❌ Ambiguous prefix '{}'. Matches: {:?}", tid, matches);
81 return;
82 }
83 }
84
85 let tid_full = match found {
86 Some(s) => s,
87 None => {
88 eprintln!("❌ Thread not found: {}", tid);
89 return;
90 }
91 };
92
93 index["active_thread"] = json!(tid_full);
95 index["current_message"] = serde_json::Value::Null;
96 fs::write(&index_path, serde_json::to_string_pretty(&index).unwrap()).unwrap();
97
98 let thread_path = fur_dir.join("threads").join(format!("{}.json", tid_full));
99 let content = fs::read_to_string(thread_path).unwrap();
100 let thread_json: Value = serde_json::from_str(&content).unwrap();
101 let title = thread_json["title"].as_str().unwrap_or("Untitled");
102
103 println!("✔️ Switched active thread to {} \"{}\"", &tid_full[..8], title);
104 }
105}