1#![warn(missing_docs, clippy::missing_docs_in_private_items)]
22use clap::error::ErrorKind;
23use derive_more::Display;
24use serde::{Deserialize, Serialize};
25use std::{
26 ops::{Deref, DerefMut},
27 str::FromStr,
28};
29
30use crate::error::EpochError;
31pub mod epoch_config;
32pub use epoch_config::EpochConfig;
33
34pub mod epoch_info;
35pub use epoch_info::EpochInfo;
36pub mod epoch_traits;
37
38static SUBFOLDER_NAMING: &str = "epoch";
41static MAIN_NAME: &str = ".epoch";
43static SUB_NAME: &str = "epochinfo.json";
45
46#[derive(Debug, Clone, Serialize, Deserialize, Display)]
51#[display("{inner:?}")]
52pub struct TagList {
53 inner: Vec<String>,
55}
56
57impl TagList {
58 fn new() -> Self {
60 Self { inner: Vec::new() }
61 }
62}
63
64impl Deref for TagList {
65 type Target = Vec<String>;
66
67 fn deref(&self) -> &Self::Target {
68 &self.inner
69 }
70}
71impl DerefMut for TagList {
72 fn deref_mut(&mut self) -> &mut Self::Target {
73 &mut self.inner
74 }
75}
76
77#[allow(missing_docs)]
79pub enum SaveMode {
80 DoNotErase,
81 Erase,
82}
83
84#[derive(
86 Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display,
87)]
88#[display("{id}")]
89pub struct EpochId {
90 pub id: u64,
92}
93
94impl FromStr for EpochId {
95 type Err = EpochError;
96
97 fn from_str(s: &str) -> Result<Self, Self::Err> {
98 Ok(EpochId { id: s.parse()? })
99 }
100}
101
102impl EpochId {
103 pub fn new(id: u64) -> Self {
105 Self { id }
106 }
107
108 pub fn next(&self) -> Self {
110 Self { id: self.id + 1 }
111 }
112
113 pub fn add_to_epoch(&self, other: i64) -> Self {
115 Self {
116 id: self.id.saturating_add_signed(other),
117 }
118 }
119}
120
121impl std::ops::Deref for EpochId {
122 type Target = u64;
123
124 fn deref(&self) -> &Self::Target {
125 &self.id
126 }
127}
128
129#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
138#[serde(untagged)] pub enum EpochIdentifier {
140 Tag(String),
142
143 Id(EpochId),
145}
146
147impl FromStr for EpochIdentifier {
148 type Err = clap::Error; fn from_str(s: &str) -> Result<Self, Self::Err> {
156 if let Ok(num) = s.parse::<u64>() {
157 Ok(EpochIdentifier::Id(EpochId::new(num)))
158 } else if !s.is_empty() {
159 Ok(EpochIdentifier::Tag(s.to_owned()))
160 } else {
161 Err(clap::Error::raw(
162 ErrorKind::InvalidValue,
163 format!("{} is not a valid epoch id or tag", s),
164 ))
165 }
166 }
167}
168
169#[cfg(test)]
170#[allow(clippy::unwrap_used)]
171mod test {
172 use insta::assert_debug_snapshot;
173 use serde_json::to_string_pretty;
175
176 use crate::epoch::{EpochId, EpochIdentifier, SaveMode, TagList};
177
178 #[test]
179 fn epoch_id_arithmetic() {
180 let id = EpochId::new(42);
181 assert_eq!(*id, 42);
182
183 let next = id.next();
184 assert_eq!(*next, 43);
185
186 let added = id.add_to_epoch(5);
187 assert_eq!(*added, 47);
188
189 let negative = id.add_to_epoch(-10);
190 assert_eq!(*negative, 32);
191
192 let zero = EpochId::new(0);
194 let under = zero.add_to_epoch(-1);
195 assert_eq!(*under, 0);
196 }
197
198 #[test]
199 fn epoch_identifier_parsing() {
200 let parsed: EpochIdentifier = "7".parse().unwrap();
202 assert!(matches!(parsed, EpochIdentifier::Id(_)));
203
204 let parsed: EpochIdentifier = "winter-b".parse().unwrap();
206 assert!(matches!(parsed, EpochIdentifier::Tag(_)));
207
208 assert!("".parse::<EpochIdentifier>().is_err());
210 }
211
212 #[test]
213 fn epoch_identifier_serialisation() {
214 let id = EpochIdentifier::Id(EpochId::new(13));
216 let json = to_string_pretty(&id).unwrap();
217 assert_debug_snapshot!("epoch_identifier_id", json);
218
219 let tag = EpochIdentifier::Tag("alpha".into());
221 let json = to_string_pretty(&tag).unwrap();
222 assert_debug_snapshot!("epoch_identifier_tag", json);
223 }
224
225 #[test]
226 fn tag_list_deref_and_mut() {
227 let mut list = TagList::new();
228 list.push("one".into());
229 list.push("two".into());
230
231 assert_eq!(list[0], "one");
233 assert_eq!(list[1], "two");
234
235 list[1] = "TWO".into();
237 assert_eq!(list[1], "TWO");
238 }
239
240 #[test]
241 fn save_mode_variants() {
242 let _do_not = SaveMode::DoNotErase;
244 let _erase = SaveMode::Erase;
245 }
246}