From 7667695eba44f94c064708f5f4c54f78d685b024 Mon Sep 17 00:00:00 2001 From: Mel Date: Mon, 20 Sep 2021 00:51:47 +0200 Subject: Prototype --- .gitignore | 3 + Cargo.lock | 96 +++++++++++++++++++++ Cargo.toml | 11 +++ src/main.rs | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 389 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fbe5f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +.ka +.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..66a29be --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,96 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "ka" +version = "0.1.0" +dependencies = [ + "difference", + "serde", + "serde_json", +] + +[[package]] +name = "proc-macro2" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8e83cc7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ka" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = {version = "1.0", features = ["derive"] } +serde_json = "1.0" +difference = "2.0.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..fcbb4d0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,279 @@ +use difference::{Changeset, Difference}; +use serde::{Deserialize, Serialize}; +use std::{ + env, + fs::{self, DirEntry, File, OpenOptions}, + io::{self, Read, Seek, Write}, + path::{Path, PathBuf}, + vec, +}; + +fn main() { + let args: Vec = env::args().collect(); + let command = args[1].as_str(); + match command { + "create" => { + create_command().unwrap(); + } + "update" => { + update_command().unwrap(); + } + "shift" => { + let file_path = args[2].as_str(); + + let new_cursor: usize = args[3].as_str().parse().expect("Invalid cursor."); + + shift_command(file_path, new_cursor); + } + _ => println!("Unknown command: {}", command), + } +} + +fn create_command() -> io::Result<()> { + let repository_directory = Path::new("./repository"); + + let ka_directory = repository_directory.join(".ka"); + let ka_files_directory = ka_directory.join("files"); + + if ka_directory.exists() { + fs::remove_dir_all(ka_directory.as_path())?; + } + + fs::create_dir(ka_directory)?; + fs::create_dir(ka_files_directory)?; + + update_command()?; + + Ok(()) +} + +fn update_command() -> io::Result<()> { + let repository_directory = Path::new("./repository"); + + let ka_directory = repository_directory.join(".ka"); + + let entries: Vec = fs::read_dir(repository_directory)? + .map(Result::unwrap) + .filter(|entry| entry.path() != ka_directory) + .flat_map(flatten_directories) + .collect(); + + for element in entries.iter() { + update_file(element.path())?; + } + + Ok(()) +} + +fn shift_command(path: &str, new_cursor: usize) { + let repository_directory = Path::new("./repository"); + + let ka_directory = repository_directory.join(".ka"); + let ka_files_directory = ka_directory.join("files"); + + let file_path = Path::new(path); + + let mut tracked_file = File::create(file_path).expect("Could not find file."); + + let history_file_path = + ka_files_directory.join(file_path.strip_prefix(repository_directory).unwrap()); + let mut history_file = OpenOptions::new() + .write(true) + .read(true) + .open(history_file_path) + .expect("Could not find corresponding history file"); + + let mut current_history = + read_history_from_file(&mut history_file).expect("Failed reading history from file."); + let state_at_cursor = get_state_from_history_for_cursor(¤t_history, new_cursor); + + println!("{}", state_at_cursor); + println!("{:?}", current_history); + + tracked_file + .write_all(state_at_cursor.as_bytes()) + .expect("Failed writing new state"); + + current_history.cursor = new_cursor; + write_history_to_file(&mut history_file, ¤t_history) + .expect("Could not write new cursor to history file."); +} + +fn flatten_directories(entry: DirEntry) -> Vec { + let file_type = entry.file_type().expect("Could not read a file type."); + if file_type.is_dir() { + fs::read_dir(entry.path()) + .expect("Could not read nested directory.") + .map(Result::unwrap) + .flat_map(flatten_directories) + .collect() + } else { + vec![entry] + } +} + +fn update_file(file_path: PathBuf) -> io::Result<()> { + let repository_directory = Path::new("./repository"); + + let ka_directory = repository_directory.join(".ka"); + let ka_files_directory = ka_directory.join("files"); + + let mut tracked_file = File::open(file_path.as_path()).expect("Could not find tracked file."); + + let history_file_path = + ka_files_directory.join(file_path.strip_prefix(repository_directory).unwrap()); + + let (mut history_file, current_file_history) = if history_file_path.exists() { + let mut history_file = OpenOptions::new() + .read(true) + .write(true) + .open(history_file_path) + .expect("Could not open history file."); + + let file_history = read_history_from_file(&mut history_file).unwrap(); + + history_file + .set_len(0) + .expect("Failed truncating old history file."); + + history_file + .rewind() + .expect("Failed rewinding history file."); + + (history_file, file_history) + } else { + history_file_path.parent().map(|dir_path| { + if !dir_path.exists() { + fs::create_dir_all(dir_path).unwrap(); + } + }); + + let file = File::create(history_file_path).expect("Could not create history file."); + + let empty_file_history = FileHistory { + cursor: 0, + changes: Vec::new(), + }; + + (file, empty_file_history) + }; + + let mut new_content = String::new(); + tracked_file + .read_to_string(&mut new_content) + .expect("Could not read tracked file."); + + let old_content = + get_state_from_history_for_cursor(¤t_file_history, current_file_history.cursor); + + let change_set = Changeset::new(&old_content, &new_content, ""); + + let mut changes = Vec::new(); + let mut at: usize = 0; + for diff in change_set.diffs.iter() { + match diff { + Difference::Add(new_content) => { + let change = TextChange::Inserted(TextInserted { + at, + new_content: new_content.to_owned(), + }); + at += new_content.len(); + changes.push(change); + } + Difference::Rem(removed_content) => { + changes.push(TextChange::Deleted(TextDeleted { + at, + upto: removed_content.len(), + })); + } + Difference::Same(same_content) => { + at += same_content.len(); + } + } + } + + let mut file_changes = current_file_history.changes; + println!("{:?}", file_changes); + file_changes.push(FileChange::Updated(FileUpdated { changes })); + + let new_file_history = FileHistory { + changes: file_changes, + cursor: current_file_history.cursor + 1, + }; + + write_history_to_file(&mut history_file, &new_file_history) + .expect("Failed writing new history."); + + Ok(()) +} + +fn write_history_to_file(file: &mut File, history: &FileHistory) -> io::Result<()> { + let encoded: Vec = serde_json::to_vec(history).unwrap(); + file.write_all(encoded.as_ref())?; + Ok(()) +} + +fn read_history_from_file(file: &mut File) -> Result { + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer) + .expect("Could not read file history,"); + + let file_history = serde_json::from_slice::(&buffer); + Ok(file_history.expect("Corrupted file history.")) +} + +fn get_state_from_history_for_cursor(history: &FileHistory, cursor: usize) -> String { + let mut buffer = String::new(); + for file_change in history.changes.iter().take(cursor) { + if let FileChange::Updated(ref updated) = file_change { + for change in updated.changes.iter() { + match change { + TextChange::Deleted(deletion) => { + buffer.replace_range(deletion.at..deletion.upto, ""); + } + TextChange::Inserted(insertion) => { + buffer.insert_str(insertion.at, insertion.new_content.as_str()); + } + } + } + } else { + buffer = String::new(); + } + } + buffer +} + +#[derive(Serialize, Deserialize, Debug)] +struct FileHistory { + cursor: usize, + changes: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +enum FileChange { + Updated(FileUpdated), + Deleted, +} + +#[derive(Serialize, Deserialize, Debug)] +struct FileUpdated { + changes: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +enum TextChange { + Inserted(TextInserted), + Deleted(TextDeleted), +} + +#[derive(Serialize, Deserialize, Debug)] +struct TextInserted { + at: usize, + new_content: String, +} + +#[derive(Serialize, Deserialize, Debug)] +struct TextDeleted { + at: usize, + upto: usize, +} -- cgit 1.4.1