1- use std:: process:: ExitCode ;
1+ use core:: { fmt, panic} ;
2+ use std:: {
3+ env:: ArgsOs ,
4+ ffi:: OsString ,
5+ fs,
6+ io:: { stdin, Read , Write } ,
7+ iter:: Peekable ,
8+ process:: ExitCode ,
9+ vec,
10+ } ;
11+
12+ #[ derive( Debug , PartialEq , Eq ) ]
13+ struct Params {
14+ file1 : OsString ,
15+ file2 : OsString ,
16+ }
17+
18+ #[ derive( Debug , PartialEq , Eq ) ]
19+ enum ParseErr {
20+ InsufficientArgs ,
21+ }
22+
23+ impl fmt:: Display for ParseErr {
24+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> fmt:: Result {
25+ match self {
26+ ParseErr :: InsufficientArgs => write ! ( f, "Insufficient args passed" ) ,
27+ }
28+ }
29+ }
30+
31+ impl std:: error:: Error for ParseErr { }
32+
33+ // Exit codes are documented at
34+ // https://www.gnu.org/software/diffutils/manual/html_node/Invoking-sdiff.html.
35+ // An exit status of 0 means no differences were found,
36+ // 1 means some differences were found,
37+ // and 2 means trouble.
38+ pub fn main ( opts : Peekable < ArgsOs > ) -> ExitCode {
39+ let Ok ( params) = parse_params ( opts) else {
40+ // if we have insufficient args ...
41+ eprintln ! ( "Usage: <exe> <file1> <file2>" ) ;
42+ return ExitCode :: from ( 2 ) ;
43+ } ;
44+
45+ // first we need to get the properly files
46+ let file1 = read_file_contents ( & params. file1 ) ;
47+ let file2 = read_file_contents ( & params. file2 ) ;
48+
49+ // now we get the lines from the files as bytes, cuz the sdiff
50+ // must be compatible with ut8, ascii etc.
51+ let mut lines_left: Vec < & [ u8 ] > = file1. split ( |& c| c == b'\n' ) . collect ( ) ;
52+ let mut lines_rght: Vec < & [ u8 ] > = file2. split ( |& c| c == b'\n' ) . collect ( ) ;
53+
54+ // for some reason, the original file appends a empty line at
55+ // the end of file. I did not search for it, but my guess is
56+ // that this is EOL or an zeroed terminated file. Just remove it
57+ if lines_left. last ( ) == Some ( & & b"" [ ..] ) {
58+ lines_left. pop ( ) ;
59+ }
60+
61+ if lines_rght. last ( ) == Some ( & & b"" [ ..] ) {
62+ lines_rght. pop ( ) ;
63+ }
64+
65+ let mut output: Vec < u8 > = Vec :: new ( ) ;
66+ let width = 60 ;
67+ let max_lines = lines_left. len ( ) . max ( lines_rght. len ( ) ) ;
68+
69+ // ok, now we start running over the lines and get the lines right
70+ // and left file
71+ for i in 0 ..max_lines {
72+ let left = lines_left. get ( i) . map ( |l| String :: from_utf8_lossy ( l) ) ; // we can convert this is ut8?
73+ let right = lines_rght. get ( i) . map ( |r| String :: from_utf8_lossy ( r) ) ;
74+
75+ match ( left, right) {
76+ ( Some ( l) , Some ( r) ) if l == r => {
77+ // this is nice, cuz if the line is empty we stiill can print it, cause it equal : )
78+ writeln ! ( output, "{:<width$} {}" , l, r, width = width) . unwrap ( ) ;
79+ }
80+ ( Some ( l) , Some ( r) ) => {
81+ // if both lines are present but not equal, they are different, just print with |
82+ writeln ! ( output, "{:<width$} | {}" , l, r, width = width) . unwrap ( ) ;
83+ }
84+ ( Some ( l) , None ) => {
85+ // we have only left val, so print it with <
86+ writeln ! ( output, "{:<width$} <" , l, width = width) . unwrap ( ) ;
87+ }
88+ ( None , Some ( r) ) => {
89+ // we have only the ...
90+ writeln ! ( output, "{:<width$} > {}" , "" , r, width = width) . unwrap ( ) ;
91+ }
92+ _ => { }
93+ }
94+ }
95+
96+ // now print the line at stdout
97+ println ! ( "{}" , String :: from_utf8( output) . unwrap( ) ) ;
298
3- pub fn main ( ) -> ExitCode {
499 ExitCode :: SUCCESS
5- }
100+ }
101+
102+ fn parse_params < I : Iterator < Item = OsString > > ( mut opts : Peekable < I > ) -> Result < Params , ParseErr > {
103+ opts. next ( ) ; // this is the executable name, just jmp it
104+
105+ let Some ( arg1) = opts. next ( ) else {
106+ return Err ( ParseErr :: InsufficientArgs ) ;
107+ } ;
108+ let Some ( arg2) = opts. next ( ) else {
109+ return Err ( ParseErr :: InsufficientArgs ) ;
110+ } ;
111+
112+ Ok ( Params {
113+ file1 : arg1,
114+ file2 : arg2,
115+ } )
116+ }
117+
118+ fn read_file_contents ( filepath : & OsString ) -> Vec < u8 > {
119+ if filepath == "-" {
120+ get_file_from_stdin ( )
121+ } else {
122+ fs:: read ( filepath) . unwrap ( )
123+ }
124+ }
125+
126+ fn get_file_from_stdin ( ) -> Vec < u8 > {
127+ let mut stdin = stdin ( ) . lock ( ) ;
128+ let mut buf: Vec < u8 > = vec ! [ ] ;
129+
130+ if let Ok ( _) = stdin. read_to_end ( & mut buf) {
131+ return buf;
132+ } else {
133+ panic ! ( "Failed to read from stdin" )
134+ }
135+ }
136+
137+ #[ cfg( test) ]
138+ mod tests {
139+ use std:: ffi:: OsString ;
140+
141+ use crate :: sdiff:: { parse_params, Params , ParseErr } ;
142+
143+ fn str_os ( str : & str ) -> OsString {
144+ OsString :: from ( str)
145+ }
146+
147+ #[ test]
148+ fn test_params_convert ( ) {
149+ assert_eq ! (
150+ Ok ( Params {
151+ file1: str_os( "file1" ) ,
152+ file2: str_os( "file2" )
153+ } ) ,
154+ parse_params(
155+ [ str_os( "file1" ) , str_os( "file2" ) ]
156+ . iter( )
157+ . cloned( )
158+ . peekable( )
159+ )
160+ ) ;
161+ }
162+
163+ #[ test]
164+ fn parse_params_returns_err_insufficient_args_when_opts_iter_has_not_even_one_item ( ) {
165+ assert_eq ! (
166+ Err ( ParseErr :: InsufficientArgs ) ,
167+ parse_params( [ ] . iter( ) . cloned( ) . peekable( ) )
168+ )
169+ }
170+ }
0 commit comments