Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_syntax/tests/help.rs

8.9 KiB, 1 run

created by r1870400018:62030, 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

1mod fixture;
2
3use fixture::ore;
4
5use oxedyne_fe2o3_syntax::{
6 cmd::{
7 Cmd,
8 CmdConfig,
9 },
10 core::{
11 Syntax,
12 SyntaxConfig,
13 },
14 help::{
15 Help,
16 HelpDisplayConfig,
17 Page,
18 },
19};
20
21use oxedyne_fe2o3_core::prelude::*;
22
23use std::ffi::OsString;
24
25
26fn plain(width: usize) -> Help { Help::new(HelpDisplayConfig::plain(width)) }
27
28fn check(got: Vec<String>, want: &str) -> Outcome<()> {
29 let want: Vec<&str> = want.lines().collect();
30 for (i, (g, w)) in got.iter().zip(want.iter()).enumerate() {
31 req!(g.as_str(), *w, "line {} differs", i + 1);
32 }
33 req!(got.len(), want.len(), "line counts differ; got:\n{}", got.join("\n"));
34 Ok(())
35}
36
37const SUMMARY: &str = r#" ore 0.1.0 -- version control whose unit of history is the whole edit
38
39USAGE:
40 ore <command> [<values>] [<options>]
41 ore help <command | topic> ore <command> --help
42
43RECORD:
44 init Create a repository, minting a replica and a signing key.
45 mark Capture the working copy and name this point in history.
46 key Mint or replace this replica's keys: signing, content, veil.
47GO BACK:
48 back Put the working copy back to a mark (records nothing).
49 forget Take content out of operations, keep their shape, record why.
50EXCHANGE:
51 sync Exchange operations with another repository or a relay.
52
53TOPICS:
54 capture relay
55
56Every command except init and help begins by capturing the working copy, so
57nothing has to be staged."#;
58
59const SYNC: &str = r#" ore sync -- exchange operations with another repository or a relay
60
61USAGE:
62 ore sync <repo|url> [--pull-only] [--dry-run] [--oresyn-max <n>]
63
64VALUES:
65 <repo|url> The root of another Ore repository this machine can
66 reach, or a relay named
67 https://<host>/<account>/<repository>.
68OPTIONS:
69 --pull-only Take what the other end has, hand over nothing, and
70 record nothing here.
71 --dry-run Say what a sync would bring, and write nothing.
72 --oresyn-max <n> Hold this end to an older ORESYN vocabulary.
73
74Both ends end up holding every operation either held, and nothing is ever
75overwritten: a sync only adds.
76
77A relay is reached the same way:
78
79 ore sync https://relay.example/me/notes
80
81Neither end needs to be still for a sync.
82
83SEE ALSO: ore help relay"#;
84
85const BACK: &str = r#" ore back -- put the working copy back to a mark (records nothing)
86
87USAGE:
88 ore back <mark> [-- <path>...]
89
90VALUES:
91 <mark> The mark to go back to.
92 -- <path>... Put back only these paths."#;
93
94const RELAY: &str = r#" ore help relay -- exchanging through a server
95
96A relay holds a repository's operations for machines that cannot reach each
97other."#;
98
99const REPL: &str = r#" version control whose unit of history is the whole edit
100
101USAGE:
102 <command> [<values>] [<options>]
103RECORD:
104 init Create a repository, minting a replica and a signing key.
105 <dir> Where to create it. Default: the current
106 directory.
107 --unsigned Record without signing.
108 --mirror <path> Keep a git mirror at this path.
109 mark Capture the working copy and name this point in history.
110 <name> The mark's name.
111 <message>... Words of the mark's message.
112 key Mint or replace this replica's keys: signing, content, veil.
113 --veil [<key>] Veil the store.
114 --veil-key Print the veil key.
115GO BACK:
116 back Put the working copy back to a mark (records nothing).
117 <mark> The mark to go back to.
118 -- <path>... Put back only these paths.
119 forget Take content out of operations, keep their shape, record why.
120 <op|path>... An operation or a path.
121 --reason <text> Why, for the record.
122 --dry-run Say what would be forgotten, and write nothing.
123EXCHANGE:
124 sync Exchange operations with another repository or a relay.
125 <repo|url> The root of another Ore repository this machine
126 can reach, or a relay named
127 https://<host>/<account>/<repository>.
128 --pull-only Take what the other end has, hand over nothing,
129 and record nothing here.
130 --dry-run Say what a sync would bring, and write nothing.
131 --oresyn-max <n> Hold this end to an older ORESYN vocabulary."#;
132
133#[test]
134fn golden_summary() -> Outcome<()> {
135 let syntax = res!(ore());
136 check(res!(plain(80).page(&syntax, &Page::Summary)), SUMMARY)
137}
138
139#[test]
140fn golden_command_pages() -> Outcome<()> {
141 let syntax = res!(ore());
142 res!(check(res!(plain(80).page(&syntax, &Page::Command(fmt!("sync")))), SYNC));
143 check(res!(plain(80).page(&syntax, &Page::Command(fmt!("back")))), BACK)
144}
145
146#[test]
147fn golden_topic_page() -> Outcome<()> {
148 let syntax = res!(ore());
149 check(res!(plain(80).page(&syntax, &Page::Topic(fmt!("relay")))), RELAY)
150}
151
152// The one-page layout a REPL prints for its 'help' command.
153#[test]
154fn golden_repl_page() -> Outcome<()> {
155 let syntax = res!(ore());
156 check(res!(plain(80).to_lines(&syntax)), REPL)
157}
158
159#[test]
160fn all_is_the_table_then_every_page_then_every_topic() -> Outcome<()> {
161 let syntax = res!(ore());
162 let all = res!(plain(80).page(&syntax, &Page::All)).join("\n");
163 let mut at = 0;
164 for anchor in [
165 " ore 0.1.0 --", " ore init --", " ore mark --", " ore key --", " ore back --",
166 " ore forget --", " ore sync --", " ore help capture --", " ore help relay --",
167 ] {
168 match all[at..].find(anchor) {
169 Some(i) => at += i + anchor.len(),
170 None => return Err(err!("'{}' is missing or out of order in help --all.", anchor;
171 Missing)),
172 }
173 }
174 Ok(())
175}
176
177#[test]
178fn colour_only_when_asked_for() -> Outcome<()> {
179 let syntax = res!(ore());
180 for page in [Page::All] {
181 let text = res!(plain(80).page(&syntax, &page)).join("\n");
182 req!(text.contains('\x1b'), false);
183 let mut cfg = HelpDisplayConfig::plain(80);
184 cfg.colour = true;
185 let text = res!(Help::new(cfg).page(&syntax, &page)).join("\n");
186 req!(text.contains('\x1b'), true);
187 }
188 let text = res!(plain(80).to_lines(&syntax)).join("\n");
189 req!(text.contains('\x1b'), false);
190 // no-color.org: set to anything but empty turns colour off.
191 let tty = |nc: Option<&str>, term: Option<&str>| HelpDisplayConfig::colour_for(
192 true, nc.map(OsString::from), term.map(OsString::from));
193 req!(HelpDisplayConfig::colour_for(false, None, Some(OsString::from("xterm"))), false);
194 req!(tty(None, Some("xterm-256color")), true);
195 req!(tty(None, None), true);
196 req!(tty(Some("1"), Some("xterm")), false);
197 req!(tty(Some(""), Some("xterm")), true);
198 req!(tty(None, Some("dumb")), false);
199 Ok(())
200}
201
202#[test]
203fn width_follows_columns_within_bounds() -> Outcome<()> {
204 req!(HelpDisplayConfig::width_for(None), 80);
205 req!(HelpDisplayConfig::width_for(Some(fmt!("90"))), 90);
206 req!(HelpDisplayConfig::width_for(Some(fmt!("30"))), 60);
207 req!(HelpDisplayConfig::width_for(Some(fmt!("300"))), 100);
208 req!(HelpDisplayConfig::width_for(Some(fmt!("wide"))), 80);
209 Ok(())
210}
211
212// Every line fits the width, except a preformatted line or a single word too long to break.
213#[test]
214fn wrapped_lines_fit_the_width() -> Outcome<()> {
215 let syntax = res!(ore());
216 for width in [60, 72, 100] {
217 let mut lines = res!(plain(width).page(&syntax, &Page::All));
218 lines.append(&mut res!(plain(width).to_lines(&syntax)));
219 for line in lines {
220 let n = line.chars().count();
221 let unbreakable = line.trim().split_whitespace().count() == 1;
222 let verbatim = line.starts_with(" ore sync https://");
223 if n > width && !unbreakable && !verbatim {
224 return Err(err!("A line of {} characters at width {}: '{}'", n, width, line;
225 Mismatch));
226 }
227 }
228 }
229 Ok(())
230}
231
232#[test]
233fn categories_in_the_order_commands_name_them() -> Outcome<()> {
234 let mut s = Syntax::from(SyntaxConfig { name: fmt!("x"), ..Default::default() });
235 for (name, cat) in [("z", "Zeta"), ("a", "Alpha"), ("m", "Zeta"), ("b", "")] {
236 s = res!(s.add_cmd(Cmd::from(CmdConfig {
237 name: fmt!("{}", name), cat: fmt!("{}", cat), ..Default::default() })));
238 }
239 let lines = plain(80).summary(&s);
240 let heads: Vec<&String> = lines.iter().filter(|l| l.ends_with(':')).collect();
241 req!(heads, vec!["USAGE:", "ZETA:", "ALPHA:", "COMMANDS:"]);
242 let names: Vec<String> = lines.iter()
243 .filter(|l| l.starts_with(" ") && !l.contains('<'))
244 .map(|l| l.trim().to_string()).collect();
245 req!(names, vec!["z", "m", "a", "b"]);
246 Ok(())
247}