diff --git a/src/usr/diff.rs b/src/usr/diff.rs new file mode 100644 index 00000000..1253d2e3 --- /dev/null +++ b/src/usr/diff.rs @@ -0,0 +1,121 @@ +use crate::api::console::Style; +use crate::api::fs; +use crate::api::process::ExitCode; + +use core::cmp; +use alloc::string::String; +use alloc::vec::Vec; +use alloc::vec; + +#[derive(Debug, PartialEq)] +enum Diff { + Equal(String), + Insert(String), + Delete(String), +} + +fn diff(old: &str, new: &str) -> Vec { + let old: Vec<&str> = old.lines().collect(); + let new: Vec<&str> = new.lines().collect(); + backtrack(&old, &new, &lcs(&old, &new)) +} + +fn lcs(old: &[&str], new: &[&str]) -> Vec> { + let n = old.len(); + let m = new.len(); + let mut lcs = vec![vec![0; m + 1]; n + 1]; + + for i in 0..n { + for j in 0..m { + if old[i] == new[j] { + lcs[i + 1][j + 1] = lcs[i][j] + 1; + } else { + lcs[i + 1][j + 1] = cmp::max(lcs[i][j + 1], lcs[i + 1][j]); + } + } + } + + lcs +} + +fn backtrack(old: &[&str], new: &[&str], lcs: &[Vec]) -> Vec { + let mut diff = Vec::new(); + let mut i = old.len(); + let mut j = new.len(); + + while i > 0 && j > 0 { + if old[i - 1] == new[j - 1] { + diff.push(Diff::Equal(old[i - 1].into())); + i -= 1; + j -= 1; + } else if lcs[i][j - 1] > lcs[i - 1][j] { + diff.push(Diff::Insert(new[j - 1].into())); + j -= 1; + } else { + diff.push(Diff::Delete(old[i - 1].into())); + i -= 1; + } + } + + while i > 0 { + diff.push(Diff::Delete(old[i - 1].into())); + i -= 1; + } + + while j > 0 { + diff.push(Diff::Insert(new[j - 1].into())); + j -= 1; + } + + diff.reverse(); + diff +} + +pub fn main(args: &[&str]) -> Result<(), ExitCode> { + let csi_insert = Style::color("green"); + let csi_delete = Style::color("red"); + let csi_reset = Style::reset(); + if let Ok(old) = fs::read_to_string(args[1]) { + if let Ok(new) = fs::read_to_string(args[2]) { + for op in diff(&old, &new) { + match op { + Diff::Equal(line) => { + println!(" {}", line); + } + Diff::Delete(line) => { + println!("{}-{}{}", csi_delete, line, csi_reset); + } + Diff::Insert(line) => { + println!("{}+{}{}", csi_insert, line, csi_reset); + } + } + } + } + } + Ok(()) +} + +#[test_case] +fn test_diff() { + assert_eq!( + diff("a", "a"), + vec![ + Diff::Equal("a".into()) + ] + ); + assert_eq!( + diff("a", "b"), + vec![ + Diff::Insert("b".into()), + Diff::Delete("a".into()) + ] + ); + assert_eq!( + diff("a\nb\nc\n", "a\nc\n"), + vec![ + Diff::Equal("a".into()), + Diff::Delete("b".into()), + Diff::Equal("c".into()) + ] + ); +} diff --git a/src/usr/mod.rs b/src/usr/mod.rs index 2b87ec87..df7b1458 100644 --- a/src/usr/mod.rs +++ b/src/usr/mod.rs @@ -6,6 +6,7 @@ pub mod date; pub mod decode; pub mod delete; pub mod dhcp; +pub mod diff; pub mod disk; pub mod edit; pub mod elf; diff --git a/src/usr/shell.rs b/src/usr/shell.rs index d3736eeb..cf535bef 100644 --- a/src/usr/shell.rs +++ b/src/usr/shell.rs @@ -14,12 +14,12 @@ use alloc::vec::Vec; use core::sync::atomic::{fence, Ordering}; // TODO: Scan /bin -const AUTOCOMPLETE_COMMANDS: [&str; 39] = [ +const AUTOCOMPLETE_COMMANDS: [&str; 40] = [ "2048", "calc", "chess", "copy", "date", "decode", "delete", "dhcp", - "disk", "edit", "elf", "encode", "env", "goto", "hash", "help", "hex", - "host", "http", "httpd", "install", "keyboard", "life", "lisp", "list", - "memory", "move", "net", "pci", "quit", "read", "shell", "socket", "tcp", - "time", "user", "vga", "view", "write", + "diff", "disk", "edit", "elf", "encode", "env", "goto", "hash", "help", + "hex", "host", "http", "httpd", "install", "keyboard", "life", "lisp", + "list", "memory", "move", "net", "pci", "quit", "read", "shell", "socket", + "tcp", "time", "user", "vga", "view", "write", ]; struct Config { @@ -524,6 +524,7 @@ fn dispatch(args: &[&str], config: &mut Config) -> Result<(), ExitCode> { "decode" => usr::decode::main(args), "delete" => usr::delete::main(args), "dhcp" => usr::dhcp::main(args), + "diff" => usr::diff::main(args), "disk" => usr::disk::main(args), "edit" => usr::edit::main(args), "elf" => usr::elf::main(args),