fur_cli/commands/
sweep.rs1use std::fs;
2use std::path::PathBuf;
3use chrono::{DateTime, Local};
4use serde_json::{Value, json};
5use clap::Parser;
6use walkdir::WalkDir;
7use dirs;
8use colored::*;
9
10#[derive(Parser)]
12pub struct SweepArgs {
13 #[arg(long)]
15 pub dir: Option<String>,
16
17 #[arg(long, default_value_t = 5)]
19 pub depth: usize,
20
21 #[arg(long)]
23 pub json: bool,
24
25 #[arg(long)]
27 pub silent: bool,
28}
29
30pub fn run_sweep(args: SweepArgs) {
31 let start_dir = args.dir
32 .clone()
33 .map(PathBuf::from)
34 .unwrap_or_else(|| dirs::home_dir().unwrap_or_else(|| PathBuf::from("/")));
35
36 let mut results = Vec::new();
37
38 for entry in WalkDir::new(&start_dir)
39 .follow_links(false)
40 .max_depth(args.depth)
41 .into_iter()
42 .filter_map(|e| e.ok())
43 {
44 let path = entry.path();
45 if path.is_dir() && path.ends_with(".fur") {
46 let index_path = path.join("index.json");
47 if index_path.exists() {
48 if let Ok(content) = fs::read_to_string(&index_path) {
49 if let Ok(json) = serde_json::from_str::<Value>(&content) {
50 let threads_vec = json["threads"]
51 .as_array()
52 .cloned()
53 .unwrap_or_else(|| Vec::new());
54 let active = json["active_thread"].as_str().unwrap_or("-");
55 let msg_count = threads_vec.iter().filter_map(|tid| {
56 tid.as_str().map(|t| {
57 let tp = path.join("threads").join(format!("{}.json", t));
58 if let Ok(tc) = fs::read_to_string(tp) {
59 serde_json::from_str::<Value>(&tc)
60 .ok()
61 .and_then(|v| v["messages"].as_array().map(|a| a.len()))
62 .unwrap_or(0)
63 } else { 0 }
64 })
65 }).sum::<usize>();
66
67 let modified = fs::metadata(&index_path)
68 .and_then(|m| m.modified())
69 .ok()
70 .map(|t| {
71 let dt: DateTime<Local> = DateTime::from(t);
72 dt.format("%Y-%m-%d %H:%M").to_string()
73 })
74 .unwrap_or("-".into());
75
76 results.push(json!({
77 "path": path.parent().unwrap_or(path).display().to_string(),
78 "active_thread": active,
79 "threads": threads_vec.len(),
80 "messages": msg_count,
81 "modified": modified
82 }));
83 }
84 }
85 }
86 }
87 }
88
89 let mut total_projects = 0;
90 let mut total_threads = 0;
91 let mut total_messages = 0;
92
93 if args.json {
94 println!("{}", serde_json::to_string_pretty(&results).unwrap());
95 } else {
96 println!("{}", "=== 🧭 FUR Project Sweep ===\n".bold().bright_cyan());
97 for r in &results {
98 total_projects += 1;
99 total_threads += r["threads"].as_u64().unwrap_or(0);
100 total_messages += r["messages"].as_u64().unwrap_or(0);
101
102 let path = r["path"].as_str().unwrap_or("-").bright_yellow().bold();
103 let active = r["active_thread"].as_str().unwrap_or("-").bright_cyan();
104 let threads = r["threads"].as_u64().unwrap_or(0).to_string().green();
105 let messages = r["messages"].as_u64().unwrap_or(0).to_string().blue();
106 let modified = r["modified"].as_str().unwrap_or("-").bright_black();
107
108 println!(
109 "{}\n {} {}\n {} {}\n {} {}\n {} {}\n",
110 path,
111 "Active Thread :".dimmed(),
112 active,
113 "Threads :".dimmed(),
114 threads,
115 "Messages :".dimmed(),
116 messages,
117 "Last Modified :".dimmed(),
118 modified,
119 );
120 }
121
122 println!("{}", "─".repeat(50).dimmed());
124 println!(
125 "🌍 Found {} {} | {} {} | {} {}\n",
126 total_projects.to_string().bold().bright_yellow(),
127 if total_projects == 1 { "project" } else { "projects" },
128 total_threads.to_string().bold().green(),
129 if total_threads == 1 { "thread" } else { "threads" },
130 total_messages.to_string().bold().blue(),
131 if total_messages == 1 { "message" } else { "messages" },
132 );
133 }
134}
135
136