1use std::fs;
2use colored::*;
3use crate::frs::{parser, persist_frs};
4use crate::commands::{timeline, tree};
5use crate::commands::timeline::TimelineArgs;
6use crate::commands::tree::TreeArgs;
7
8pub fn run_frs(path: &str) {
14 let raw = fs::read_to_string(path)
15 .unwrap_or_else(|_| panic!("❌ Could not read .frs file: {}", path));
16
17 let lines: Vec<String> = raw
18 .lines()
19 .map(|l| l.trim().to_string())
20 .filter(|l| !l.is_empty() && !l.starts_with('#'))
21 .collect();
22
23 let conversation = parser::parse_frs(path);
24 let mut stored = false;
25
26 for (lineno, line) in lines.iter().enumerate() {
27 if line == "store" {
29 if !stored {
30 let tid = persist_frs(&conversation);
31 println!("✔️ Thread persisted at line {} → {}", lineno + 1, &tid[..8]);
32 stored = true;
33 } else {
34 eprintln!(
35 "{}",
36 format!("⚠️ Ignoring extra `store` at line {} — already persisted", lineno + 1)
37 .yellow()
38 .bold()
39 );
40 }
41 continue;
42 }
43
44 if line.starts_with("status") {
46 with_ephemeral(stored, &conversation, |tid_override| {
47 let args = crate::commands::status::StatusArgs {
48 conversation_override: tid_override,
49 };
50 crate::commands::status::run_status(args);
51 });
52 continue;
53 }
54
55 if line.starts_with("timeline") {
57 let parts: Vec<&str> = line.split_whitespace().collect();
58 let mut args = TimelineArgs {
59 verbose: false,
60 contents: false,
61 out: None,
62 conversation_override: None,
63 };
64 for (i, p) in parts.iter().enumerate() {
65 if *p == "--out" {
66 args.out = parts.get(i + 1).map(|s| s.to_string());
67 }
68 if *p == "--contents" {
69 args.contents = true;
70 }
71 }
72
73 with_ephemeral(stored, &conversation, |tid_override| {
74 let mut args = args.clone();
75 args.conversation_override = tid_override;
76 timeline::run_timeline(args);
77 });
78 continue;
79 }
80
81 if line.starts_with("printed") {
83 let parts: Vec<&str> = line.split_whitespace().collect();
84 let mut out: Option<String> = None;
85 let mut verbose = false;
86
87 for (i, p) in parts.iter().enumerate() {
88 if *p == "--out" {
89 out = parts.get(i + 1).map(|s| s.to_string());
90 }
91 if *p == "--verbose" || *p == "-v" {
92 verbose = true;
93 }
94 }
95
96 with_ephemeral(stored, &conversation, |tid_override| {
97 let index_path = std::path::Path::new(".fur").join("index.json");
99 let mut index_json: serde_json::Value =
100 serde_json::from_str(&std::fs::read_to_string(&index_path).unwrap()).unwrap();
101
102 let original_active = index_json["active_thread"].as_str().map(|s| s.to_string());
103
104 if let Some(tid) = &tid_override {
105 index_json["active_thread"] = tid.clone().into();
106 std::fs::write(&index_path, serde_json::to_string_pretty(&index_json).unwrap()).unwrap();
107 }
108
109 crate::commands::printed::run_printed(out.clone(), verbose);
111
112 if let Some(orig) = original_active {
114 index_json["active_thread"] = orig.into();
115 std::fs::write(&index_path, serde_json::to_string_pretty(&index_json).unwrap()).unwrap();
116 }
117 });
118
119 continue;
120 }
121
122
123 if line.starts_with("tree") {
125 let args = TreeArgs { conversation_override: None };
126 with_ephemeral(stored, &conversation, |tid_override| {
127 let mut args = args.clone();
128 args.conversation_override = tid_override;
129 tree::run_tree(args);
130 });
131 continue;
132 }
133
134 }
136
137 if !stored {
138 eprintln!("{}", "⚠️ Script finished without a `store` — nothing persisted.".yellow());
139 }
140}
141
142fn with_ephemeral<F>(stored: bool, conversation: &crate::frs::ast::Thread, mut f: F)
144where
145 F: FnMut(Option<String>),
146{
147 if !stored {
148 let tid = crate::frs::persist::persist_ephemeral(conversation);
149 f(Some(tid.clone()));
150 crate::frs::persist::cleanup_ephemeral(&tid);
151 } else {
152 f(None);
153 }
154}