Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_tui/examples/term_dump.rs

3.4 KiB, 1 run

created by r1870400018:20818, 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//! Feeds a recording of a pseudoterminal to the terminal model and prints the resulting screen.
2//!
3//! This exists so that the model can be compared with a real terminal without anybody transcribing
4//! anything by hand. `script --log-out out.bin -c 'stty rows 24 cols 80; some_programme'` records
5//! exactly what a programme writes to a pseudoterminal; feeding that same recording to tmux and to
6//! this tool and diffing the two screens is what the oracle cases in `tests/term.rs` were built on.
7//!
8//! ```text
9//! cargo run --example term_dump -- 80 24 out.bin
10//! cargo run --example term_dump -- 80 24 out.bin --chunk 7 --scrollback
11//! ```
12//!
13//! `--chunk n` feeds the recording `n` bytes at a time, which cuts characters and control sequences
14//! at every kind of boundary and is how the parser's held over state gets exercised. `--resize
15//! COLSxROWS` resizes after the feed, which is how the rewrapping is compared with tmux.
16
17use oxedyne_fe2o3_tui::lib_tui::term::Terminal;
18
19use oxedyne_fe2o3_core::prelude::*;
20
21use std::{
22 fs,
23 process,
24};
25
26
27fn main() {
28 match run() {
29 Ok(()) => {}
30 Err(e) => {
31 eprintln!("{}", e);
32 process::exit(1);
33 }
34 }
35}
36
37/// Reads the arguments, feeds the recording and prints the screen.
38fn run() -> Outcome<()> {
39 let args: Vec<String> = std::env::args().collect();
40 if args.len() < 4 {
41 return Err(err!(
42 "Usage: term_dump <cols> <rows> <file> [--chunk n] [--scrollback] \
43 [--resize COLSxROWS] [--cursor]";
44 Invalid, Input));
45 }
46 let cols = res!(args[1].parse::<usize>(), Invalid, Input);
47 let rows = res!(args[2].parse::<usize>(), Invalid, Input);
48 let byts = res!(fs::read(&args[3]), IO, File);
49 let mut chunk = 0usize;
50 let mut scrollback = false;
51 let mut cursor = false;
52 let mut resize: Option<(usize, usize)> = None;
53 let mut i = 4;
54 while i < args.len() {
55 match args[i].as_str() {
56 "--chunk" => {
57 i += 1;
58 let v = res!(need(args.get(i), "--chunk needs a size"));
59 chunk = res!(v.parse::<usize>(), Invalid, Input);
60 }
61 "--scrollback" => scrollback = true,
62 "--cursor" => cursor = true,
63 "--resize" => {
64 i += 1;
65 let v = res!(need(args.get(i), "--resize needs a size"));
66 resize = Some(res!(parse_size(v)));
67 }
68 other => return Err(err!("{} is not an argument this tool takes.", other;
69 Invalid, Input)),
70 }
71 i += 1;
72 }
73 let mut term = res!(Terminal::new(cols, rows));
74 if chunk == 0 {
75 res!(term.feed(&byts));
76 } else {
77 let mut a = 0;
78 while a < byts.len() {
79 let b = (a + chunk).min(byts.len());
80 res!(term.feed(&byts[a..b]));
81 a = b;
82 }
83 }
84 if let Some((c, r)) = resize {
85 res!(term.resize(c, r));
86 }
87 let scr = term.screen();
88 if scrollback {
89 for i in 0..scr.scrollback_len() {
90 match scr.scrollback_text(i) {
91 Some(s) => println!("{}", s),
92 None => {}
93 }
94 }
95 }
96 for r in 0..scr.rows() {
97 println!("{}", scr.row_text(r));
98 }
99 if cursor {
100 let cur = scr.cursor();
101 eprintln!("CUR {},{} HIST {} VIEW {}",
102 cur.reported_col(), cur.row, scr.scrollback_len(), scr.view_offset());
103 }
104 Ok(())
105}
106
107/// Reads a `COLSxROWS` argument.
108fn parse_size(s: &str) -> Outcome<(usize, usize)> {
109 let i = res!(need(s.find('x'), "a size is written as COLSxROWS"));
110 let c = res!(s[..i].parse::<usize>(), Invalid, Input);
111 let r = res!(s[i + 1..].parse::<usize>(), Invalid, Input);
112 Ok((c, r))
113}
114
115/// Turns an absent value into an error that names what was missing.
116fn need<T>(v: Option<T>, what: &str) -> Outcome<T> {
117 match v {
118 Some(v) => Ok(v),
119 None => Err(err!("Expected {}.", what; Invalid, Input)),
120 }
121}