Oregami
Repositories/oxedyne/ore

oxedyne/ore/store/src/say.rs

8.2 KiB, 1 run

created by r2848102244:389, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Putting what the renderer noticed into plain language.
2//!
3//! A flag is a structure: a variant, some identities and some byte ranges. What a
4//! person needs is a sentence, and there must be exactly one sentence per flag in
5//! the whole system. A reader who has met a flag in a terminal and then meets the
6//! same flag on a page must not be told two different things about it, and a flag
7//! that gained a sentence in one place and not the other is a reader told nothing
8//! by half the tools.
9//!
10//! # The sentence is split, and the caller joins it
11//!
12//! [`describe`] returns a [`Described`]: the one word a listing sorts and scans
13//! under, and the sentence that goes beside it. A terminal wants them in one
14//! fixed-width line, which is [`Described::line`]; a page wants them in two cells
15//! of a table. Neither presentation is in here, and the words are in one place.
16
17use crate::tree::shown;
18
19use oxedyne_fe2o3_core::prelude::*;
20use oxedyne_fe2o3_ore::id::OpId;
21use oxedyne_fe2o3_ore::seq::render::{
22 Flag,
23 Repo,
24};
25
26
27/// Names a file for a message: the path it is live at, or the identity, which is
28/// what a file actually is.
29///
30/// A deleted file has no live path, and the path it last had is worth more to a
31/// reader than nothing, so it is given along with the identity that names it.
32pub fn name_of(render: &Repo, file: OpId) -> String {
33 match render.file(file) {
34 Some(f) if f.is_live() => shown(f.path()),
35 Some(f) => fmt!("{} (deleted, was {})", file, shown(f.path())),
36 None => fmt!("{}", file),
37 }
38}
39
40
41/// What one flag is, in two parts: the word a listing sorts under and the
42/// sentence beside it.
43pub struct Described {
44 /// The one word, as a listing prints it.
45 pub word: &'static str,
46 /// The sentence, in plain language.
47 pub said: String,
48}
49
50impl Described {
51 /// Returns the two parts as one line, the word in a fixed column.
52 ///
53 /// The column is nine wide with a space after it, which is one more than the
54 /// longest word, so a listing of flags reads down the sentences as well as
55 /// down the words.
56 pub fn line(&self) -> String {
57 fmt!("{:<9} {}", self.word, self.said)
58 }
59}
60
61/// Puts a flag into the words the tools use for it.
62///
63/// The two flags that name a file name it by identity, since that is what a file
64/// is; a person wants the path, so the render is asked for one.
65///
66/// # The wildcard arm is deliberate
67///
68/// The flag vocabulary is the engine's and it grows: a render that notices
69/// something new adds a member there, and a caller built against last month's
70/// engine must show that flag as an unrecognised one rather than fail to compile
71/// or, worse, drop it from the honesty surface it exists to be. The `#[allow]` is
72/// there because today's match happens to be exhaustive; the arm is not redundant
73/// tomorrow. `Flag::Yielded` arrived exactly this way, and now has a sentence of
74/// its own.
75///
76/// What the wildcard costs is the compiler's reminder that a new flag needs a
77/// sentence. That reminder is bought back in one place by the test module's
78/// `every_flag_is_matched`, whose match has no wildcard and so does not build
79/// until the new member is written down.
80pub fn describe(flag: &Flag, render: &Repo) -> Described {
81 #[allow(unreachable_patterns)]
82 match flag {
83 Flag::Torn { op, lost } => {
84 let runs: Vec<String> = lost.iter().map(|r| fmt!("{}", r)).collect();
85 Described {
86 word: "torn",
87 said: fmt!("{} no longer holds {}", op, runs.join(", ")),
88 }
89 },
90 Flag::Demoted { op, sub, origin } => Described {
91 word: "demoted",
92 said: fmt!(
93 "{} at +{}, its {} origin resolved against the splice that wrote the \
94 content rather than where the content now sits", op, sub, origin),
95 },
96 Flag::Dropped { op, sub, origin } => Described {
97 word: "dropped",
98 said: fmt!(
99 "{} at +{}, its {} origin was dropped and the placement fell to the edge \
100 of the file", op, sub, origin),
101 },
102 Flag::Overlap { ops, region } => {
103 let who: Vec<String> = ops.iter().map(|id| fmt!("{}", id)).collect();
104 Described {
105 word: "overlap",
106 said: fmt!("{} concurrently named {}", who.join(" and "), region),
107 }
108 },
109 Flag::CrossedFile { op, sub, from, to } => Described {
110 word: "crossed",
111 said: fmt!(
112 "{} at +{} was demoted across a file boundary: what it holds was written \
113 into {} and renders in {}",
114 op, sub, name_of(render, *from), name_of(render, *to)),
115 },
116 Flag::MovedIntoDeleted { op, file } => Described {
117 word: "buried",
118 said: fmt!(
119 "{} moved content into {}, which has been deleted, so the bytes render \
120 nowhere a reader looks", op, name_of(render, *file)),
121 },
122 Flag::Orphaned { op, sub } => Described {
123 word: "orphaned",
124 said: fmt!(
125 "{} at +{} fell out of every file, so the bytes it owns render nowhere at \
126 all", op, sub),
127 },
128 Flag::Confined { op, home, denied } => Described {
129 word: "confined",
130 said: fmt!(
131 "{} was caught in a cross-file cycle and voided back to {}; the move it \
132 asked for, into {}, did not happen",
133 op, name_of(render, *home), name_of(render, *denied)),
134 },
135 Flag::Won { op } => Described {
136 word: "won",
137 said: fmt!(
138 "{} was the latest move in a cross-file cycle and completed; the cycle's \
139 other moves were confined", op),
140 },
141 Flag::Stranded { op, by } => Described {
142 word: "stranded",
143 said: fmt!(
144 "{} inserted into context {} concurrently deleted, so the new bytes \
145 render nowhere a reader looks", op, by),
146 },
147 Flag::SplicedIntoDeleted { op, file, del } => Described {
148 word: "swallowed",
149 said: fmt!(
150 "{} edited {} while {} concurrently deleted it, so the edit is present in \
151 the log and absent from the tree", op, name_of(render, *file), del),
152 },
153 // The group prevailed, not the operation named: the arbitration decides a
154 // connected component, so the operation that prevailed routinely never
155 // touched a byte this one touched, and saying it rewrote your region would
156 // be a false sentence roughly three times in ten.
157 Flag::Yielded { op, to, group, through } => Described {
158 word: "yielded",
159 said: match through {
160 Some(host) => fmt!(
161 "{} sits wholly inside {}, which was buried, so it went with it; {} \
162 prevailed over that group of {}, and this edit is in the log and not \
163 in the file", op, host, to, group.len()),
164 None => fmt!(
165 "{} was one of {} concurrent edits over overlapping content; {} was \
166 highest in op order and prevailed, so this edit is in the log and not \
167 in the file", op, group.len(), to),
168 },
169 },
170 other => Described {
171 word: "unknown",
172 said: fmt!(
173 "{} is an unrecognised flag, {}, which this build is older than. The \
174 operations it names are in the history and are unaffected; what is \
175 missing is the sentence explaining it.",
176 match other.op() {
177 Some(op) => fmt!("{}", op),
178 None => fmt!("something"),
179 },
180 other.name()),
181 },
182 }
183}
184
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 /// Every flag the engine can raise has a sentence written for it.
191 ///
192 /// This match has no wildcard and is never called. It exists so that adding a
193 /// member to the engine's `Flag` fails to build here, which is the reminder
194 /// [`describe`]'s wildcard arm gives up in exchange for never dropping a flag
195 /// a reader is owed. When this stops compiling, write the sentence.
196 #[allow(dead_code)]
197 fn every_flag_is_matched(flag: &Flag) {
198 match flag {
199 Flag::Torn { .. } => (),
200 Flag::Demoted { .. } => (),
201 Flag::Dropped { .. } => (),
202 Flag::Overlap { .. } => (),
203 Flag::CrossedFile { .. } => (),
204 Flag::MovedIntoDeleted { .. } => (),
205 Flag::Orphaned { .. } => (),
206 Flag::Confined { .. } => (),
207 Flag::Won { .. } => (),
208 Flag::Stranded { .. } => (),
209 Flag::SplicedIntoDeleted { .. } => (),
210 Flag::Yielded { .. } => (),
211 }
212 }
213
214 /// The word sits in a fixed column, so a listing reads down the sentences as
215 /// well as down the words.
216 #[test]
217 fn a_flag_line_is_two_columns() -> Outcome<()> {
218 let said = Described { word: "torn", said: fmt!("something happened") }.line();
219 assert_eq!(said, "torn something happened");
220 let long = Described { word: "swallowed", said: fmt!("x") }.line();
221 assert_eq!(long, "swallowed x");
222 assert_eq!(said.find("something"), long.find("x"),
223 "every sentence begins in the same column");
224 Ok(())
225 }
226}