fetch functions complete
This commit is contained in:
parent
0df646844a
commit
a4f32ff5b1
1014
Cargo.lock
generated
1014
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
13
Cargo.toml
13
Cargo.toml
|
@ -5,11 +5,12 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.4", features = ["derive"] }
|
clap = { version = "4.5.4", features = ["derive"] }
|
||||||
|
crossterm = "0.27.0"
|
||||||
# clap = "2.0.0"
|
|
||||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
rust-embed = "8.3.0"
|
|
||||||
ctrlc = "3.4.4"
|
ctrlc = "3.4.4"
|
||||||
|
dirs = "5.0.1"
|
||||||
|
image = "0.25.1"
|
||||||
|
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||||
reqwest = { version = "0.11", features = ["json", "blocking"] }
|
reqwest = { version = "0.11", features = ["json", "blocking"] }
|
||||||
zip = "0.5"
|
rust-embed = "8.3.0"
|
||||||
|
serde_json = "1.0.115"
|
||||||
|
zip = "0.6.6"
|
||||||
|
|
|
@ -9,7 +9,7 @@ struct Args {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
||||||
// big
|
// big
|
||||||
/// Show a larger version of the sprite
|
/// Show a bigger version of the sprite
|
||||||
#[arg(short, long, default_value_t = false)]
|
#[arg(short, long, default_value_t = false)]
|
||||||
big: bool,
|
big: bool,
|
||||||
|
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
// this fetches the sprites and downloads to the user's local machine
|
|
||||||
// https://github.com/Vomitblood/pokesprite/archive/refs/heads/master.zip
|
|
||||||
|
|
||||||
// TODO: pass in url as argument and use it
|
|
||||||
// for now, we use this as default
|
|
||||||
const TARGET_URL: &str = "https://github.com/Vomitblood/pokesprite/archive/refs/heads/master.zip";
|
|
||||||
const WORKING_DIRECTORY: &str = "/tmp/rustmon/";
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// // create a working directory for the program to use
|
|
||||||
// match create_working_directory() {
|
|
||||||
// Ok(_) => (),
|
|
||||||
// Err(e) => eprintln!("Error creating working directory: {}", e),
|
|
||||||
// }
|
|
||||||
|
|
||||||
// match download_colorscripts_archive(TARGET_URL) {
|
|
||||||
// Ok(_) => (),
|
|
||||||
// Err(e) => eprintln!("Error downloading file: {}", e),
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TODO: extract here as default unless specified in flags
|
|
||||||
extract_colorscripts_archive(std::path::Path::new(WORKING_DIRECTORY)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_working_directory() -> std::io::Result<()> {
|
|
||||||
println!("Creating working directory at {}...", WORKING_DIRECTORY);
|
|
||||||
std::fs::create_dir(WORKING_DIRECTORY)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn download_colorscripts_archive(target_url: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
println!("Fetching colorscripts archive...");
|
|
||||||
|
|
||||||
let response = reqwest::blocking::get(target_url)?;
|
|
||||||
|
|
||||||
let mut dest = std::fs::File::create(format!("{}colorscripts.zip", WORKING_DIRECTORY))?;
|
|
||||||
|
|
||||||
let response_body = response.error_for_status()?.bytes()?;
|
|
||||||
std::io::copy(&mut response_body.as_ref(), &mut dest)?;
|
|
||||||
|
|
||||||
println!("Downloaded pokesprite.zip");
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_colorscripts_archive(extract_location: &std::path::Path) -> zip::result::ZipResult<()> {
|
|
||||||
// let archive_file = std::fs::File::open(&archive_path)?;
|
|
||||||
let archive_file = std::fs::File::open(std::path::Path::new(
|
|
||||||
format!("{}colorscripts.zip", WORKING_DIRECTORY).as_str(),
|
|
||||||
))?;
|
|
||||||
let mut archive = zip::read::ZipArchive::new(std::io::BufReader::new(archive_file))?;
|
|
||||||
|
|
||||||
for i in 0..archive.len() {
|
|
||||||
let mut file = archive.by_index(i)?;
|
|
||||||
let file_path = std::path::Path::new(file.name());
|
|
||||||
let parent_dir = file_path
|
|
||||||
.parent()
|
|
||||||
.and_then(std::path::Path::file_name)
|
|
||||||
.and_then(std::ffi::OsStr::to_str)
|
|
||||||
.unwrap_or("");
|
|
||||||
|
|
||||||
if (file
|
|
||||||
.name()
|
|
||||||
.starts_with("pokesprite-master/pokemon-gen8/regular/")
|
|
||||||
&& parent_dir == "regular")
|
|
||||||
|| (file
|
|
||||||
.name()
|
|
||||||
.starts_with("pokesprite-master/pokemon-gen8/shiny/")
|
|
||||||
&& parent_dir == "shiny")
|
|
||||||
{
|
|
||||||
let file_name = file_path
|
|
||||||
.file_name()
|
|
||||||
.and_then(std::ffi::OsStr::to_str)
|
|
||||||
.unwrap_or("");
|
|
||||||
|
|
||||||
let outpath = extract_location.join(parent_dir).join(file_name);
|
|
||||||
|
|
||||||
if !file.name().ends_with('/') {
|
|
||||||
if let Some(p) = outpath.parent() {
|
|
||||||
if !p.exists() {
|
|
||||||
std::fs::create_dir_all(&p)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut outfile = std::fs::File::create(&outpath)?;
|
|
||||||
std::io::copy(&mut file, &mut outfile)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
4
src/constants.rs
Normal file
4
src/constants.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
pub const TARGET_URL: &str =
|
||||||
|
"https://github.com/Vomitblood/pokesprite/archive/refs/heads/master.zip";
|
||||||
|
pub const WORKING_DIRECTORY: &str = "/tmp/rustmon/";
|
||||||
|
pub const EXTRACT_DESTINATION: &str = "~/.local/share/rustmon/";
|
349
src/fetch.rs
Normal file
349
src/fetch.rs
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
use image::GenericImageView;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
pub fn fetch(extract_destination: &std::path::Path, silent: bool) {
|
||||||
|
// prep working directory
|
||||||
|
match create_working_directory() {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => eprintln!("Error creating working directory: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
// download colorscripts archive
|
||||||
|
match download_colorscripts_archive(crate::constants::TARGET_URL) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => eprintln!("Error downloading colorscripts archive: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
// extract colorscripts archive
|
||||||
|
// now we have the raw images
|
||||||
|
match extract_colorscripts_archive() {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => eprintln!("Error extracting colorscripts archive: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
// crop images to content
|
||||||
|
match crop_all_images_in_directory() {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => eprintln!("Error cropping images: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
// convert images to unicode, both small and big
|
||||||
|
match convert_images_to_ascii(extract_destination, silent) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => eprintln!("Error converting images to ASCII: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
match cleanup() {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => eprintln!("Error cleaning up: {}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_working_directory() -> std::io::Result<()> {
|
||||||
|
println!(
|
||||||
|
"Creating working directory at {}...",
|
||||||
|
&crate::constants::WORKING_DIRECTORY
|
||||||
|
);
|
||||||
|
// create intermediate directories also
|
||||||
|
std::fs::create_dir(&crate::constants::WORKING_DIRECTORY)?;
|
||||||
|
println!("Created working directory");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn download_colorscripts_archive(target_url: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
println!("Fetching colorscripts archive...");
|
||||||
|
|
||||||
|
let response = reqwest::blocking::get(target_url)?;
|
||||||
|
|
||||||
|
let mut dest = std::fs::File::create(format!(
|
||||||
|
"{}pokesprite.zip",
|
||||||
|
&crate::constants::WORKING_DIRECTORY
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let response_body = response.error_for_status()?.bytes()?;
|
||||||
|
std::io::copy(&mut response_body.as_ref(), &mut dest)?;
|
||||||
|
|
||||||
|
println!("Downloaded colorscripts archive");
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_colorscripts_archive() -> zip::result::ZipResult<()> {
|
||||||
|
println!("Extracting colorscripts archive...");
|
||||||
|
|
||||||
|
let archive_file = std::fs::File::open(std::path::Path::new(
|
||||||
|
format!("{}pokesprite.zip", &crate::constants::WORKING_DIRECTORY).as_str(),
|
||||||
|
))?;
|
||||||
|
let mut archive = zip::read::ZipArchive::new(std::io::BufReader::new(archive_file))?;
|
||||||
|
|
||||||
|
// iterate over every single file in the archive
|
||||||
|
for i in 0..archive.len() {
|
||||||
|
let mut file = archive.by_index(i)?;
|
||||||
|
let file_path = std::path::Path::new(file.name());
|
||||||
|
|
||||||
|
let parent_dir = file_path
|
||||||
|
.parent()
|
||||||
|
.and_then(std::path::Path::file_name)
|
||||||
|
.and_then(std::ffi::OsStr::to_str)
|
||||||
|
.unwrap_or("");
|
||||||
|
|
||||||
|
// check if the file is a in the correct directory that is NOT a directory
|
||||||
|
if (file
|
||||||
|
.name()
|
||||||
|
.starts_with("pokesprite-master/pokemon-gen8/regular/")
|
||||||
|
&& parent_dir == "regular")
|
||||||
|
|| (file
|
||||||
|
.name()
|
||||||
|
.starts_with("pokesprite-master/pokemon-gen8/shiny/")
|
||||||
|
&& parent_dir == "shiny")
|
||||||
|
{
|
||||||
|
let file_name = file_path
|
||||||
|
.file_name()
|
||||||
|
.and_then(std::ffi::OsStr::to_str)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let outpath = std::path::Path::new(&crate::constants::WORKING_DIRECTORY)
|
||||||
|
.join("raw_images")
|
||||||
|
.join(parent_dir)
|
||||||
|
.join(file_name);
|
||||||
|
|
||||||
|
if !file.name().ends_with('/') {
|
||||||
|
if let Some(p) = outpath.parent() {
|
||||||
|
if !p.exists() {
|
||||||
|
std::fs::create_dir_all(&p)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut outfile = std::fs::File::create(&outpath)?;
|
||||||
|
std::io::copy(&mut file, &mut outfile)?;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Extracted colorscripts archive");
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn crop_all_images_in_directory() -> std::io::Result<()> {
|
||||||
|
println!("Cropping images...");
|
||||||
|
|
||||||
|
// make sure the cropped_images directory exists
|
||||||
|
std::fs::create_dir_all(
|
||||||
|
std::path::Path::new(crate::constants::WORKING_DIRECTORY).join("cropped_images"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// do for both regular and shiny subdirectories
|
||||||
|
for subdirectory in ["regular", "shiny"].iter() {
|
||||||
|
let input_subdirectory_path = std::path::Path::new(crate::constants::WORKING_DIRECTORY)
|
||||||
|
.join("raw_images")
|
||||||
|
.join(subdirectory);
|
||||||
|
let output_subdirectory_path = std::path::Path::new(crate::constants::WORKING_DIRECTORY)
|
||||||
|
.join("cropped_images")
|
||||||
|
.join(subdirectory);
|
||||||
|
|
||||||
|
std::fs::create_dir_all(&output_subdirectory_path)?;
|
||||||
|
|
||||||
|
for entry in std::fs::read_dir(&input_subdirectory_path)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
let output_path = output_subdirectory_path.join(path.file_name().unwrap());
|
||||||
|
crop_to_content(&path, &output_path).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Cropped images");
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn crop_to_content(
|
||||||
|
input_path: &std::path::Path,
|
||||||
|
output_path: &std::path::Path,
|
||||||
|
) -> image::ImageResult<image::DynamicImage> {
|
||||||
|
// load the image
|
||||||
|
let img = image::open(input_path)?;
|
||||||
|
|
||||||
|
let (width, height) = img.dimensions();
|
||||||
|
let mut min_x = width;
|
||||||
|
let mut min_y = height;
|
||||||
|
let mut max_x = 0;
|
||||||
|
let mut max_y = 0;
|
||||||
|
|
||||||
|
for y in 0..height {
|
||||||
|
for x in 0..width {
|
||||||
|
let pixel = img.get_pixel(x, y);
|
||||||
|
if pixel[3] != 0 {
|
||||||
|
// if pixel is not transparent
|
||||||
|
if x < min_x {
|
||||||
|
min_x = x;
|
||||||
|
}
|
||||||
|
if y < min_y {
|
||||||
|
min_y = y;
|
||||||
|
}
|
||||||
|
if x > max_x {
|
||||||
|
max_x = x;
|
||||||
|
}
|
||||||
|
if y > max_y {
|
||||||
|
max_y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cropped_width = max_x - min_x + 1;
|
||||||
|
let cropped_height = max_y - min_y + 1;
|
||||||
|
|
||||||
|
let mut cropped_img: image::ImageBuffer<image::Rgba<u8>, Vec<u8>> =
|
||||||
|
image::ImageBuffer::new(cropped_width, cropped_height);
|
||||||
|
|
||||||
|
for y in 0..cropped_height {
|
||||||
|
for x in 0..cropped_width {
|
||||||
|
let pixel = img.get_pixel(x + min_x, y + min_y);
|
||||||
|
cropped_img.put_pixel(x, y, pixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cropped_img = image::DynamicImage::ImageRgba8(cropped_img);
|
||||||
|
|
||||||
|
// write the cropped image
|
||||||
|
cropped_img.save(output_path)?;
|
||||||
|
|
||||||
|
return Ok(cropped_img);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_images_to_ascii(
|
||||||
|
output_directory_path: &std::path::Path,
|
||||||
|
silent: bool,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
println!("Extract destination: {:?}", output_directory_path);
|
||||||
|
println!("Converting images to ASCII...");
|
||||||
|
|
||||||
|
std::fs::create_dir_all(output_directory_path)?;
|
||||||
|
|
||||||
|
for size in ["small", "big"].iter() {
|
||||||
|
for subdirectory in ["regular", "shiny"].iter() {
|
||||||
|
let input_subdirectory_path =
|
||||||
|
std::path::PathBuf::from(crate::constants::WORKING_DIRECTORY)
|
||||||
|
.join("cropped_images")
|
||||||
|
.join(subdirectory);
|
||||||
|
let output_subdirectory_path = output_directory_path
|
||||||
|
.join("colorscripts")
|
||||||
|
.join(size)
|
||||||
|
.join(subdirectory);
|
||||||
|
|
||||||
|
std::fs::create_dir_all(&output_subdirectory_path)?;
|
||||||
|
|
||||||
|
for entry in std::fs::read_dir(&input_subdirectory_path)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if path.is_file() {
|
||||||
|
let img = image::open(&path).unwrap();
|
||||||
|
let ascii_art = if *size == "small" {
|
||||||
|
convert_image_to_unicode_small(&img)
|
||||||
|
} else {
|
||||||
|
convert_image_to_unicode_big(&img)
|
||||||
|
};
|
||||||
|
|
||||||
|
// print for fun
|
||||||
|
if silent == false {
|
||||||
|
println!("{}", ascii_art);
|
||||||
|
};
|
||||||
|
|
||||||
|
let output_path = output_subdirectory_path.join(path.file_stem().unwrap());
|
||||||
|
let mut file = std::fs::File::create(output_path)?;
|
||||||
|
file.write_all(ascii_art.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Converted images to ASCII");
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_image_to_unicode_small(img: &image::DynamicImage) -> String {
|
||||||
|
let mut unicode_sprite = String::new();
|
||||||
|
let (width, height) = img.dimensions();
|
||||||
|
|
||||||
|
for y in (0..height).step_by(2) {
|
||||||
|
for x in 0..width {
|
||||||
|
let upper_pixel = img.get_pixel(x, y);
|
||||||
|
let lower_pixel = if y + 1 < height {
|
||||||
|
img.get_pixel(x, y + 1)
|
||||||
|
} else {
|
||||||
|
upper_pixel // Fallback to upper pixel if there's no lower pixel.
|
||||||
|
};
|
||||||
|
|
||||||
|
if upper_pixel[3] == 0 && lower_pixel[3] == 0 {
|
||||||
|
unicode_sprite.push(' ');
|
||||||
|
} else if upper_pixel[3] == 0 {
|
||||||
|
unicode_sprite.push_str(&get_color_escape_code(lower_pixel, false));
|
||||||
|
unicode_sprite.push('▄');
|
||||||
|
} else if lower_pixel[3] == 0 {
|
||||||
|
unicode_sprite.push_str(&get_color_escape_code(upper_pixel, false));
|
||||||
|
unicode_sprite.push('▀');
|
||||||
|
} else {
|
||||||
|
unicode_sprite.push_str(&get_color_escape_code(upper_pixel, false));
|
||||||
|
unicode_sprite.push_str(&get_color_escape_code(lower_pixel, true));
|
||||||
|
unicode_sprite.push('▀');
|
||||||
|
}
|
||||||
|
unicode_sprite.push_str("\x1b[0m"); // Reset ANSI code after each character
|
||||||
|
}
|
||||||
|
unicode_sprite.push('\n'); // New line for each row, plus reset might be added here too if colors extend beyond.
|
||||||
|
}
|
||||||
|
|
||||||
|
return unicode_sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_image_to_unicode_big(img: &image::DynamicImage) -> String {
|
||||||
|
let mut unicode_sprite = String::new();
|
||||||
|
let (width, height) = img.dimensions();
|
||||||
|
|
||||||
|
for y in 0..height {
|
||||||
|
for x in 0..width {
|
||||||
|
let pixel = img.get_pixel(x, y);
|
||||||
|
|
||||||
|
if pixel[3] == 0 {
|
||||||
|
unicode_sprite.push_str(" ");
|
||||||
|
} else {
|
||||||
|
unicode_sprite.push_str(&get_color_escape_code(pixel, false));
|
||||||
|
unicode_sprite.push_str("██");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unicode_sprite.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return unicode_sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_color_escape_code(pixel: image::Rgba<u8>, background: bool) -> String {
|
||||||
|
if pixel[3] == 0 {
|
||||||
|
return format!("{}", crossterm::style::ResetColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
let color = crossterm::style::Color::Rgb {
|
||||||
|
r: pixel[0],
|
||||||
|
g: pixel[1],
|
||||||
|
b: pixel[2],
|
||||||
|
};
|
||||||
|
|
||||||
|
if background {
|
||||||
|
format!("{}", crossterm::style::SetBackgroundColor(color))
|
||||||
|
} else {
|
||||||
|
format!("{}", crossterm::style::SetForegroundColor(color))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup() -> std::io::Result<()> {
|
||||||
|
println!("Cleaning up...");
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(std::path::Path::new(crate::constants::WORKING_DIRECTORY))?;
|
||||||
|
|
||||||
|
println!("Cleaned up");
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod constants;
|
||||||
|
pub mod fetch;
|
342
src/main.rs
342
src/main.rs
|
@ -1,279 +1,99 @@
|
||||||
use rand::Rng;
|
use clap::Parser;
|
||||||
|
|
||||||
// set global constants
|
/*
|
||||||
#[derive(rust_embed::RustEmbed)]
|
# Arguments
|
||||||
#[folder = "colorscripts/"]
|
|
||||||
struct ColorScriptsDir;
|
|
||||||
|
|
||||||
const POKEMON_JSON: &str = std::include_str!("../pokemon.json");
|
## Fetch
|
||||||
|
- `fetch` - Fetch the latest colorscripts from the repository
|
||||||
|
- `silent` - Don't print colorscripts to the console when generating
|
||||||
|
- `extract_destination` - eXtract the colorscripts archive to a specified location
|
||||||
|
|
||||||
const REGULAR_SUBDIR: &str = "regular";
|
## Print
|
||||||
const SHINY_SUBDIR: &str = "shiny";
|
- `name` - Select pokemon by name
|
||||||
|
- `big` - Show a bigger version of the sprite
|
||||||
|
- `list` - Show a list of all pokemon names
|
||||||
|
- `no-title` - Do not display pokemon name
|
||||||
|
- `shiny` - Show the shiny version of the sprite
|
||||||
|
*/
|
||||||
|
|
||||||
const LARGE_SUBDIR: &str = "large";
|
/// Pokemon Colorscripts written in Rust
|
||||||
const SMALL_SUBDIR: &str = "small";
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
// fetch
|
||||||
|
/// Fetch the latest colorscripts from the repository
|
||||||
|
#[arg(short, long, default_value_t = false)]
|
||||||
|
fetch: bool,
|
||||||
|
|
||||||
const SHINY_RATE: f64 = 1.0 / 128.0;
|
// silent
|
||||||
|
/// Don't print colorscripts to the console when generating
|
||||||
|
#[arg(long = "silent", default_value_t = false)]
|
||||||
|
silent: bool,
|
||||||
|
|
||||||
const GENERATIONS: [(&str, (u32, u32)); 8] = [
|
// extract destination
|
||||||
("1", (1, 151)),
|
/// eXtract the colorscripts archive to a specified location
|
||||||
("2", (152, 251)),
|
#[arg(short = 'x', long = "extract", default_value_t = String::from(""))]
|
||||||
("3", (252, 386)),
|
extract_destination: String,
|
||||||
("4", (387, 493)),
|
/*
|
||||||
("5", (494, 649)),
|
// big
|
||||||
("6", (650, 721)),
|
/// Show a bigger version of the sprite
|
||||||
("7", (722, 809)),
|
#[arg(short, long, default_value_t = false)]
|
||||||
("8", (810, 898)),
|
big: bool,
|
||||||
];
|
|
||||||
|
|
||||||
fn print_file(filepath: &str) -> std::io::Result<()> {
|
// list
|
||||||
return ColorScriptsDir::get(filepath)
|
/// Show a list of all pokemon names
|
||||||
.map(|file| {
|
#[arg(short, long, default_value_t = false)]
|
||||||
let content = std::str::from_utf8(file.data.as_ref()).unwrap();
|
list: bool,
|
||||||
println!("{}", content);
|
|
||||||
})
|
|
||||||
.ok_or(std::io::Error::new(
|
|
||||||
std::io::ErrorKind::NotFound,
|
|
||||||
"File not found",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_pokemon_names() {
|
// name
|
||||||
let pokemon_json: serde_json::Value = serde_json::from_str(POKEMON_JSON).unwrap();
|
/// Select pokemon by name
|
||||||
|
#[arg(short = 'a', long, default_value_t = String::from(""))]
|
||||||
|
name: String,
|
||||||
|
|
||||||
let mut count = 0;
|
// no-title
|
||||||
|
// NOTE: clap will convert the kebab-case to snake_case
|
||||||
|
// very smart!
|
||||||
|
// ...but very annoying for beginners
|
||||||
|
/// Do not display pokemon name
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
no_title: bool,
|
||||||
|
|
||||||
if let serde_json::Value::Array(array) = pokemon_json {
|
// shiny
|
||||||
for pokemon in array {
|
/// Show the shiny version of the sprite
|
||||||
if let Some(name) = pokemon.get("name") {
|
#[arg(short, long, default_value_t = false)]
|
||||||
if let serde_json::Value::String(name_str) = name {
|
|
||||||
println!("{}", name_str);
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("-------------------");
|
|
||||||
println!("Total: {} Pokémons", count);
|
|
||||||
println!("Use the --name flag to view a specific Pokémon");
|
|
||||||
println!("Tip: Use `grep` to search for a specific Pokémon");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_pokemon_by_name(
|
|
||||||
name: &str,
|
|
||||||
show_title: bool,
|
|
||||||
shiny: bool,
|
shiny: bool,
|
||||||
is_large: bool,
|
*/
|
||||||
form: Option<&str>,
|
|
||||||
) -> std::io::Result<()> {
|
|
||||||
// set variables
|
|
||||||
let color_subdir = if shiny { SHINY_SUBDIR } else { REGULAR_SUBDIR };
|
|
||||||
let size_subdir = if is_large { LARGE_SUBDIR } else { SMALL_SUBDIR };
|
|
||||||
|
|
||||||
let pokemon_json: serde_json::Value = serde_json::from_str(POKEMON_JSON)?;
|
|
||||||
|
|
||||||
let pokemon_names: Vec<&str> = pokemon_json
|
|
||||||
.as_array()
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.map(|pokemon| pokemon["name"].as_str().unwrap())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if !pokemon_names.contains(&name) {
|
|
||||||
println!("Invalid pokemon {}", name);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut name = name.to_string();
|
|
||||||
|
|
||||||
if let Some(form) = form {
|
|
||||||
let forms: Vec<&str> = pokemon_json
|
|
||||||
.as_array()
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.filter(|pokemon| pokemon["name"].as_str().unwrap() == name)
|
|
||||||
.flat_map(|pokemon| pokemon["forms"].as_array().unwrap().iter())
|
|
||||||
.map(|form| form.as_str().unwrap())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let alternate_forms: Vec<&str> =
|
|
||||||
forms.iter().filter(|&f| *f != "regular").cloned().collect();
|
|
||||||
|
|
||||||
if alternate_forms.contains(&form) {
|
|
||||||
name.push_str(&format!("-{}", form));
|
|
||||||
} else {
|
|
||||||
println!("Invalid form '{}' for pokemon {}", form, name);
|
|
||||||
if alternate_forms.is_empty() {
|
|
||||||
println!("No alternate forms available for {}", name);
|
|
||||||
} else {
|
|
||||||
println!("Available alternate forms are");
|
|
||||||
for form in alternate_forms {
|
|
||||||
println!("- {}", form);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if show_title {
|
|
||||||
if shiny {
|
|
||||||
println!("{} (shiny)", name);
|
|
||||||
} else {
|
|
||||||
println!("{}", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct the embedded file path
|
|
||||||
let file_path = format!("{}/{}/{}", size_subdir, color_subdir, name);
|
|
||||||
|
|
||||||
// Use the adjusted function to print file contents from embedded resources
|
|
||||||
print_file(&file_path)?;
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_random_pokemon(
|
|
||||||
generations: &str,
|
|
||||||
show_title: bool,
|
|
||||||
shiny: bool,
|
|
||||||
is_large: bool,
|
|
||||||
) -> std::io::Result<()> {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
let start_gen = if generations.is_empty() {
|
|
||||||
"1"
|
|
||||||
} else if generations.contains(",") {
|
|
||||||
let gens: Vec<&str> = generations.split(",").collect();
|
|
||||||
let gen = gens[rng.gen_range(0..gens.len())];
|
|
||||||
gen
|
|
||||||
} else if generations.contains("-") {
|
|
||||||
let gens: Vec<&str> = generations.split("-").collect();
|
|
||||||
gens[0]
|
|
||||||
} else {
|
|
||||||
generations
|
|
||||||
};
|
|
||||||
|
|
||||||
let pokemon_json: serde_json::Value = serde_json::from_str(POKEMON_JSON)?;
|
|
||||||
let pokemon: Vec<String> = pokemon_json
|
|
||||||
.as_array()
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.map(|p| p["name"].as_str().unwrap().to_string())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let generations_map: std::collections::HashMap<_, _> = GENERATIONS.iter().cloned().collect();
|
|
||||||
|
|
||||||
if let Some((start_idx, end_idx)) = generations_map.get(start_gen) {
|
|
||||||
let random_idx = rng.gen_range(*start_idx..=*end_idx);
|
|
||||||
let random_pokemon = &pokemon[random_idx as usize - 1];
|
|
||||||
let shiny = if !shiny {
|
|
||||||
rng.gen::<f64>() <= SHINY_RATE
|
|
||||||
} else {
|
|
||||||
shiny
|
|
||||||
};
|
|
||||||
show_pokemon_by_name(random_pokemon, show_title, shiny, is_large, None)?;
|
|
||||||
} else {
|
|
||||||
println!("Invalid generation '{}'", generations);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn pause() {
|
|
||||||
use std::io::{self, Read, Write};
|
|
||||||
let mut stdout = io::stdout();
|
|
||||||
let mut stdin = io::stdin();
|
|
||||||
|
|
||||||
stdout.write_all(b"Press any key to continue...").unwrap();
|
|
||||||
stdout.flush().unwrap();
|
|
||||||
stdin.read(&mut [0]).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
fn pause() {
|
|
||||||
// do literally nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let matches = clap::App::new("rustmon")
|
let args = argument_validation();
|
||||||
.about("CLI utility to print out unicode image of a pokemon in your shell")
|
|
||||||
.arg(
|
|
||||||
clap::Arg::with_name("list")
|
|
||||||
.short("l")
|
|
||||||
.long("list")
|
|
||||||
.help("Print list of all pokemon"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
clap::Arg::with_name("name")
|
|
||||||
.short("n")
|
|
||||||
.long("name")
|
|
||||||
.value_name("POKEMON NAME")
|
|
||||||
.help("Select pokemon by name. Generally spelled like in the games."),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
clap::Arg::with_name("form")
|
|
||||||
.short("f")
|
|
||||||
.long("form")
|
|
||||||
.value_name("FORM")
|
|
||||||
.help("Show an alternate form of a pokemon"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
clap::Arg::with_name("no-title")
|
|
||||||
.long("no-title")
|
|
||||||
.help("Do not display pokemon name"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
clap::Arg::with_name("shiny")
|
|
||||||
.short("s")
|
|
||||||
.long("shiny")
|
|
||||||
.help("Show the shiny version of the pokemon instead"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
clap::Arg::with_name("big")
|
|
||||||
.short("b")
|
|
||||||
.long("big")
|
|
||||||
.help("Show a larger version of the sprite"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
clap::Arg::with_name("random")
|
|
||||||
.short("r")
|
|
||||||
.long("random")
|
|
||||||
.value_name("GENERATION")
|
|
||||||
.help("Show a random pokemon. This flag can optionally be followed by a generation number or range (1-8) to show random pokemon from a specific generation or range of generations. The generations can be provided as a continuous range (eg. 1-3) or as a list of generations (1,3,6)"),
|
|
||||||
)
|
|
||||||
.after_help("P.S. Use the minimon command for a minimalistic version of this tool")
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
if matches.is_present("list") {
|
if args.fetch == true {
|
||||||
list_pokemon_names();
|
// get data directory
|
||||||
} else if matches.is_present("name") {
|
let data_directory = match dirs::data_dir() {
|
||||||
let name = matches.value_of("name").unwrap();
|
Some(dir) => dir.join("rustmon"),
|
||||||
let no_title = matches.is_present("no-title");
|
None => {
|
||||||
let shiny = matches.is_present("shiny");
|
println!("Data directory not found");
|
||||||
let big = matches.is_present("big");
|
std::process::exit(1);
|
||||||
let form = matches.value_of("form");
|
}
|
||||||
show_pokemon_by_name(name, !no_title, shiny, big, form).unwrap();
|
};
|
||||||
} else if matches.is_present("random") {
|
|
||||||
let random = matches.value_of("random").unwrap_or("");
|
// decicde whether to use the default data directory or the one specified by the user
|
||||||
let no_title = matches.is_present("no-title");
|
// if the user specifies a directory, use that
|
||||||
let shiny = matches.is_present("shiny");
|
let extract_destination = if args.extract_destination.is_empty() {
|
||||||
let big = matches.is_present("big");
|
data_directory
|
||||||
if matches.is_present("form") {
|
} else {
|
||||||
println!("--form flag unexpected with --random");
|
std::path::PathBuf::from(&args.extract_destination)
|
||||||
std::process::exit(1);
|
};
|
||||||
}
|
|
||||||
show_random_pokemon(random, !no_title, shiny, big).unwrap();
|
rustmon::fetch::fetch(&extract_destination, args.silent);
|
||||||
} else {
|
} else {
|
||||||
// show random pokemon by default with support for other flags
|
println!("print deez nuts");
|
||||||
let no_title = matches.is_present("no-title");
|
|
||||||
let shiny = matches.is_present("shiny");
|
|
||||||
let big = matches.is_present("big");
|
|
||||||
show_random_pokemon("", !no_title, shiny, big).unwrap();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// pause the program before exiting only for windows
|
|
||||||
pause();
|
fn argument_validation() -> Args {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
return args;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue