Works as an MVP

This commit is contained in:
Ward Wouts 2024-11-20 10:49:18 +01:00
parent a0014cf247
commit 3ba9097489
3 changed files with 329 additions and 20 deletions

196
Cargo.lock generated
View file

@ -8,12 +8,50 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "console"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"unicode-width",
"windows-sys 0.52.0",
]
[[package]]
name = "dialoguer"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
dependencies = [
"console",
"shell-words",
"tempfile",
"thiserror",
"zeroize",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.9" version = "0.3.9"
@ -30,6 +68,23 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.164" version = "0.2.164"
@ -46,6 +101,8 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
name = "mvw" name = "mvw"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"dialoguer",
"rand",
"tempfile", "tempfile",
] ]
@ -55,6 +112,63 @@ version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.41" version = "0.38.41"
@ -68,6 +182,23 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "syn"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.14.0" version = "3.14.0"
@ -81,6 +212,44 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"
@ -162,3 +331,30 @@ name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"

View file

@ -5,3 +5,5 @@ edition = "2021"
[dependencies] [dependencies]
tempfile = "3.14.0" tempfile = "3.14.0"
dialoguer = "0.11.0"
rand = "0.8.5"

View file

@ -1,13 +1,24 @@
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::path::Path; use std::io::{ErrorKind, Write, BufRead, BufReader};
use std::io::ErrorKind; use std::process;
use std::io::Write; use std::process::Command;
use std::collections::HashMap;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use dialoguer::Confirm;
use rand::distributions::{Alphanumeric, DistString};
const LOCK_FILE: &str = ".mvwrap"; const LOCK_FILE: &str = ".mvwrap";
struct Cleanup;
impl Drop for Cleanup {
fn drop(&mut self) {
unlock_dir();
}
}
fn read_current_dir() -> Vec<String> { fn read_current_dir() -> Vec<String> {
let paths : Vec<PathBuf> = match fs::read_dir(".") { let paths : Vec<PathBuf> = match fs::read_dir(".") {
Err(e) if e.kind() == ErrorKind::NotFound => Vec::new(), Err(e) if e.kind() == ErrorKind::NotFound => Vec::new(),
@ -47,7 +58,7 @@ fn unlock_dir() {
} }
} }
fn create_temp_file(paths: Vec<String>) -> String { fn create_temp_file(paths: &Vec<String>) -> String {
let mut tmpfile = match NamedTempFile::new(){ let mut tmpfile = match NamedTempFile::new(){
Ok(file) => file, Ok(file) => file,
Err(e) => panic!("Could not create tempfile: {}", e), Err(e) => panic!("Could not create tempfile: {}", e),
@ -67,35 +78,135 @@ fn create_temp_file(paths: Vec<String>) -> String {
filepath filepath
} }
fn remove_temp_file(tmpfile: String) { fn remove_temp_file(tmpfile: &String) {
if Path::new(&tmpfile).is_file() { if Path::new(tmpfile).is_file() {
let _result = match fs::remove_file(&tmpfile){ let _result = match fs::remove_file(tmpfile){
Ok(result) => result, Ok(result) => result,
Err(_) => panic!("Could not remove tempfile: {}", &tmpfile), Err(_) => panic!("Could not remove tempfile: {}", tmpfile),
}; };
} }
} }
fn main() { fn edit_temp_file(tmpfile: &String) {
lock_dir(); let _output = Command::new("vi")
.arg(tmpfile)
.status()
.expect("failed to execute editor process");
}
fn read_temp_file(tmpfile: &String) -> Vec<String> {
let file = File::open(tmpfile).expect("no such file");
let buf = BufReader::new(file);
buf.lines()
.map(|l| l.expect("Could not parse line"))
.collect()
}
fn unique_length(dst_paths: &Vec<String>) -> usize {
let map = dst_paths.into_iter().map(|x| (x, x)).collect::<HashMap<_, _>>();
map.len()
}
fn unique_filename(dst_paths: &Vec<String>) -> String {
let mut string = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
// Generate unique name that does not exist in dir and is not in dst_paths
while dst_paths.iter().any(|j| j==&string) || Path::new(&string).is_file() {
string = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
}
string
}
fn move_safely(src_paths: &Vec<String>, dst_paths: &Vec<String>) {
let src_len = src_paths.len();
let mut intermediate_files: HashMap<String, String> = HashMap::new();
for i in 0..src_len {
if src_paths[i] != dst_paths[i] {
// is the destination already in the source list?
// if so, an intermediate file is needed
if src_paths.iter().any(|j| j==&dst_paths[i]) {
let unique = unique_filename(&dst_paths);
intermediate_files.insert(unique.clone(), dst_paths[i].clone());
fs::rename(&src_paths[i], &unique).expect("failed to rename file");
println!("Moving {} -> {}", src_paths[i], unique);
} else {
fs::rename(&src_paths[i], &dst_paths[i]).expect("failed to rename file");
println!("Moving {} -> {}", src_paths[i], dst_paths[i]);
}
}
}
for (src, dst) in intermediate_files.iter() {
fs::rename(&src, &dst).expect("failed to rename file");
println!("Moving {} -> {}", src, dst);
}
}
fn run_checks(src_paths: &Vec<String>, dst_paths: &Vec<String>) -> bool {
// Make sure there are an equal number of sources and destinations
if src_paths.len() != dst_paths.len() {
println!("ERROR: Source and target list don't have the same number of lines.");
return true
}
// Make sure destination names are not empty
if dst_paths.iter().any(|i| i=="") {
println!("ERROR: You can't move to empty names.");
return true
}
// Make sure all destination files are unique
let dst_paths_length_unique = unique_length(&dst_paths);
if dst_paths.len() != dst_paths_length_unique {
println!("Clashing destination names!");
// TODO: show which ones!
return true
}
false
}
fn main() {
// Place lockfile to not have multiple mvw processes running in the same dir at the same time
lock_dir();
let _cleanup = Cleanup; // Cleanup LOCK_FILE on panic
// read directory contents into vector of Strings
let paths = read_current_dir(); let paths = read_current_dir();
// create named tempfile and fill with paths // create named tempfile and fill with paths
let temp_file = create_temp_file(paths); let temp_file = create_temp_file(&paths);
// edit tempfile //display_temp_file(&temp_file);
// read and process tempfile edit_temp_file(&temp_file);
println!("In file: {}", temp_file);
let contents = fs::read_to_string(temp_file.clone()) let mut new_paths = read_temp_file(&temp_file);
.expect("Should have been able to read the file");
println!("With text:\n{contents}"); // Compare length, if not equal offer to try again (or panic for now)
println!("----- END ----"); while run_checks(&paths, &new_paths) {
remove_temp_file(temp_file); let confirmation = Confirm::new()
.with_prompt("Continue editing?")
.interact()
.unwrap();
if ! confirmation {
unlock_dir();
process::exit(1);
}
edit_temp_file(&temp_file);
new_paths = read_temp_file(&temp_file);
}
// (also don't overwrite existing files that are not in the input list!)
move_safely(&paths, &new_paths);
//display_temp_file(&temp_file);
remove_temp_file(&temp_file);
unlock_dir(); unlock_dir();
} }