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);
}
}