diff --git a/src/kanji_search.rs b/src/kanji_search.rs new file mode 100644 index 0000000..61628f9 --- /dev/null +++ b/src/kanji_search.rs @@ -0,0 +1,161 @@ +use std::{ + io::{stdin, stdout, Write}, + path::PathBuf, + collections::HashSet, +}; + +use colored::*; +use kradical_parsing::radk; + +pub fn search_by_radical(mut query: &mut String){ + let mut result: HashSet<_> = HashSet::new(); + let mut aux: HashSet<_> = HashSet::new(); + let path = get_radkfile_path(); + + match radk::parse_file(path.unwrap()) { /* if it doesn't exist, just panic */ + Ok(radk_list) => { + result.clear(); + + /* First iteration: get the baseline for the results */ + let mut rad = query.chars().nth(1).unwrap(); + if rad == '*' || rad == '*' { + /* if search_by_radical returned an error then something is very wrong */ + rad = search_by_strokes(&mut query, &radk_list, 1).expect("Couldn't parse input"); + } + + for k in radk_list.iter() { + if k.radical.glyph.contains(rad) { + for input in &k.kanji { + result.insert(input); + } + break; + } + } + + /* Iterate until you've exhausted user input: refine the baseline to get final output */ + for (i, mut rad) in query.clone().chars().skip(2).enumerate() { + if rad == '*' || rad == '*' { + /* if search_by_radical returned an error then something is very wrong */ + rad = search_by_strokes(&mut query, &radk_list, i+2).expect("Couldn't parse input"); + } + + for k in radk_list.iter() { + if k.radical.glyph.contains(rad) { + for input in &k.kanji { + aux.insert(input); + } + result = &result & &aux; + aux.clear(); + break; + } + } + } + for r in result { + print!("{r} "); + } + println!(); + } + Err(_e) => eprintln!("Error while reading radkfile\nIf you don't have the radkfile, download it from \ + https://www.edrdg.org/krad/kradinf.html and place it in \"~/.local/share/\" on Linux or \"~\\AppData\\Local\\\" on Windows. \ + This file is needed to search radicals by strokes."), + } +} + +#[cfg(unix)] +fn get_radkfile_path() -> Option { + #[allow(deprecated)] /* obviously no windows problem here */ + std::env::home_dir() + .map(|path| path.join(".local/share/radkfile")) +} + +#[cfg(windows)] +/* Nicked this section straight from https://github.com/rust-lang/cargo/blob/master/crates/home/src/windows.rs */ +extern "C" { + fn wcslen(buf: *const u16) -> usize; +} +#[cfg(windows)] +fn get_radkfile_path() -> Option { + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + use windows_sys::Win32::Foundation::{MAX_PATH, S_OK}; + use windows_sys::Win32::UI::Shell::{SHGetFolderPathW, CSIDL_PROFILE}; + + match env::var_os("USERPROFILE").filter(|s| !s.is_empty()).map(PathBuf::from) { + Some(path) => { + Some(path.join("Appdata\\Local\\radkfile")) + }, + None => { + unsafe { + let mut path: Vec = Vec::with_capacity(MAX_PATH as usize); + match SHGetFolderPathW(0, CSIDL_PROFILE as i32, 0, 0, path.as_mut_ptr()) { + S_OK => { + let len = wcslen(path.as_ptr()); + path.set_len(len); + let s = OsString::from_wide(&path); + Some(PathBuf::from(s).join("Appdata\\Local\\radkfile")) + } + _ => None, + } + } + } + } +} + +fn search_by_strokes(query: &mut String, radk_list: &[radk::Membership], n: usize) -> Result { + + let mut strokes = String::new(); + let mut radicals: Vec = Vec::new(); + let rad; + loop{ + print!("How many strokes does your radical have? "); + stdout().flush()?; + strokes.clear(); + if (stdin().read_line(&mut strokes).expect("Can't read from stdin")) == 0 { + std::process::exit(0); + } + + match strokes.trim().parse::() { + Ok(strk) => { + let mut i = 1; + for k in radk_list.iter() { + if k.radical.strokes == strk { + print!("{}{} ", i, k.radical.glyph); + radicals.push(k.radical.glyph.chars().next().unwrap()); + i += 1; + } else if k.radical.strokes > strk { + println!(); + break; + } + } + loop { + print!("Choose the radical to use for your search: "); + stdout().flush()?; + strokes.clear(); + if (stdin().read_line(&mut strokes).expect("Can't read from stdin")) == 0 { + std::process::exit(0); + } + + match strokes.trim().parse::() { + Ok(strk) => { + if strk < 1 || strk > i-1 { + eprintln!("Couldn't parse input: number not in range"); + } else { + rad = radicals.get(strk-1).unwrap(); + /* UTF-8 is not fun */ + let char_and_index = query.char_indices().nth(n).unwrap(); + query.replace_range(char_and_index.0.. + char_and_index.0 + + char_and_index.1.len_utf8(), + rad.to_string().as_str()); + println!("{}", query.as_str().bright_black()); + return Ok(*rad); + } + }, + Err(e) => { eprintln!("{e}"); } + } + } + }, + Err(e) => { eprintln!("{e}") } + } + } +} diff --git a/src/main.rs b/src/main.rs index 182d527..2aca60f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,17 @@ +mod kanji_search; use std::{ io::{stdin, stdout, Write}, process::{Command, Stdio}, - path::PathBuf, - collections::HashSet, error::Error, env, }; +use kanji_search::search_by_radical; + use argparse::{ArgumentParser, List, Print, Store, StoreTrue}; use colored::*; use serde_json::Value; use atty::Stream; -use kradical_parsing::radk; macro_rules! JISHO_URL { () => { @@ -61,61 +61,8 @@ fn main() -> Result<(), Box> { let mut lines_output = 0; let mut output = String::with_capacity(5242880); /* Give output 5MB of buffer; Should be enough to avoid reallocs*/ - /* for kanji radical search */ - let mut result: HashSet<_> = HashSet::new(); - let mut aux: HashSet<_> = HashSet::new(); - if query.starts_with(':') || query.starts_with(':') { - - let path = get_radkfile_path(); - - match radk::parse_file(path.unwrap()) { /* if it doesn't exist, just panic */ - Ok(radk_list) => { - result.clear(); - - /* First iteration: get the baseline for the results */ - let mut rad = query.chars().nth(1).unwrap(); - if rad == '*' || rad == '*' { - /* if search_by_radical returned an error then something is very wrong */ - rad = search_by_strokes(&mut query, &radk_list, 1).expect("Couldn't parse input"); - } - - for k in radk_list.iter() { - if k.radical.glyph.contains(rad) { - for input in &k.kanji { - result.insert(input); - } - break; - } - } - - /* Iterate until you've exhausted user input: refine the baseline to get final output */ - for (i, mut rad) in query.clone().chars().skip(2).enumerate() { - if rad == '*' || rad == '*' { - /* if search_by_radical returned an error then something is very wrong */ - rad = search_by_strokes(&mut query, &radk_list, i+2).expect("Couldn't parse input"); - } - - for k in radk_list.iter() { - if k.radical.glyph.contains(rad) { - for input in &k.kanji { - aux.insert(input); - } - result = &result & &aux; - aux.clear(); - break; - } - } - } - for r in result { - print!("{r} "); - } - println!(); - } - Err(_e) => eprintln!("Error while reading radkfile\nIf you don't have the radkfile, download it from \ - https://www.edrdg.org/krad/kradinf.html and place it in \"~/.local/share/\" on Linux or \"~\\AppData\\Local\\\" on Windows. \ - This file is needed to search radicals by strokes."), - } + search_by_radical(&mut query); } else { // Do API request let body: Value = ureq::get(&format!(JISHO_URL!(), query)) @@ -406,65 +353,6 @@ fn parse_args() -> Options { options } -fn search_by_strokes(query: &mut String, radk_list: &[radk::Membership], n: usize) -> Result { - - let mut strokes = String::new(); - let mut radicals: Vec = Vec::new(); - let rad; - loop{ - print!("How many strokes does your radical have? "); - stdout().flush()?; - strokes.clear(); - if (stdin().read_line(&mut strokes).expect("Can't read from stdin")) == 0 { - std::process::exit(0); - } - - match strokes.trim().parse::() { - Ok(strk) => { - let mut i = 1; - for k in radk_list.iter() { - if k.radical.strokes == strk { - print!("{}{} ", i, k.radical.glyph); - radicals.push(k.radical.glyph.chars().next().unwrap()); - i += 1; - } else if k.radical.strokes > strk { - println!(); - break; - } - } - loop { - print!("Choose the radical to use for your search: "); - stdout().flush()?; - strokes.clear(); - if (stdin().read_line(&mut strokes).expect("Can't read from stdin")) == 0 { - std::process::exit(0); - } - - match strokes.trim().parse::() { - Ok(strk) => { - if strk < 1 || strk > i-1 { - eprintln!("Couldn't parse input: number not in range"); - } else { - rad = radicals.get(strk-1).unwrap(); - /* UTF-8 is not fun */ - let char_and_index = query.char_indices().nth(n).unwrap(); - query.replace_range(char_and_index.0.. - char_and_index.0 + - char_and_index.1.len_utf8(), - rad.to_string().as_str()); - println!("{}", query.as_str().bright_black()); - return Ok(*rad); - } - }, - Err(e) => { eprintln!("{e}"); } - } - } - }, - Err(e) => { eprintln!("{e}") } - } - } -} - fn pipe_to_less(output: String) { let command = Command::new("less") @@ -539,43 +427,3 @@ fn terminal_size() -> Result { } } } - -#[cfg(unix)] -fn get_radkfile_path() -> Option { - #[allow(deprecated)] /* obviously no windows problem here */ - std::env::home_dir() - .map(|path| path.join(".local/share/radkfile")) -} - -#[cfg(windows)] -/* Nicked this section straight from https://github.com/rust-lang/cargo/blob/master/crates/home/src/windows.rs */ -extern "C" { - fn wcslen(buf: *const u16) -> usize; -} -#[cfg(windows)] -fn get_radkfile_path() -> Option { - use std::ffi::OsString; - use std::os::windows::ffi::OsStringExt; - use windows_sys::Win32::Foundation::{MAX_PATH, S_OK}; - use windows_sys::Win32::UI::Shell::{SHGetFolderPathW, CSIDL_PROFILE}; - - match env::var_os("USERPROFILE").filter(|s| !s.is_empty()).map(PathBuf::from) { - Some(path) => { - Some(path.join("Appdata\\Local\\radkfile")) - }, - None => { - unsafe { - let mut path: Vec = Vec::with_capacity(MAX_PATH as usize); - match SHGetFolderPathW(0, CSIDL_PROFILE as i32, 0, 0, path.as_mut_ptr()) { - S_OK => { - let len = wcslen(path.as_ptr()); - path.set_len(len); - let s = OsString::from_wide(&path); - Some(PathBuf::from(s).join("Appdata\\Local\\radkfile")) - } - _ => None, - } - } - } - } -}