fur_cli/commands/
run.rs

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
8/// Run an .frs script:
9/// - Parse into Thread (in-memory)
10/// - Execute inline commands (tree, timeline, status)
11/// - Persist once at first `store`
12/// - Ignore later `store`s
13pub 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        // --- Commit point
28        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        // --- Status
45        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        // --- Timeline
56        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        // --- Tree
82        if line.starts_with("tree") {
83            let args = TreeArgs { conversation_override: None };
84            with_ephemeral(stored, &conversation, |tid_override| {
85                let mut args = args.clone();
86                args.conversation_override = tid_override;
87                tree::run_tree(args);
88            });
89            continue;
90        }
91
92        // Default: skip (jots already parsed by parser::parse_frs)
93    }
94
95    if !stored {
96        eprintln!("{}", "⚠️ Script finished without a `store` — nothing persisted.".yellow());
97    }
98}
99
100/// Run a command either with an ephemeral conversation (if not stored) or directly.
101fn with_ephemeral<F>(stored: bool, conversation: &crate::frs::ast::Thread, mut f: F)
102where
103    F: FnMut(Option<String>),
104{
105    if !stored {
106        let tid = crate::frs::persist::persist_ephemeral(conversation);
107        f(Some(tid.clone()));
108        crate::frs::persist::cleanup_ephemeral(&tid);
109    } else {
110        f(None);
111    }
112}