Skip to main content

celestial_pointing/commands/
mask.rs

1use 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}