diff --git a/.img/16adb8274ff5e12b13545df2996dbdc3be149b9cd5575ceb38e2d9e031117ab9.png b/.img/16adb8274ff5e12b13545df2996dbdc3be149b9cd5575ceb38e2d9e031117ab9.png new file mode 100644 index 0000000..0a10e42 Binary files /dev/null and b/.img/16adb8274ff5e12b13545df2996dbdc3be149b9cd5575ceb38e2d9e031117ab9.png differ diff --git a/.img/dab0ab082751a1b17271309c2ffc3c16d53c8498513619e50235e8157bab01fa.png b/.img/dab0ab082751a1b17271309c2ffc3c16d53c8498513619e50235e8157bab01fa.png new file mode 100644 index 0000000..0a1df3f Binary files /dev/null and b/.img/dab0ab082751a1b17271309c2ffc3c16d53c8498513619e50235e8157bab01fa.png differ diff --git a/.img/hV3BeXWDTmREm8ujOkR3v6903.png b/.img/hV3BeXWDTmREm8ujOkR3v6903.png deleted file mode 100644 index 20a2afb..0000000 Binary files a/.img/hV3BeXWDTmREm8ujOkR3v6903.png and /dev/null differ diff --git a/Cargo.lock b/Cargo.lock index e0092a7..30ca917 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,18 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "argparse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f8ebf5827e4ac4fd5946560e6a99776ea73b596d80898f357007317a7141e47" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "atty" version = "0.2.14" @@ -34,6 +31,24 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "bumpalo" version = "3.6.1" @@ -69,6 +84,70 @@ dependencies = [ "winapi", ] +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -79,6 +158,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "hermit-abi" version = "0.1.18" @@ -109,14 +194,13 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" name = "jisho-cli" version = "0.1.4" dependencies = [ - "ansi_term", "argparse", "atty", "colored", + "kradical_parsing", "libc", "serde_json", "ureq", - "webbrowser", "windows-sys", ] @@ -129,12 +213,44 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kradical_jis" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e3de4d97ae2ae659371d11e7181b2498efe5ef6211f7103aa37f58e5408c19" + +[[package]] +name = "kradical_parsing" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7e62dbe571a488fae4ba0f5056224e56a00c58fd7c6583ca92720d1236b0087" +dependencies = [ + "encoding", + "kradical_jis", + "nom", + "thiserror", + "unicode-segmentation", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.139" @@ -156,6 +272,25 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "nom" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a7a9657c84d5814c6196b68bb4429df09c18b1573806259fba397ea4ad0d44" +dependencies = [ + "bitvec", + "funty", + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "once_cell" version = "1.7.2" @@ -186,6 +321,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "ring" version = "0.16.20" @@ -253,6 +394,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.68" @@ -264,6 +411,32 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tinyvec" version = "1.2.0" @@ -297,6 +470,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-xid" version = "0.2.1" @@ -339,6 +518,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasm-bindgen" version = "0.2.73" @@ -403,17 +588,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webbrowser" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecad156490d6b620308ed411cfee90d280b3cbd13e189ea0d3fada8acc89158a" -dependencies = [ - "web-sys", - "widestring", - "winapi", -] - [[package]] name = "webpki" version = "0.21.4" @@ -433,12 +607,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "widestring" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" - [[package]] name = "winapi" version = "0.3.9" @@ -526,3 +694,9 @@ name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" diff --git a/Cargo.toml b/Cargo.toml index 292edae..b5d045c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,32 @@ [package] name = "jisho-cli" -description = "A very simple cli tool to lookup Japanese words using jisho.org" +description = "A simple cli tool to lookup Japanese words using jisho.org" version = "0.1.4" -authors = ["jojii "] +authors = ["jojii ", "Hiers - + + # Installation +Binaries are directly available from the release tab. -From [Release tab](https://github.com/JojiiOfficial/jisho-cli/releases) -
-> Simply download the binary -
+# Compilation -From my [Arch repository](https://repo.jojii.de) +Download source and run ``` -sudo pacman -S jisho +cargo build --release ``` -From crates.io: -``` -cargo install jisho-cli -``` - -From AUR: -``` -yay -S jisho -``` - -
- # Usage ``` jisho [] +jisho :[] ``` +When looking up radicals, * (or *) can be used to add a radical that can't be easily typed, e.g. 气. -> Note: The binary from crates.io is called `jisho-cli`, not `jisho`
-> Note: You can use spaces +# Note +To search kanji by radicals, the [radkfile](https://www.edrdg.org/krad/kradinf.html) needs to be installed in either `~/.local/share/` on Linux or `~\AppData\Local\` on Windows. diff --git a/src/main.rs b/src/main.rs index 7ce311d..dadab93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ use std::{ io::{stdin, stdout, Write}, process::{Command, Stdio}, - thread::{self, JoinHandle}, + path::PathBuf, + collections::HashSet, + error::Error, env, }; @@ -9,6 +11,7 @@ use argparse::{ArgumentParser, List, Print, Store, StoreTrue}; use colored::*; use serde_json::Value; use atty::Stream; +use kradical_parsing::radk; macro_rules! JISHO_URL { () => { @@ -16,73 +19,110 @@ macro_rules! JISHO_URL { }; } -#[derive(Debug, Clone)] +#[derive(Default, Debug, Clone)] struct Options { limit: usize, query: String, - kanji: bool, // Sadly not (yet) supported by jisho.org's API interactive: bool, } -impl Default for Options { - fn default() -> Self { - Self { - limit: 0, - query: String::default(), - kanji: false, - interactive: false, - } - } -} +fn main() -> Result<(), Box> { -fn main() -> Result<(), ureq::Error> { - let term_size; - - if atty::is(Stream::Stdout) { - term_size = terminal_size().unwrap_or(0); + let term_size = if atty::is(Stream::Stdout) { + terminal_size().unwrap_or(0) } else { - term_size = 0; - } + 0 + }; let options = parse_args(); - let mut query = { + let mut query = String::new(); + loop { + + query.clear(); if options.interactive { - let mut o = String::new(); - while o.trim().is_empty() { + while query.trim().is_empty() || query.trim() == ":" || query.trim() == ":" { + query.clear(); print!("=> "); stdout().flush().unwrap(); - if (stdin().read_line(&mut o).expect("Can't read from stdin")) == 0 { - // Exit on EOF + if (stdin().read_line(&mut query).expect("Can't read from stdin")) == 0 { + /* Exit on EOF */ return Ok(()); } } - o } else { - options.query.clone() + query = options.query.clone(); + if query.trim().is_empty() || query.trim() == ":" || query.trim() == ":" { + return Ok(()); + } } - }; + query = query.trim().to_string(); - loop { let mut lines_output = 0; - let mut output = String::new(); + let mut output = String::with_capacity(5242880); /* Give output 5MB of buffer; Should be enough to avoid reallocs*/ - if options.kanji { - // Open kanji page here - let threads = query - .chars() - .into_iter() - .map(|kanji| { - let kanji = kanji.clone(); - thread::spawn(move || { - webbrowser::open(&format!("https://jisho.org/search/{}%23kanji", kanji)) - .expect("Couldn't open browser"); - }) - }) - .collect::>>(); + /* for kanji radical search */ + let mut result: HashSet<_> = HashSet::new(); + let mut aux: HashSet<_> = HashSet::new(); - for thread in threads { - thread.join().unwrap(); + 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.char_indices().nth(1).unwrap().1; + if rad == '*' || rad == '*' { + let a = search_by_strokes(&mut query, &radk_list, 1); + if a.is_err() { + /* if search_by_radical returned an error then something is very wrong */ + panic!("Couldn't parse input: {}", a.err().unwrap()); + } + rad = a.unwrap(); + } + + 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 == '*' { + let a = search_by_strokes(&mut query, &radk_list, i+2); + if a.is_err() { + /* if search_by_radical returned an error then something is very wrong */ + panic!("Couldn't parse input: {}", a.err().unwrap()); + } + rad = a.unwrap(); + } + + 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."), } } else { // Do API request @@ -106,49 +146,35 @@ fn main() -> Result<(), ureq::Error> { println!(); } - // Iterate over meanings and print them + /* Iterate over meanings and print them */ for (i, entry) in body.iter().enumerate() { if i >= options.limit && options.limit != 0 { break; } - match print_item(&query, entry, &mut output) { - Some(r) => lines_output += r, - None => continue, + if let Some(r) = print_item(&query, entry, &mut output) { + lines_output += r; } output.push('\n'); lines_output += 1; } output.pop(); - if lines_output > 0 { - lines_output -= 1; + lines_output = lines_output.saturating_sub(1); + + if lines_output >= term_size - 1 && term_size != 0 { + /* Output is a different process that is not a tty (i.e. less), but we want to keep colour */ + env::set_var("CLICOLOR_FORCE", "1"); + pipe_to_less(output); + } else { + print!("{}", output); } - } - if lines_output >= term_size - 1 && term_size != 0 { - // Output is a different process that is not a tty (i.e. less), but we want to keep colour - env::set_var("CLICOLOR_FORCE", "1"); - pipe_to_less(output); - } else { - print!("{}", output); } - if !options.interactive { break; } - - query.clear(); - while query.trim().is_empty() { - print!("=> "); - stdout().flush().unwrap(); - if (stdin().read_line(&mut query).expect("Can't read from stdin")) == 0 { - // Exit on EOF - return Ok(()); - } - } } - Ok(()) } @@ -159,47 +185,47 @@ fn print_item(query: &str, value: &Value, output: &mut String) -> Option *output += &format!("{} {}\n", format_form(query, main_form)?, format_result_tags(value)); - // Print senses + /* Print senses */ let senses = value_to_arr(value.get("senses")?); let mut prev_parts_of_speech = String::new(); for (i, sense) in senses.iter().enumerate() { - let (sense_str, bump) = format_sense(&sense, i, &mut prev_parts_of_speech); - if sense_str.is_empty() { - continue; - } - // This bump is to keep count of lines that may or may not be printed (like noun, adverb) - if bump { - num_of_lines += 1; - } - - *output += &format!(" {}\n", sense_str); - } - - // Print alternative readings and kanji usage - match japanese.get(1) { - Some (form) => { - num_of_lines += 2; - - *output += &format!(" {}", "Other forms\n".bright_blue()); - *output += &format!(" {}", format_form(query, form)?); - - for i in 2..japanese.len() { - *output += &format!(", {}", format_form(query, japanese.get(i)?)?); + let (sense_str, new_part_of_speech) = format_sense(sense, i, &mut prev_parts_of_speech); + if !sense_str.is_empty() { + /* + * If the current meaning of our word is a different part of speech + * (e.g. previous meaning was 'Noun' and the current is 'Adverb'), an extra line will be + * printed with this information + */ + if new_part_of_speech { + num_of_lines += 1; } - output.push('\n'); + + *output += &format!(" {}\n", sense_str); } - None => {} } - num_of_lines += senses.iter().count() + 1; + /* Print alternative readings and kanji usage */ + if let Some(form) = japanese.get(1) { + num_of_lines += 2; + + *output += &format!(" {}", "Other forms\n".bright_blue()); + *output += &format!(" {}", format_form(query, form)?); + + for i in 2..japanese.len() { + *output += &format!(", {}", format_form(query, japanese.get(i)?)?); + } + output.push('\n'); + } + + num_of_lines += senses.len() + 1; Some(num_of_lines) } fn format_form(query: &str, form: &Value) -> Option { let reading = form .get("reading") - .map(|i| value_to_str(i)) + .map(value_to_str) .unwrap_or(query); let word = value_to_str(form.get("word").unwrap_or(form.get("reading")?)); @@ -218,25 +244,14 @@ fn format_sense(value: &Value, index: usize, prev_parts_of_speech: &mut String) let parts_of_speech = if let Some(parts_of_speech) = parts_of_speech { let parts = value_to_arr(parts_of_speech) - .to_owned() .iter() .map(|i| { - let s = value_to_str(i); - match s { - "Suru verb - irregular" => "Irregular verb", - _ => { - if s.contains("Godan verb") { - "Godan verb" - } else { - s - } - } - } + value_to_str(i) }) .collect::>() .join(", "); - // Do not repeat a meaning's part of speech if it is the same as the previous meaning + /* Do not repeat a meaning's part of speech if it is the same as the previous meaning */ if !parts.is_empty() && parts != *prev_parts_of_speech { *prev_parts_of_speech = parts.clone(); format!("{}\n ", parts.bright_blue()) @@ -247,11 +262,7 @@ fn format_sense(value: &Value, index: usize, prev_parts_of_speech: &mut String) String::new() }; - let bump = if parts_of_speech.is_empty() { - false - } else { - true - }; + let new_part_of_speech = !parts_of_speech.is_empty(); let index_str = format!("{}.",(index + 1)); let mut tags = format_sense_tags(value); @@ -267,12 +278,12 @@ fn format_sense(value: &Value, index: usize, prev_parts_of_speech: &mut String) index_str.bright_black(), english_definiton .iter() - .map(|i| value_to_str(i)) + .map(value_to_str) .collect::>() .join(", "), tags.bright_black(), info.bright_black(), - ), bump) + ), new_part_of_speech) } /// Format tags from a whole meaning @@ -285,12 +296,16 @@ fn format_result_tags(value: &Value) -> String { } if let Some(jlpt) = value.get("jlpt") { - let jlpt = value_to_arr(&jlpt); + /* + * The jisho API actually returns an array of all of JLTP levels for each alternative of a word + * Since the main one is always at index 0, we take that for formatting + */ + let jlpt = value_to_arr(jlpt); if !jlpt.is_empty() { let jlpt = value_to_str(jlpt.get(0).unwrap()) .replace("jlpt-", "") .to_uppercase(); - builder.push_str(&format!("({}) ", jlpt.bright_blue().to_string())); + builder.push_str(&format!("({}) ", jlpt.bright_blue())); } } @@ -314,7 +329,6 @@ fn format_sense_tags(value: &Value) -> String { builder += &format!(", {}", t.as_str()); } } - builder } @@ -338,7 +352,7 @@ fn format_sense_info(value: &Value) -> String { builder += &format!(", {}", value_to_str(info)); } } - return builder; + builder } // @@ -371,7 +385,9 @@ fn parse_args() -> Options { let mut query_vec: Vec = Vec::new(); { let mut ap = ArgumentParser::new(); - ap.set_description("Use jisho.org from cli"); + ap.set_description("Use jisho.org from cli. \ + Searching for kanji by radicals is also available if the radkfile file is installed in \"~/.local/share\" \ + or \"~\\AppData\\Local\\\" if you're on Windows."); ap.add_option( &["-V", "--version"], Print(env!("CARGO_PKG_VERSION").to_string()), @@ -383,7 +399,9 @@ fn parse_args() -> Options { "Limit the amount of results", ); ap.refer(&mut query_vec) - .add_argument("Query", List, "The query to search for"); + .add_argument("Query", List, "Search terms using jisho.org; + Prepend it with ':' to search a kanji by radicals instead \ + and ':*' to search a radical by strokes (e.g. ':口*')."); ap.refer(&mut options.interactive).add_option( &["-i", "--interactive"], @@ -391,13 +409,6 @@ fn parse_args() -> Options { "Don't exit after running a query", ); - /* Uncomment when supported by jisho.org */ - ap.refer(&mut options.kanji).add_option( - &["--kanji", "-k"], - StoreTrue, - "Look up a certain kanji", - ); - ap.parse_args_or_exit(); } @@ -405,6 +416,64 @@ 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 */ + query.replace_range(query.char_indices().nth(n).unwrap().0.. + query.char_indices().nth(n).unwrap().0 + + query.char_indices().nth(n).unwrap().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") @@ -418,28 +487,34 @@ fn pipe_to_less(output: String) { panic!("couldn't pipe to less: {}", e); } - // We don't care about the return value, only whether wait failed or not + /* We don't care about the return value, only whether wait failed or not */ if process.wait().is_err() { panic!("wait() was called on non-existent child process\ - this should not be possible"); } } - // less not found in PATH; print normally + /* less not found in PATH; print normally */ Err(_e) => print!("{}", output) }; } +/* OS specific part of the program */ #[cfg(unix)] fn terminal_size() -> Result { - use libc::{c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ}; + use libc::{ioctl, STDOUT_FILENO, TIOCGWINSZ, winsize}; unsafe { - let mut size: c_ushort = 0; - if ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size as *mut _) != 0 { - Err(-1) + let mut size = winsize { + ws_row: 0, + ws_col: 0, + ws_xpixel: 0, + ws_ypixel: 0, + }; + if ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size as *mut _) == 0 { + Ok(size.ws_row as usize) } else { - Ok(size as usize) + Err(-1) } } } @@ -451,7 +526,7 @@ fn terminal_size() -> Result { unsafe { let handle = GetStdHandle(STD_OUTPUT_HANDLE) as windows_sys::Win32::Foundation::HANDLE; - // Unlike the linux function, rust will complain if only part of the struct is sent + /* Unlike the linux function, rust will complain if only part of the struct is sent */ let mut window = CONSOLE_SCREEN_BUFFER_INFO { dwSize: COORD { X: 0, Y: 0}, dwCursorPosition: COORD { X: 0, Y: 0}, @@ -471,3 +546,43 @@ 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) => { + return 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, + } + } + } + } +}