Skip to content

Commit a0c70fc

Browse files
authored
Merge pull request #424 from rustcoreutils/updates
Updates
2 parents 26f197d + 7fe74d2 commit a0c70fc

File tree

9 files changed

+1609
-148
lines changed

9 files changed

+1609
-148
lines changed

editors/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,9 @@ path = "./vi/lib.rs"
2222

2323
[[bin]]
2424
name = "vi"
25-
path = "./vi/main.rs"
25+
path = "vi_main.rs"
26+
27+
[[bin]]
28+
name = "ex"
29+
path = "ex_main.rs"
2630

editors/ex_main.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//! ex - POSIX line editor
2+
//!
3+
//! This is the entry point for the ex binary.
4+
//! It delegates to the common editor core with ex (line) mode.
5+
6+
use std::env;
7+
use std::process;
8+
use vi_rs::{run_editor, InvokedAs};
9+
10+
fn main() {
11+
let args: Vec<String> = env::args().collect();
12+
let exit_code = run_editor(InvokedAs::Ex, &args);
13+
process::exit(exit_code);
14+
}

editors/tests/ex_tests.rs

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
//! Integration tests for the ex editor.
2+
//!
3+
//! These tests verify the ex binary works correctly in line-oriented mode,
4+
//! testing POSIX ex commands via stdin/stdout.
5+
6+
use plib::testing::{run_test, TestPlan};
7+
use std::fs;
8+
use tempfile::NamedTempFile;
9+
10+
// Helper to create a test plan for ex in silent mode
11+
fn ex_test(stdin: &str, expected_out: &str) {
12+
run_test(TestPlan {
13+
cmd: "ex".to_string(),
14+
args: vec!["-s".to_string()],
15+
stdin_data: stdin.to_string(),
16+
expected_out: expected_out.to_string(),
17+
expected_err: String::new(),
18+
expected_exit_code: 0,
19+
});
20+
}
21+
22+
// Helper to test ex with a file
23+
fn ex_test_with_file(file_content: &str, stdin: &str, expected_out: &str) {
24+
let temp = NamedTempFile::new().unwrap();
25+
fs::write(temp.path(), file_content).unwrap();
26+
27+
run_test(TestPlan {
28+
cmd: "ex".to_string(),
29+
args: vec!["-s".to_string(), temp.path().to_string_lossy().to_string()],
30+
stdin_data: stdin.to_string(),
31+
expected_out: expected_out.to_string(),
32+
expected_err: String::new(),
33+
expected_exit_code: 0,
34+
});
35+
}
36+
37+
// ============================================================================
38+
// Basic Operation Tests
39+
// ============================================================================
40+
41+
#[test]
42+
fn test_ex_quit() {
43+
ex_test("q\n", "");
44+
}
45+
46+
#[test]
47+
fn test_ex_append_and_print() {
48+
ex_test(
49+
"a\nhello world\nline two\n.\n1,$p\nq!\n",
50+
"hello world\nline two\n",
51+
);
52+
}
53+
54+
#[test]
55+
fn test_ex_insert_and_print() {
56+
ex_test(
57+
"a\nfirst line\n.\n1i\ninserted line\n.\n1,$p\nq!\n",
58+
"inserted line\nfirst line\n",
59+
);
60+
}
61+
62+
#[test]
63+
fn test_ex_number_command() {
64+
ex_test(
65+
"a\nline one\nline two\n.\n1,$nu\nq!\n",
66+
" 1\tline one\n 2\tline two\n",
67+
);
68+
}
69+
70+
#[test]
71+
fn test_ex_list_command() {
72+
// Test that list shows $ at end of lines
73+
ex_test("a\nhello\n.\n1l\nq!\n", "hello$\n");
74+
}
75+
76+
#[test]
77+
fn test_ex_delete() {
78+
ex_test(
79+
"a\nline one\nline two\nline three\n.\n2d\n1,$p\nq!\n",
80+
"line one\nline three\n",
81+
);
82+
}
83+
84+
#[test]
85+
fn test_ex_substitute() {
86+
ex_test(
87+
"a\nhello world\n.\n1s/world/everyone/\n1p\nq!\n",
88+
"hello everyone\n",
89+
);
90+
}
91+
92+
#[test]
93+
fn test_ex_substitute_global() {
94+
ex_test(
95+
"a\nhello hello hello\n.\n1s/hello/hi/g\n1p\nq!\n",
96+
"hi hi hi\n",
97+
);
98+
}
99+
100+
#[test]
101+
fn test_ex_yank_and_put() {
102+
ex_test(
103+
"a\nline one\nline two\n.\n1y\n2pu\n1,$p\nq!\n",
104+
"line one\nline two\nline one\n",
105+
);
106+
}
107+
108+
#[test]
109+
fn test_ex_copy() {
110+
ex_test(
111+
"a\nline one\nline two\n.\n1co2\n1,$p\nq!\n",
112+
"line one\nline two\nline one\n",
113+
);
114+
}
115+
116+
#[test]
117+
fn test_ex_move() {
118+
ex_test(
119+
"a\nline one\nline two\nline three\n.\n1m2\n1,$p\nq!\n",
120+
"line two\nline one\nline three\n",
121+
);
122+
}
123+
124+
#[test]
125+
fn test_ex_goto_line() {
126+
ex_test(
127+
"a\nline one\nline two\nline three\n.\n2\np\nq!\n",
128+
"line two\n",
129+
);
130+
}
131+
132+
#[test]
133+
fn test_ex_join() {
134+
ex_test(
135+
"a\nline one\nline two\n.\n1,2j\n1p\nq!\n",
136+
"line one line two\n",
137+
);
138+
}
139+
140+
#[test]
141+
fn test_ex_undo() {
142+
ex_test("a\nhello\n.\n1s/hello/goodbye/\nu\n1p\nq!\n", "hello\n");
143+
}
144+
145+
// ============================================================================
146+
// File Operation Tests
147+
// ============================================================================
148+
149+
#[test]
150+
fn test_ex_read_file() {
151+
ex_test_with_file("content from file\n", "1,$p\nq\n", "content from file\n");
152+
}
153+
154+
#[test]
155+
fn test_ex_write_file() {
156+
let temp = NamedTempFile::new().unwrap();
157+
let path = temp.path().to_string_lossy().to_string();
158+
159+
run_test(TestPlan {
160+
cmd: "ex".to_string(),
161+
args: vec!["-s".to_string()],
162+
stdin_data: format!("a\ntest content\n.\nw {}\nq\n", path),
163+
expected_out: String::new(),
164+
expected_err: String::new(),
165+
expected_exit_code: 0,
166+
});
167+
168+
let content = fs::read_to_string(temp.path()).unwrap();
169+
assert_eq!(content, "test content\n");
170+
}
171+
172+
// ============================================================================
173+
// Address Range Tests
174+
// ============================================================================
175+
176+
#[test]
177+
fn test_ex_range_all() {
178+
ex_test("a\na\nb\nc\nd\n.\n1,$p\nq!\n", "a\nb\nc\nd\n");
179+
}
180+
181+
#[test]
182+
fn test_ex_range_single() {
183+
ex_test("a\na\nb\nc\n.\n2p\nq!\n", "b\n");
184+
}
185+
186+
#[test]
187+
fn test_ex_range_explicit() {
188+
ex_test("a\na\nb\nc\nd\ne\n.\n2,4p\nq!\n", "b\nc\nd\n");
189+
}
190+
191+
#[test]
192+
fn test_ex_current_line_address() {
193+
ex_test("a\nfirst\nsecond\nthird\n.\n2\n.p\nq!\n", "second\n");
194+
}
195+
196+
#[test]
197+
fn test_ex_last_line_address() {
198+
ex_test("a\nfirst\nlast\n.\n$p\nq!\n", "last\n");
199+
}
200+
201+
// ============================================================================
202+
// Global Command Tests
203+
// ============================================================================
204+
205+
#[test]
206+
fn test_ex_global_delete() {
207+
ex_test(
208+
"a\nkeep this\ndelete me\nkeep this too\ndelete me also\n.\ng/delete/d\n1,$p\nq!\n",
209+
"keep this\nkeep this too\n",
210+
);
211+
}
212+
213+
#[test]
214+
fn test_ex_global_print() {
215+
ex_test(
216+
"a\napple\nbanana\napricot\ncherry\n.\ng/^a/p\nq!\n",
217+
"apple\napricot\n",
218+
);
219+
}
220+
221+
// ============================================================================
222+
// Set Option Tests
223+
// ============================================================================
224+
225+
#[test]
226+
fn test_ex_set_option() {
227+
// Just verify set command is accepted without error
228+
ex_test("set number\nq!\n", "");
229+
}
230+
231+
// ============================================================================
232+
// Error Handling Tests
233+
// ============================================================================
234+
235+
#[test]
236+
fn test_ex_invalid_command_silent() {
237+
// In silent mode, errors exit with code 1
238+
// Note: Error message goes to stderr which TestPlan captures separately
239+
run_test(TestPlan {
240+
cmd: "ex".to_string(),
241+
args: vec!["-s".to_string()],
242+
stdin_data: "invalidcmd\n".to_string(),
243+
expected_out: String::new(),
244+
expected_err: "Invalid command: invalidcmd\n".to_string(),
245+
expected_exit_code: 1,
246+
});
247+
}
248+
249+
// ============================================================================
250+
// Version and Help Tests
251+
// ============================================================================
252+
253+
#[test]
254+
fn test_ex_version_command() {
255+
// Version command should return package info - but we don't check exact output
256+
// Just verify it runs without error
257+
ex_test("version\nq!\n", "");
258+
}
259+
260+
// ============================================================================
261+
// Shift Command Tests
262+
// ============================================================================
263+
264+
#[test]
265+
fn test_ex_shift_right() {
266+
ex_test(
267+
"a\nline one\nline two\n.\n1,2>\n1,$p\nq!\n",
268+
" line one\n line two\n",
269+
);
270+
}
271+
272+
#[test]
273+
fn test_ex_shift_left() {
274+
ex_test("a\n indented\n.\n1<\n1p\nq!\n", "indented\n");
275+
}
276+
277+
// ============================================================================
278+
// Line Number Command Tests
279+
// ============================================================================
280+
281+
#[test]
282+
fn test_ex_line_number() {
283+
ex_test("a\nline one\nline two\nline three\n.\n=\nq!\n", "3\n");
284+
}
285+
286+
#[test]
287+
fn test_ex_line_number_with_address() {
288+
ex_test("a\nline one\nline two\nline three\n.\n2=\nq!\n", "2\n");
289+
}
290+
291+
// ============================================================================
292+
// Print with Line Numbers (#) Tests
293+
// ============================================================================
294+
295+
#[test]
296+
fn test_ex_hash_command() {
297+
ex_test(
298+
"a\nfirst\nsecond\n.\n1,2#\nq!\n",
299+
" 1\tfirst\n 2\tsecond\n",
300+
);
301+
}
302+
303+
// ============================================================================
304+
// Z Command Tests
305+
// ============================================================================
306+
307+
#[test]
308+
fn test_ex_z_command() {
309+
// z should display lines from the file
310+
ex_test(
311+
"a\nline 1\nline 2\nline 3\nline 4\nline 5\n.\n1z3\nq!\n",
312+
"line 1\nline 2\nline 3\n",
313+
);
314+
}
315+
316+
// ============================================================================
317+
// Repeat Substitute (&) Tests
318+
// ============================================================================
319+
320+
#[test]
321+
fn test_ex_repeat_substitute() {
322+
ex_test(
323+
"a\nhello world\nhello universe\n.\n1s/hello/hi/\n2&\n1,$p\nq!\n",
324+
"hi world\nhi universe\n",
325+
);
326+
}

0 commit comments

Comments
 (0)