aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2021-09-20 00:51:47 +0200
committerMel <einebeere@gmail.com>2021-09-20 00:51:47 +0200
commit7667695eba44f94c064708f5f4c54f78d685b024 (patch)
tree5bd04b5854eebd680c2d4e45def7116d44ebaafa /src
downloadka-7667695eba44f94c064708f5f4c54f78d685b024.tar.zst
ka-7667695eba44f94c064708f5f4c54f78d685b024.zip
Prototype
Diffstat (limited to 'src')
-rw-r--r--src/main.rs279
1 files changed, 279 insertions, 0 deletions
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,
+}