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]
|
||||
clap = { version = "4.5.4", features = ["derive"] }
|
||||
|
||||
# clap = "2.0.0"
|
||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||
serde_json = "1.0"
|
||||
rust-embed = "8.3.0"
|
||||
crossterm = "0.27.0"
|
||||
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"] }
|
||||
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,
|
||||
|
||||
// big
|
||||
/// Show a larger version of the sprite
|
||||
/// Show a bigger version of the sprite
|
||||
#[arg(short, long, default_value_t = false)]
|
||||
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;
|
336
src/main.rs
336
src/main.rs
|
@ -1,279 +1,99 @@
|
|||
use rand::Rng;
|
||||
use clap::Parser;
|
||||
|
||||
// set global constants
|
||||
#[derive(rust_embed::RustEmbed)]
|
||||
#[folder = "colorscripts/"]
|
||||
struct ColorScriptsDir;
|
||||
/*
|
||||
# Arguments
|
||||
|
||||
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";
|
||||
const SHINY_SUBDIR: &str = "shiny";
|
||||
## Print
|
||||
- `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";
|
||||
const SMALL_SUBDIR: &str = "small";
|
||||
/// Pokemon Colorscripts written in Rust
|
||||
#[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] = [
|
||||
("1", (1, 151)),
|
||||
("2", (152, 251)),
|
||||
("3", (252, 386)),
|
||||
("4", (387, 493)),
|
||||
("5", (494, 649)),
|
||||
("6", (650, 721)),
|
||||
("7", (722, 809)),
|
||||
("8", (810, 898)),
|
||||
];
|
||||
// extract destination
|
||||
/// eXtract the colorscripts archive to a specified location
|
||||
#[arg(short = 'x', long = "extract", default_value_t = String::from(""))]
|
||||
extract_destination: String,
|
||||
/*
|
||||
// big
|
||||
/// Show a bigger version of the sprite
|
||||
#[arg(short, long, default_value_t = false)]
|
||||
big: bool,
|
||||
|
||||
fn print_file(filepath: &str) -> std::io::Result<()> {
|
||||
return ColorScriptsDir::get(filepath)
|
||||
.map(|file| {
|
||||
let content = std::str::from_utf8(file.data.as_ref()).unwrap();
|
||||
println!("{}", content);
|
||||
})
|
||||
.ok_or(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"File not found",
|
||||
));
|
||||
}
|
||||
// list
|
||||
/// Show a list of all pokemon names
|
||||
#[arg(short, long, default_value_t = false)]
|
||||
list: bool,
|
||||
|
||||
fn list_pokemon_names() {
|
||||
let pokemon_json: serde_json::Value = serde_json::from_str(POKEMON_JSON).unwrap();
|
||||
// name
|
||||
/// 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 {
|
||||
for pokemon in array {
|
||||
if let Some(name) = pokemon.get("name") {
|
||||
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
|
||||
/// Show the shiny version of the sprite
|
||||
#[arg(short, long, default_value_t = false)]
|
||||
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() {
|
||||
let matches = clap::App::new("rustmon")
|
||||
.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();
|
||||
let args = argument_validation();
|
||||
|
||||
if matches.is_present("list") {
|
||||
list_pokemon_names();
|
||||
} else if matches.is_present("name") {
|
||||
let name = matches.value_of("name").unwrap();
|
||||
let no_title = matches.is_present("no-title");
|
||||
let shiny = matches.is_present("shiny");
|
||||
let big = matches.is_present("big");
|
||||
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("");
|
||||
let no_title = matches.is_present("no-title");
|
||||
let shiny = matches.is_present("shiny");
|
||||
let big = matches.is_present("big");
|
||||
if matches.is_present("form") {
|
||||
println!("--form flag unexpected with --random");
|
||||
if args.fetch == true {
|
||||
// get data directory
|
||||
let data_directory = match dirs::data_dir() {
|
||||
Some(dir) => dir.join("rustmon"),
|
||||
None => {
|
||||
println!("Data directory not found");
|
||||
std::process::exit(1);
|
||||
}
|
||||
show_random_pokemon(random, !no_title, shiny, big).unwrap();
|
||||
};
|
||||
|
||||
// decicde whether to use the default data directory or the one specified by the user
|
||||
// if the user specifies a directory, use that
|
||||
let extract_destination = if args.extract_destination.is_empty() {
|
||||
data_directory
|
||||
} else {
|
||||
// show random pokemon by default with support for other flags
|
||||
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();
|
||||
std::path::PathBuf::from(&args.extract_destination)
|
||||
};
|
||||
|
||||
rustmon::fetch::fetch(&extract_destination, args.silent);
|
||||
} else {
|
||||
println!("print deez nuts");
|
||||
}
|
||||
}
|
||||
|
||||
// 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