aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Cargo.lock96
-rw-r--r--Cargo.toml11
-rw-r--r--src/main.rs279
4 files changed, 389 insertions, 0 deletions
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<String> = 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<DirEntry> = 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(&current_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, &current_history)
+ .expect("Could not write new cursor to history file.");
+}
+
+fn flatten_directories(entry: DirEntry) -> Vec<DirEntry> {
+ 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(&current_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<u8> = serde_json::to_vec(history).unwrap();
+ file.write_all(encoded.as_ref())?;
+ Ok(())
+}
+
+fn read_history_from_file(file: &mut File) -> Result<FileHistory, ()> {
+ let mut buffer = Vec::new();
+ file.read_to_end(&mut buffer)
+ .expect("Could not read file history,");
+
+ let file_history = serde_json::from_slice::<FileHistory>(&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<FileChange>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+enum FileChange {
+ Updated(FileUpdated),
+ Deleted,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+struct FileUpdated {
+ changes: Vec<TextChange>,
+}
+
+#[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,
+}