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