use clap::Parser;
use std::process::exit;
use std::path::PathBuf;
use std::fs::{read_to_string, write};
use full2half::CharacterWidth;
#[derive(Parser, Debug)]
#[command(version, override_help = HELP, version = "v0.1.1", arg_required_else_help = true)]
struct Args {
#[arg(short = 'f', long)]
file: Option<String>,
#[arg(short = 'c', long, default_value_t = false)]
copy: bool,
#[arg(short = 'a', long, default_value_t = false)]
alpha: bool,
#[arg(short = 's', long, default_value_t = false)]
symbols: bool,
#[arg(short = 'j', long, default_value_t = false)]
kana: bool,
#[arg(short = 'k', long, default_value_t = false)]
hangul: bool,
#[arg(short = 'i', long, value_delimiter = ' ', num_args = 0..)]
ignore: Option<Vec<String>>,
#[arg(short = 'r', long, default_value_t = false)]
reverse: bool,
#[arg()]
string: Option<String>,
}
const HELP: &str = r#"ㅤ
________ ___ ___ ___ ___ _______ ___ ___ ________ ___ ________
|\ _____\\ \|\ \|\ \ |\ \ / ___ \|\ \|\ \|\ __ \|\ \ |\ _____\
\ \ \__/\ \ \\\ \ \ \ \ \ \ /__/|_/ /\ \ \\\ \ \ \|\ \ \ \ \ \ \__/
\ \ __\\ \ \\\ \ \ \ \ \ \ |__|// / /\ \ __ \ \ __ \ \ \ \ \ __\
\ \ \_| \ \ \\\ \ \ \____\ \ \____ / /_/__\ \ \ \ \ \ \ \ \ \ \____\ \ \_|
\ \__\ \ \_______\ \_______\ \_______\\________\ \__\ \__\ \__\ \__\ \_______\ \__\
\|__| \|_______|\|_______|\|_______|\|_______|\|__|\|__|\|__|\|__|\|_______|\|__|
------------------------------------------------------------------------------------------
CLI for converting full-width characters to half-width characters and vice versa.
https://gitlab.com/pSchwietzer/full2half
------------------------------------------------------------------------------------------
USAGE:
full2half [OPTIONS] <STRING>
full2half [OPTIONS] -f <FILE>
ARGUMENTS:
<STRING> String to convert, only if no file is specified
<FILE> File to convert, only if no string is specified
OPTIONS:
-f, --file File to do the conversion on
-c, --copy Copy file instead of overwriting it
-a, --alpha Activate / Deactivate alpha numeric conversion
[default: true]
-s, --symbols Activate / Deactivate symbol conversion
[default: true]
-j, --kana Activate / Deactivate kana conversion
[default: true]
-k, --hangul Activate / Deactivate hangul conversion
[default: true]
-i, --ignore Set specific characters to ignore,
Characters must be part of alpha, symbols, kana or hangul sets.
-r, --reverse Set conversion from halfwidth to fullwidth
[default: false]
------------------------------------------------------------------------------------------
"#;
fn main() {
let args = Args::parse();
if args.string.is_some() && args.file.is_some() {
eprintln!("You can't convert a file and string at the same time. Try only using the '-f' argument without a string or not using a string with the '-f' argument.");
exit(1);
}
let ignore: Vec<&str> = match args.ignore {
Some(ref values) => values.iter().map(|x| x.as_str()).collect(),
None => Vec::new(),
};
if args.string.is_some() {
let result = match args.reverse {
true => args.string.unwrap().to_full_width_ext(ignore.clone(), !args.alpha, !args.symbols, !args.kana, !args.hangul),
false => args.string.unwrap().to_half_width_ext(ignore.clone(), !args.alpha, !args.symbols, !args.kana, !args.hangul),
};
if result.is_ok() {
println!("{}", result.unwrap());
exit(0);
} else {
eprintln!("{:?}", result.err().unwrap());
exit(1);
}
}
if args.file.is_some() {
let file_path = PathBuf::from(args.file.unwrap());
if !file_path.exists() && !file_path.is_file() {
eprintln!("'{:?}' file does not exist.", file_path);
exit(1);
}
let original_content = match read_to_string(&file_path) {
Ok(content) => content,
Err(err) => {
eprintln!("{}", err);
exit(1);
}
};
let converted_content = match args.reverse {
true => original_content.to_full_width_ext(ignore.clone(), !args.alpha, !args.symbols, !args.kana, !args.hangul),
false => original_content.to_half_width_ext(ignore.clone(), !args.alpha, !args.symbols, !args.kana, !args.hangul),
};
let file_path = if args.copy {
find_file_copy_name(&file_path).unwrap_or_else(|err| {
eprintln!("Failed to find a suitable name for the copy: {}", err);
exit(1);
})
} else {
file_path
};
if let Err(err) = converted_content {
eprintln!("{:?}", err);
exit(1);
}
if let Err(err) = write(&file_path, converted_content.unwrap()) {
eprintln!("{}", err);
exit(1);
} else {
println!("Successfully converted and written to file: {:?}.", file_path);
exit(0);
}
}
eprintln!("No string or file specified. Try using the '-f' argument with a file or a string without the '-f' argument.");
exit(1);
}
fn find_file_copy_name(original_path: &PathBuf) -> Result<PathBuf, String> {
let mut counter = 1;
let max_counter = 999;
if !original_path.exists() {
return Err(format!("File '{:?}' does not exist.", original_path));
}
if !original_path.is_file() {
return Err(format!("'{:?}' is not a file.", original_path));
}
while counter <= max_counter {
let mut copy_name = format!(
"{} ({})",
original_path.file_stem().unwrap().to_string_lossy(),
counter
);
if original_path.extension().is_some() {
copy_name.push_str(&format!(".{}", original_path.extension().unwrap().to_string_lossy()));
}
let copy_path = original_path.with_file_name(copy_name);
if !copy_path.exists() {
return Ok(copy_path);
}
counter += 1;
}
Err(format!("Exceeded maximum attempts to find a unique copy name. Maximum attempts: {}", max_counter))
}