From 5f60a5fad271f595336354978eedf690970cfb99 Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Tue, 21 Jan 2025 22:12:25 +0800 Subject: [PATCH] sidestepper: yeah its done lol this language is kinda nice actually --- src/main.rs | 188 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 180 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index ccca32f..1566d23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,15 +14,19 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -use std::env; use std::error::Error; +use std::fs::metadata; +use std::io::Write; use std::path::{Path, PathBuf}; +use std::time::{Duration, SystemTime}; +use std::{env, fs, io}; -const SOTA_SIDESTEP_CHUNK_SIZE: u16 = 16; -const SOTA_SIDESTEP_MAX_WORKERS: u16 = 4; +use ignore; + +// const SOTA_SIDESTEP_CHUNK_SIZE: u16 = 16; +// const SOTA_SIDESTEP_MAX_WORKERS: u16 = 4; const SOTA_SIDESTEP_LARGE_FILE_SIZE: u64 = 100000000; // 100mb -#[derive(Debug)] struct Behaviour { repo_dir_path: PathBuf, repo_sotaignore_path: PathBuf, @@ -30,7 +34,6 @@ struct Behaviour { // chunk_size: u16, // max_workers: u16, large_file_size: u64, - search_here: bool, plumbing: bool, } @@ -83,7 +86,6 @@ fn cli_get_behaviour() -> Result> { repo_dir_path: PathBuf::from(¤t_dir), repo_sotaignore_path: PathBuf::from(current_dir.join(".sotaignore")), large_file_size, - search_here, plumbing, }); } @@ -114,13 +116,137 @@ fn cli_get_behaviour() -> Result> { repo_dir_path: PathBuf::from(repo_dir_path), repo_sotaignore_path: PathBuf::from(repo_dir_path.join(".sotaignore")), large_file_size, - search_here, plumbing, }) } +fn ss_scan_for_unignored_files(behaviour: &Behaviour) -> Vec { + // for file in ignore::WalkBuilder::new(&behaviour.repo_dir_path) + // .hidden(false) + // .build() + // .into_iter() + // .filter_map(|e| e.ok()) + // { + // if file + // .path() + // .starts_with(Path::new(&behaviour.repo_dir_path).join(".git/")) + // { + // continue; + // } + // if file.path().is_file() { + // files.push(file.into_path()); + // } + // } + ignore::WalkBuilder::new(&behaviour.repo_dir_path) + .hidden(false) + .build() + .filter_map(|e| e.ok()) + .filter(|file| { + !file + .path() + .starts_with(Path::new(&behaviour.repo_dir_path).join(".git/")) + && file.path().is_file() + }) + .map(|file| file.into_path()) + .collect() +} + +fn ss_check_for_large_files(behaviour: &Behaviour, files: &Vec) -> Vec { + // let mut large_files: Vec = Vec::new(); + // for file in files { + // let result = metadata(file); + // if let Err(_) = result { + // continue; + // } + // let metadata = result.unwrap(); + // if metadata.len() >= behaviour.large_file_size { + // large_files.push(file.into()); + // } + // } + files + .iter() + .filter_map(|file| { + metadata(file) + .ok() + .filter(|meta| meta.len() >= behaviour.large_file_size) + .map(|_| file.into()) + }) + .collect() +} + +fn ss_write_sotaignore(behaviour: &Behaviour, large_files: &Vec) -> io::Result { + if large_files.is_empty() { + return Ok(false); + } + + // are we outputting to stdout for other programs? + // do so and return true, we did write something + if behaviour.plumbing { + eprintln!(); + for file in large_files { + println!("{}", file.to_str().unwrap()); + } + return Ok(true); + } + + let old_sotaignore = if behaviour.repo_sotaignore_path.try_exists().ok() == Some(true) { + fs::read_to_string(&behaviour.repo_sotaignore_path)? + .lines() + .map(String::from) + .collect::>() + } else { + Vec::new() + }; + + let mut new_sotaignore = old_sotaignore.clone(); + for file in large_files { + if let Ok(file_relative) = file.strip_prefix(&behaviour.repo_dir_path) { + let relative_path_str = file_relative.to_string_lossy(); + if !old_sotaignore.contains(&relative_path_str.to_string()) { + new_sotaignore.push(relative_path_str.to_string()); + } + } + } + + // no new changes? return, nothing has been written + if new_sotaignore == old_sotaignore { + return Ok(false); + } + + // check if the sotaignore file starts with a comment + if !new_sotaignore.is_empty() & !new_sotaignore[0].starts_with("#") { + let header = vec![ + "# .sotaignore file generated by sota staircase ReStepper/SideStepper", + "# anything here either can't or shouldn't be uploaded to GitHub", + "# unless you know what you're doing, don't edit this file! >:(", + ]; + new_sotaignore.splice(0..0, header.iter().map(|&line| line.to_string())); + } + + let mut sotaignore_file = fs::File::create(&behaviour.repo_sotaignore_path)?; + sotaignore_file.write_all(new_sotaignore.join("\n").as_bytes())?; + sotaignore_file.write_all(b"\n")?; + + Ok(true) +} + +fn format_elapsed_time(secs: f64) -> String { + let hours = (secs / 3600.0).floor() as i64; + let minutes = ((secs % 3600.0) / 60.0).floor() as i64; + let seconds = (secs % 60.0).round() as f64; + let secs_string: String; + if secs > 3600.0 { + secs_string = format!("{}h {}′ {:.1}″", hours, minutes, seconds); + } else if secs > 60.0 { + secs_string = format!("{}′ {:.2}″", minutes, seconds); + } else { + secs_string = format!("{:.3}″", secs); + } + secs_string +} + fn main() { - eprintln!("\nsota staircase SideStepper v5 (i3/a4)"); + eprintln!("\nsota staircase SideStepper v5 (i3/a5)"); let behaviour = { let behaviour = cli_get_behaviour(); // huh. pattern matching consumes the variable, so we ref (&) it. damn. @@ -151,4 +277,50 @@ fn main() { } }, ); + + let zero_duration = Duration::new(0, 0); + let all = SystemTime::now(); + + eprint!("1/3 scanning repository... "); + let now = SystemTime::now(); + let files = ss_scan_for_unignored_files(&behaviour); + eprintln!( + "done in {} (found {})", + format_elapsed_time(now.elapsed().unwrap_or(zero_duration).as_secs_f64()), + files.len() + ); + + eprint!("2/3 finding large files... "); + let now = SystemTime::now(); + let large_files = ss_check_for_large_files(&behaviour, &files); + eprintln!( + "done in {} (found {})", + format_elapsed_time(now.elapsed().unwrap_or(zero_duration).as_secs_f64()), + large_files.len() + ); + + eprint!("3/3 writing .sotaignore file... "); + match ss_write_sotaignore(&behaviour, &large_files) { + Ok(true) => { + eprintln!( + "{}", + if behaviour.plumbing { + "done (to stdout)" + } else { + "done" + } + ); + } + Ok(false) => { + eprintln!("skipped") + } + Err(e) => { + eprintln!("error ({})", e) + } + } + + eprintln!( + "\n--- done! took {} ″~ ☆*: .。. o(≧▽≦)o .。.:*☆ ---\n", + format_elapsed_time(all.elapsed().unwrap_or(zero_duration).as_secs_f64()) + ); }