geop_core_editor/output/
mod.rs1mod export;
12mod inspect;
13
14pub use export::{Content, ErasedExporter, Export, Exporter, ExporterInfo};
15pub use inspect::{
16 Answer, AnswerAction, Block, Does, ErasedInspector, Inspection, Inspector, InspectorInfo,
17 Picks, Row,
18};
19
20use geop_core_math::{
21 geop_error::{GeopError, GeopResult},
22 scalars::Scalar,
23};
24use geop_core_program::{part::Part, program::Program, reader::Reader, runner::Runner};
25use serde::{Deserialize, Serialize, de::DeserializeOwned};
26
27#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
30pub struct Bounded {
31 pub value: f64,
32 pub error: f64,
33}
34
35impl Bounded {
36 pub fn of(x: Scalar) -> Self {
38 let (lo, hi) = (x.lower().to_f64(), x.upper().to_f64());
39 Self {
40 value: 0.5 * (lo + hi),
41 error: 0.5 * (hi - lo),
42 }
43 }
44
45 pub fn times(self, n: f64) -> Self {
48 Self {
49 value: self.value * n,
50 error: self.error * n,
51 }
52 }
53
54 pub fn contains(&self, exact: f64) -> bool {
56 (exact - self.value).abs() <= self.error
57 }
58
59 pub fn show(&self, digits: usize) -> String {
62 let value = format!("{:.digits$}", self.value);
63 match self.error >= 0.5 * 10f64.powi(-(digits as i32)) {
64 true => format!("{value} ± {:.1e}", self.error),
65 false => value,
66 }
67 }
68
69 pub fn show_mass(&self) -> String {
72 match self.value < 1.0 {
73 true => format!("{} g", self.times(1000.0).show(3)),
74 false => format!("{} kg", self.show(3)),
75 }
76 }
77}
78
79pub struct Output<'a> {
84 pub runner: &'a Runner,
85 pub part: &'a mut Part,
86 pub file: &'a Program,
87 pub editing: Option<&'a str>,
90 pub date: &'a str,
93}
94
95impl<'a> Output<'a> {
96 pub fn part(&self) -> Reader<'_> {
98 self.part.current()
99 }
100
101 pub fn part_before(&self, step: &str) -> GeopResult<Reader<'_>> {
104 self.file.index_of(step)?;
105 Ok(self.part.before(step))
106 }
107
108 pub fn file_name(&self) -> &str {
110 &self.runner.location.file
111 }
112}
113
114#[derive(Clone, Copy, Debug, Default, Deserialize)]
118pub struct NoOptions {}
119
120fn read_options<T: DeserializeOwned + Default>(
123 kind: &str,
124 json: &serde_json::Value,
125) -> GeopResult<T> {
126 match json {
127 serde_json::Value::Null => Ok(T::default()),
128 json => T::deserialize(json)
129 .map_err(|e| GeopError::new(format!("the options of `{kind}`: {e}"))),
130 }
131}
132
133#[derive(Default)]
136pub struct Outputs {
137 exporters: Vec<Box<dyn ErasedExporter>>,
138 inspectors: Vec<Box<dyn ErasedInspector>>,
139}
140
141impl Outputs {
142 pub fn exporter<E: Exporter>(mut self, exporter: E) -> GeopResult<Self> {
145 if self.exporters.iter().any(|e| e.info().kind == E::INFO.kind) {
146 return Err(GeopError::new(format!(
147 "exporter `{}` is registered twice",
148 E::INFO.kind
149 )));
150 }
151 self.exporters.push(Box::new(exporter));
152 Ok(self)
153 }
154
155 pub fn inspector<I: Inspector>(mut self, inspector: I) -> GeopResult<Self> {
157 if self
158 .inspectors
159 .iter()
160 .any(|i| i.info().kind == I::INFO.kind)
161 {
162 return Err(GeopError::new(format!(
163 "inspector `{}` is registered twice",
164 I::INFO.kind
165 )));
166 }
167 self.inspectors.push(Box::new(inspector));
168 Ok(self)
169 }
170
171 pub fn exporters(&self) -> Vec<&'static ExporterInfo> {
173 self.exporters.iter().map(|e| e.info()).collect()
174 }
175
176 pub fn inspectors(&self) -> Vec<&'static InspectorInfo> {
178 self.inspectors.iter().map(|i| i.info()).collect()
179 }
180
181 pub fn get_exporter(&self, kind: &str) -> GeopResult<&dyn ErasedExporter> {
183 of_kind(&self.exporters, kind, "exporter", |e| e.info().kind)
184 }
185
186 pub fn exporter_for(&self, format: &str) -> GeopResult<&dyn ErasedExporter> {
189 let format = format.to_ascii_lowercase();
190 (self.exporters.iter())
191 .find(|e| e.info().formats.contains(&format.as_str()))
192 .map(Box::as_ref)
193 .ok_or_else(|| {
194 let known: Vec<&str> = (self.exporters.iter())
195 .flat_map(|e| e.info().formats.iter().copied())
196 .collect();
197 GeopError::new(format!(
198 "no exporter writes `.{format}` files; they write: {}",
199 known.join(", ")
200 ))
201 })
202 }
203
204 pub fn get_inspector(&self, kind: &str) -> GeopResult<&dyn ErasedInspector> {
206 of_kind(&self.inspectors, kind, "inspector", |i| i.info().kind)
207 }
208}
209
210fn of_kind<'a, T: ?Sized>(
213 entries: &'a [Box<T>],
214 kind: &str,
215 what: &str,
216 kind_of: impl Fn(&T) -> &'static str,
217) -> GeopResult<&'a T> {
218 (entries
219 .iter()
220 .map(Box::as_ref)
221 .find(|&e| kind_of(e) == kind))
222 .ok_or_else(|| {
223 let known: Vec<&str> = entries.iter().map(Box::as_ref).map(&kind_of).collect();
224 GeopError::new(format!(
225 "there is no {what} `{kind}`; there are: {}",
226 known.join(", ")
227 ))
228 })
229}