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
| 1 | mod fixture; |
| 2 | |
| 3 | use fixture::ore; |
| 4 | |
| 5 | use 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 | |
| 21 | use oxedyne_fe2o3_core::prelude::*; |
| 22 | |
| 23 | use std::ffi::OsString; |
| 24 | |
| 25 | |
| 26 | fn plain(width: usize) -> Help { Help::new(HelpDisplayConfig::plain(width)) } |
| 27 | |
| 28 | fn 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 | |
| 37 | const SUMMARY: &str = r#" ore 0.1.0 -- version control whose unit of history is the whole edit |
| 38 | |
| 39 | USAGE: |
| 40 | ore <command> [<values>] [<options>] |
| 41 | ore help <command | topic> ore <command> --help |
| 42 | |
| 43 | RECORD: |
| 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. |
| 47 | GO 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. |
| 50 | EXCHANGE: |
| 51 | sync Exchange operations with another repository or a relay. |
| 52 | |
| 53 | TOPICS: |
| 54 | capture relay |
| 55 | |
| 56 | Every command except init and help begins by capturing the working copy, so |
| 57 | nothing has to be staged."#; |
| 58 | |
| 59 | const SYNC: &str = r#" ore sync -- exchange operations with another repository or a relay |
| 60 | |
| 61 | USAGE: |
| 62 | ore sync <repo|url> [--pull-only] [--dry-run] [--oresyn-max <n>] |
| 63 | |
| 64 | VALUES: |
| 65 | <repo|url> The root of another Ore repository this machine can |
| 66 | reach, or a relay named |
| 67 | https://<host>/<account>/<repository>. |
| 68 | OPTIONS: |
| 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 | |
| 74 | Both ends end up holding every operation either held, and nothing is ever |
| 75 | overwritten: a sync only adds. |
| 76 | |
| 77 | A relay is reached the same way: |
| 78 | |
| 79 | ore sync https://relay.example/me/notes |
| 80 | |
| 81 | Neither end needs to be still for a sync. |
| 82 | |
| 83 | SEE ALSO: ore help relay"#; |
| 84 | |
| 85 | const BACK: &str = r#" ore back -- put the working copy back to a mark (records nothing) |
| 86 | |
| 87 | USAGE: |
| 88 | ore back <mark> [-- <path>...] |
| 89 | |
| 90 | VALUES: |
| 91 | <mark> The mark to go back to. |
| 92 | -- <path>... Put back only these paths."#; |
| 93 | |
| 94 | const RELAY: &str = r#" ore help relay -- exchanging through a server |
| 95 | |
| 96 | A relay holds a repository's operations for machines that cannot reach each |
| 97 | other."#; |
| 98 | |
| 99 | const REPL: &str = r#" version control whose unit of history is the whole edit |
| 100 | |
| 101 | USAGE: |
| 102 | <command> [<values>] [<options>] |
| 103 | RECORD: |
| 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. |
| 115 | GO 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. |
| 123 | EXCHANGE: |
| 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] |
| 134 | fn golden_summary() -> Outcome<()> { |
| 135 | let syntax = res!(ore()); |
| 136 | check(res!(plain(80).page(&syntax, &Page::Summary)), SUMMARY) |
| 137 | } |
| 138 | |
| 139 | #[test] |
| 140 | fn 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] |
| 147 | fn 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] |
| 154 | fn golden_repl_page() -> Outcome<()> { |
| 155 | let syntax = res!(ore()); |
| 156 | check(res!(plain(80).to_lines(&syntax)), REPL) |
| 157 | } |
| 158 | |
| 159 | #[test] |
| 160 | fn 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] |
| 178 | fn 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] |
| 203 | fn 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] |
| 214 | fn 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] |
| 233 | fn 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 | } |