camloc_common/
lib.rs

1use std::{
2    fmt::Display,
3    str::FromStr,
4    time::{Duration, Instant},
5};
6use thiserror::Error as ThisError;
7
8#[cfg(feature = "cv")]
9pub mod cv;
10
11pub mod hosts;
12pub mod position;
13
14pub use position::Position;
15
16pub trait Lerp {
17    fn lerp(start: &Self, end: &Self, t: f64) -> Self;
18}
19
20impl Lerp for f64 {
21    fn lerp(s: &Self, e: &Self, t: f64) -> Self {
22        (1. - t) * s + t * e
23    }
24}
25
26#[derive(Debug, ThisError)]
27pub enum GetFromStdinError<P> {
28    #[error(transparent)]
29    Io(#[from] std::io::Error),
30
31    #[error(transparent)]
32    Parse(P),
33}
34
35pub fn get_from_stdin<T>(prompt: &str) -> Result<T, GetFromStdinError<<T as FromStr>::Err>>
36where
37    T: FromStr,
38{
39    use std::io::Write;
40
41    let mut stdout = std::io::stdout().lock();
42    stdout.write_all(prompt.as_bytes())?;
43    stdout.flush()?;
44    drop(stdout);
45
46    let mut l = String::new();
47    std::io::stdin().read_line(&mut l)?;
48
49    // exclude newline at the end
50    l[..l.len() - 1].parse().map_err(GetFromStdinError::Parse)
51}
52
53pub fn yes_no_choice(prompt: &str, default: bool) -> bool {
54    let default_text = if default { "Y/n" } else { "y/N" };
55    let prompt = &format!("{prompt} ({default_text}) ");
56
57    match get_from_stdin::<String>(prompt) {
58        Ok(answer) if !answer.is_empty() => matches!(&answer.to_lowercase()[..], "y" | "yes"),
59        _ => default,
60    }
61}
62
63#[derive(Debug, ThisError)]
64pub enum ChoiceError {
65    #[error("{0}")]
66    GetFromStdin(#[from] GetFromStdinError<<usize as FromStr>::Err>),
67
68    #[error("Invalid choice: {0}, no default")]
69    NoDefault(usize),
70}
71
72pub fn choice<T: Display>(
73    listed: impl Iterator<Item = (T, bool)>,
74    choice_prompt: Option<&str>,
75    default_choice: Option<usize>,
76) -> Result<usize, ChoiceError> {
77    let mut mapping = vec![];
78    for (i, (c, is_choice)) in listed.enumerate() {
79        if is_choice {
80            print!("{:<3}", mapping.len());
81            mapping.push(i);
82        } else {
83            print!("   ");
84        }
85
86        println!("{c}");
87    }
88
89    let v = get_from_stdin::<usize>(choice_prompt.unwrap_or("Enter choice: "))?;
90
91    if v >= mapping.len() {
92        if let Some(def) = default_choice {
93            Ok(def)
94        } else {
95            Err(ChoiceError::NoDefault(v))
96        }
97    } else {
98        Ok(mapping[v])
99    }
100}
101
102#[derive(Debug, Clone, Copy)]
103pub struct TimeValidated<T> {
104    last_changed: Instant,
105    pub valid_time: Duration,
106    value: T,
107}
108
109impl<T> TimeValidated<T> {
110    pub fn new(value: T, valid_time: Duration) -> Self {
111        Self {
112            last_changed: Instant::now(),
113            valid_time,
114            value,
115        }
116    }
117
118    pub const fn new_with_change(value: T, valid_time: Duration, last_changed: Instant) -> Self {
119        Self {
120            last_changed,
121            valid_time,
122            value,
123        }
124    }
125
126    pub fn get(&self) -> Option<&T> {
127        if self.is_valid() {
128            Some(&self.value)
129        } else {
130            None
131        }
132    }
133
134    pub fn set(&mut self, value: T) {
135        self.last_changed = Instant::now();
136        self.value = value;
137    }
138
139    pub fn set_with_time(&mut self, value: T, last_changed: Instant) {
140        self.last_changed = last_changed;
141        self.value = value;
142    }
143
144    pub fn is_valid(&self) -> bool {
145        self.last_changed.elapsed() <= self.valid_time
146    }
147
148    pub const fn last_changed(&self) -> Instant {
149        self.last_changed
150    }
151}