celestial_pointing/commands/
mod.rs1pub 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}