Skip to main content

celestial_pointing/commands/
mod.rs

1pub mod adjust;
2pub mod apply;
3pub mod clist;
4pub mod correct;
5pub mod fauto;
6pub mod fit;
7pub mod fix;
8pub mod gdec;
9pub mod gdist;
10pub mod gha;
11pub mod ghyst;
12pub mod gmap;
13pub mod gscat;
14pub mod help;
15pub mod indat;
16pub mod inmod;
17pub mod lose;
18pub mod lst;
19pub mod mask;
20pub mod mvet;
21pub mod optimal;
22pub mod outl;
23pub mod outmod;
24pub mod parallel;
25pub mod predict;
26pub mod reset;
27pub mod show;
28pub mod slist;
29pub mod use_term;
30
31use crate::error::Result;
32use crate::session::Session;
33
34pub enum CommandOutput {
35    Text(String),
36    Table {
37        headers: Vec<String>,
38        rows: Vec<Vec<String>>,
39    },
40    FitDisplay(FitDisplay),
41    None,
42}
43
44pub struct FitDisplay {
45    pub term_names: Vec<String>,
46    pub coefficients: Vec<f64>,
47    pub sigma: Vec<f64>,
48    pub sky_rms: f64,
49}
50
51pub trait Command {
52    fn name(&self) -> &str;
53    fn description(&self) -> &str;
54    fn execute(&self, session: &mut Session, args: &[&str]) -> Result<CommandOutput>;
55}
56
57pub fn dispatch(session: &mut Session, input: &str) -> Result<CommandOutput> {
58    let parts: Vec<&str> = input.split_whitespace().collect();
59    if parts.is_empty() {
60        return Ok(CommandOutput::None);
61    }
62    let cmd_name = parts[0].to_uppercase();
63    let args = &parts[1..];
64    match cmd_name.as_str() {
65        "ADJUST" => adjust::Adjust.execute(session, args),
66        "APPLY" => apply::Apply.execute(session, args),
67        "CHAIN" => parallel::Chain.execute(session, args),
68        "CLIST" => clist::Clist.execute(session, args),
69        "CORRECT" => correct::Correct.execute(session, args),
70        "FAUTO" => fauto::Fauto.execute(session, args),
71        "FIT" => fit::Fit.execute(session, args),
72        "FIX" => fix::Fix.execute(session, args),
73        "GDEC" => gdec::Gdec.execute(session, args),
74        "GDIST" => gdist::Gdist.execute(session, args),
75        "GHA" => gha::Gha.execute(session, args),
76        "GHYST" => ghyst::Ghyst.execute(session, args),
77        "GMAP" => gmap::Gmap.execute(session, args),
78        "GSCAT" => gscat::Gscat.execute(session, args),
79        "HELP" => help::Help.execute(session, args),
80        "INDAT" => indat::Indat.execute(session, args),
81        "INMOD" => inmod::Inmod.execute(session, args),
82        "LOSE" => lose::Lose.execute(session, args),
83        "LST" => lst::Lst.execute(session, args),
84        "MASK" => mask::Mask.execute(session, args),
85        "MVET" => mvet::Mvet.execute(session, args),
86        "OPTIMAL" => optimal::Optimal.execute(session, args),
87        "OUTL" => outl::Outl.execute(session, args),
88        "OUTMOD" => outmod::Outmod.execute(session, args),
89        "PARALLEL" => parallel::Parallel.execute(session, args),
90        "PREDICT" => predict::Predict.execute(session, args),
91        "QUIT" => Ok(CommandOutput::Text("Use Ctrl-D to exit".to_string())),
92        "RESET" => reset::Reset.execute(session, args),
93        "SHOW" => show::Show.execute(session, args),
94        "SLIST" => slist::Slist.execute(session, args),
95        "UNFIX" => fix::Unfix.execute(session, args),
96        "UNMASK" => mask::Unmask.execute(session, args),
97        "USE" => use_term::Use.execute(session, args),
98        _ => Err(crate::error::Error::Parse(format!(
99            "unknown command: {}",
100            parts[0]
101        ))),
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::session::Session;
109
110    #[test]
111    fn dispatch_use_adds_terms() {
112        let mut session = Session::new();
113        let result = dispatch(&mut session, "USE IH ID").unwrap();
114        assert_eq!(session.model.term_count(), 2);
115        assert_eq!(session.model.term_names(), vec!["IH", "ID"]);
116        match result {
117            CommandOutput::Text(s) => assert!(s.contains("IH")),
118            _ => panic!("expected Text output"),
119        }
120    }
121
122    #[test]
123    fn dispatch_lose_removes_term() {
124        let mut session = Session::new();
125        session.model.add_term("IH").unwrap();
126        session.model.add_term("ID").unwrap();
127        dispatch(&mut session, "LOSE IH").unwrap();
128        assert_eq!(session.model.term_count(), 1);
129        assert_eq!(session.model.term_names(), vec!["ID"]);
130    }
131
132    #[test]
133    fn dispatch_unknown_command_errors() {
134        let mut session = Session::new();
135        let result = dispatch(&mut session, "ZZZNOTACMD");
136        assert!(result.is_err());
137    }
138
139    #[test]
140    fn dispatch_fit_no_observations_errors() {
141        let mut session = Session::new();
142        session.model.add_term("IH").unwrap();
143        let result = dispatch(&mut session, "FIT");
144        assert!(result.is_err());
145    }
146
147    #[test]
148    fn dispatch_empty_input_returns_none() {
149        let mut session = Session::new();
150        let result = dispatch(&mut session, "").unwrap();
151        matches!(result, CommandOutput::None);
152    }
153
154    #[test]
155    fn dispatch_case_insensitive() {
156        let mut session = Session::new();
157        let result = dispatch(&mut session, "use IH");
158        assert!(result.is_ok());
159        assert_eq!(session.model.term_count(), 1);
160    }
161}