celestial_pointing/commands/
mask.rs1use super::{Command, CommandOutput};
2use crate::error::{Error, Result};
3use crate::session::Session;
4
5pub struct Mask;
6pub struct Unmask;
7
8impl Command for Mask {
9 fn name(&self) -> &str {
10 "MASK"
11 }
12 fn description(&self) -> &str {
13 "Mask observations (exclude from fit)"
14 }
15
16 fn execute(&self, session: &mut Session, args: &[&str]) -> Result<CommandOutput> {
17 if args.is_empty() {
18 return Err(Error::Parse("MASK requires observation numbers".into()));
19 }
20 let indices = parse_obs_indices(args, session.observations.len())?;
21 let mut count = 0;
22 for idx in &indices {
23 if !session.observations[*idx].masked {
24 session.observations[*idx].masked = true;
25 count += 1;
26 }
27 }
28 Ok(CommandOutput::Text(format!(
29 "Masked {} observations",
30 count
31 )))
32 }
33}
34
35impl Command for Unmask {
36 fn name(&self) -> &str {
37 "UNMASK"
38 }
39 fn description(&self) -> &str {
40 "Unmask observations (include in fit)"
41 }
42
43 fn execute(&self, session: &mut Session, args: &[&str]) -> Result<CommandOutput> {
44 if args.is_empty() {
45 return Err(Error::Parse(
46 "UNMASK requires observation numbers or ALL".into(),
47 ));
48 }
49 if args[0].eq_ignore_ascii_case("ALL") {
50 let count = session.observations.iter().filter(|o| o.masked).count();
51 for obs in &mut session.observations {
52 obs.masked = false;
53 }
54 return Ok(CommandOutput::Text(format!(
55 "Unmasked {} observations",
56 count
57 )));
58 }
59 let indices = parse_obs_indices(args, session.observations.len())?;
60 let mut count = 0;
61 for idx in &indices {
62 if session.observations[*idx].masked {
63 session.observations[*idx].masked = false;
64 count += 1;
65 }
66 }
67 Ok(CommandOutput::Text(format!(
68 "Unmasked {} observations",
69 count
70 )))
71 }
72}
73
74fn parse_obs_indices(args: &[&str], total: usize) -> Result<Vec<usize>> {
75 let mut indices = Vec::new();
76 for arg in args {
77 if arg.contains('-') {
78 let parts: Vec<&str> = arg.splitn(2, '-').collect();
79 let start: usize = parts[0]
80 .parse()
81 .map_err(|e| Error::Parse(format!("invalid range start: {}", e)))?;
82 let end: usize = parts[1]
83 .parse()
84 .map_err(|e| Error::Parse(format!("invalid range end: {}", e)))?;
85 if start < 1 || end < 1 || start > total || end > total {
86 return Err(Error::Parse(format!(
87 "range {}-{} out of bounds (1-{})",
88 start, end, total
89 )));
90 }
91 for i in start..=end {
92 indices.push(i - 1);
93 }
94 } else {
95 let num: usize = arg
96 .parse()
97 .map_err(|e| Error::Parse(format!("invalid observation number: {}", e)))?;
98 if num < 1 || num > total {
99 return Err(Error::Parse(format!(
100 "observation {} out of bounds (1-{})",
101 num, total
102 )));
103 }
104 indices.push(num - 1);
105 }
106 }
107 Ok(indices)
108}