Intro
Not long ago I posed a challenge for those of us learning rust: https://lemmy.ml/post/12478167.
Basically write an equivalent of git diff --no-index A B
… a file differ.
While it’s never too late to attempt it, I figured it’d be a good time to check in to see what anyone thought of it, in part because some people may have forgotten about it and would still like to have a shot, and also because I had a shot and am happy with what I wrote.
Check In
I’ll post where I got up to below (probably as a comment), but before that, does anyone have anything to share on where they got up to … any general thoughts on the challenge and the general idea of these?
My experience
My personal experience was that I’d not kept up with my rust “studies” for a bit and used this as a good “warm up” or “restart” exercise and it worked really well. Obviously learning through doing is a good idea, and the Rust Book is a bit too light, IMO, on good exercises or similar activities. But I found this challenge just difficult enough to make me feel more comfortable with the language.
Future Challenges
Any ideas for future challenges??
My quick thoughts
- A simple web app like a todo app using axtix_web and diesel and some templating crate.
- Extend my diffing program to output JSON/HTML and then to diff by characters in a string
- A markdown parser??
… continued
fn main() { // > Getting args let args: Vec<String> = env::args().collect(); if args[1..].len() != 2 { panic!( "Must provide two paths. Instead provided {:}", args[1..].len() ); } println!("Args:"); for a in args[1..].iter() { println!("{}", a); } // > Reading files and splitting into lines let a = read_to_string(&args[1]).expect("Failed to read file"); let b = read_to_string(&args[2]).expect("Failed to read file"); let a_lines: Vec<&str> = a.split("\n").collect(); let b_lines: Vec<&str> = b.split("\n").collect(); // > Initialising globals let file_lengths = FileLens::new(&a_lines, &b_lines); let cursor = Cursors { a: 0, b: 0 }; let mut chunks: Vec<Chunk> = vec![]; // mut necessary as overwriting in each loop let mut state: State = State::NewChunk {cursor}; // > Loop loop { state = match state { State::NewChunk { cursor } => State::NewLine { chunk_data: ChunkData{ lines: Vec::new(), cursor: cursor.clone(), start_cursor: cursor} }, State::NewLine { chunk_data } => { let line_read = read_line(&chunk_data.cursor, &file_lengths, &a_lines, &b_lines); match chunk_data.lines.as_slice() { [] => { match line_read.kind { CompType::Match => State::ContinuingChunk { chunk_data, line_read }, CompType::Change => State::ContinuingChunk { chunk_data, line_read }, CompType::FileEnd(file_spec) => State::FileEnd { chunk_data, line_read: file_spec}, } }, [.., lc] => { match lc.kind { CompType::Match => { match line_read.kind { CompType::Match => State::ContinuingChunk { chunk_data, line_read }, CompType::Change => State::EndingChunk { chunk_data, line_read }, CompType::FileEnd(file_spec) => State::FileEnd { chunk_data, line_read: file_spec}, } } CompType::Change => { match line_read.kind { CompType::Match => State::EndingChunk { chunk_data, line_read }, CompType::Change => State::ContinuingChangeChunk { chunk_data, line_read }, CompType::FileEnd(_) => State::FileEndChange { chunk_data}, } } CompType::FileEnd(_) => panic!( // error! should not have come here from FileEnd "Failed to process file end correctly (failed at lines a:{},b:{})", line_read.cursor.a, line_read.cursor.b), } } } }, State::ContinuingChunk { mut chunk_data, line_read } => { chunk_data.lines.push(line_read); let new_cursor = chunk_data.cursor.increment_cursor(None); chunk_data.set_cursor(new_cursor); State::NewLine{chunk_data} }, State::ContinuingChangeChunk { chunk_data, line_read } => { let first_lc = chunk_data.lines.first().unwrap(); if a_lines[first_lc.cursor.a] == b_lines[line_read.cursor.b] { State::EndingChangedChunk{ chunk_data, chunk_type: ChunkType::Addition } } else if a_lines[line_read.cursor.a] == b_lines[first_lc.cursor.b] { State::EndingChangedChunk{ chunk_data, chunk_type: ChunkType::Deletion } } else { State::ContinuingChunk { chunk_data, line_read } } }, State::EndingChunk { chunk_data, line_read } => { let chunk_type = match line_read.kind { CompType::Match => ChunkType::Modification, CompType::Change => ChunkType::Match, CompType::FileEnd(_) => panic!( // error! should not have come here from FileEnd "Failed to process file end correctly (failed at lines a:{},b:{})", line_read.cursor.a, line_read.cursor.b ) }; let new_a_lines: Vec<String> = chunk_data.lines.iter() .map(|lc| String::from(a_lines[lc.cursor.a])) .collect(); let new_b_lines: Vec<String> = chunk_data.lines.iter() .map(|lc| String::from(b_lines[lc.cursor.b])) .collect(); chunks.push( Chunk{ chunk_type, a_lines: new_a_lines, b_lines: new_b_lines, cursor: chunk_data.start_cursor, } ); // continue from last read line, but with a new chunk // ... repetitive yes, but cleaner code I think State::NewChunk { cursor: line_read.cursor } }, State::EndingChangedChunk { chunk_data, chunk_type } => { let new_a_lines: Vec<String> = chunk_data.lines.iter() .map(|lc| String::from(a_lines[lc.cursor.a])) .collect(); let new_b_lines: Vec<String> = chunk_data.lines.iter() .map(|lc| String::from(b_lines[lc.cursor.b])) .collect(); chunks.push( Chunk{ chunk_type, a_lines: new_a_lines, b_lines: new_b_lines, cursor: chunk_data.start_cursor, } ); let new_cursor = chunk_data.cursor.increment_cursor(Some(chunks.last().unwrap())); State::NewChunk { cursor: new_cursor } }, State::FileEnd { chunk_data, line_read} => { match line_read { FileSpec::A => { chunks.push( Chunk{ chunk_type: ChunkType::Addition, a_lines: vec![], b_lines: b_lines[chunk_data.cursor.b..].iter() .map(|s| String::from(*s)).collect(), cursor: chunk_data.cursor, } ); State::End }, FileSpec::B => { chunks.push( Chunk{ chunk_type: ChunkType::Deletion, a_lines: a_lines[chunk_data.cursor.a..].iter() .map(|s| String::from(*s)).collect(), b_lines: vec![], cursor: chunk_data.cursor, } ); State::End }, FileSpec::AB => State::End, } }, State::FileEndChange { chunk_data } => { let a_cursor = chunk_data.start_cursor.a.clone(); let b_cursor = chunk_data.start_cursor.b.clone(); chunks.push( Chunk{ chunk_type: ChunkType::Deletion, a_lines: a_lines[a_cursor..].iter() .map(|s| String::from(*s)).collect(), b_lines: vec![], cursor: chunk_data.start_cursor, } ); chunks.push( Chunk{ chunk_type: ChunkType::Addition, a_lines: vec![], b_lines: b_lines[b_cursor..].iter() .map(|s| String::from(*s)).collect(), cursor: chunk_data.cursor, } ); State::End }, State::End => break, }; } // > Wrap up println!("Done!"); for (i,c) in chunks.iter().enumerate() { println!("\n--- Chunk: {} ---", i); println!("Type: {:?}", c.chunk_type); println!("A: {:?}", c.a_lines); println!("B: {:?}", c.b_lines); } }