sidestepper: yeah its done lol

this language is kinda nice actually
This commit is contained in:
Mark Joshwel 2025-01-21 22:12:25 +08:00
parent fa05392348
commit 5f60a5fad2

View file

@ -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<Behaviour, Box<dyn Error>> {
repo_dir_path: PathBuf::from(&current_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<Behaviour, Box<dyn Error>> {
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<PathBuf> {
// 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<PathBuf>) -> Vec<PathBuf> {
// let mut large_files: Vec<PathBuf> = 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<PathBuf>) -> io::Result<bool> {
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::<Vec<String>>()
} 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())
);
}