Split word search into its own file too.
This commit is contained in:
parent
93576a3e34
commit
5501c621c9
4 changed files with 322 additions and 310 deletions
33
src/aux.rs
Normal file
33
src/aux.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use crate::Value;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Options {
|
||||||
|
pub limit: usize,
|
||||||
|
pub query: String,
|
||||||
|
pub interactive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// --- Value helper
|
||||||
|
//
|
||||||
|
|
||||||
|
pub fn value_to_bool(value: &Value) -> bool {
|
||||||
|
match value {
|
||||||
|
Value::Bool(b) => *b,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value_to_str(value: &Value) -> &str {
|
||||||
|
match value {
|
||||||
|
Value::String(s) => s,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value_to_arr(value: &Value) -> &Vec<Value> {
|
||||||
|
match value {
|
||||||
|
Value::Array(a) => a,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ pub fn search_by_radical(mut query: &mut String){
|
||||||
/* First iteration: get the baseline for the results */
|
/* First iteration: get the baseline for the results */
|
||||||
let mut rad = query.chars().nth(1).unwrap();
|
let mut rad = query.chars().nth(1).unwrap();
|
||||||
if rad == '*' || rad == '*' {
|
if rad == '*' || rad == '*' {
|
||||||
/* if search_by_radical returned an error then something is very wrong */
|
/* if search_by_strokes returned an error then something is very wrong */
|
||||||
rad = search_by_strokes(&mut query, &radk_list, 1).expect("Couldn't parse input");
|
rad = search_by_strokes(&mut query, &radk_list, 1).expect("Couldn't parse input");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ pub fn search_by_radical(mut query: &mut String){
|
||||||
/* Iterate until you've exhausted user input: refine the baseline to get final output */
|
/* 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() {
|
for (i, mut rad) in query.clone().chars().skip(2).enumerate() {
|
||||||
if rad == '*' || rad == '*' {
|
if rad == '*' || rad == '*' {
|
||||||
/* if search_by_radical returned an error then something is very wrong */
|
/* if search_by_strokes returned an error then something is very wrong */
|
||||||
rad = search_by_strokes(&mut query, &radk_list, i+2).expect("Couldn't parse input");
|
rad = search_by_strokes(&mut query, &radk_list, i+2).expect("Couldn't parse input");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,61 @@ pub fn search_by_radical(mut query: &mut String){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_by_strokes(query: &mut String, radk_list: &[radk::Membership], n: usize) -> Result<char, std::io::Error> {
|
||||||
|
|
||||||
|
let mut strokes = String::new();
|
||||||
|
let mut radicals: Vec<char> = Vec::new();
|
||||||
|
let rad;
|
||||||
|
loop{
|
||||||
|
print!("How many strokes does your radical have? ");
|
||||||
|
stdout().flush()?;
|
||||||
|
strokes.clear();
|
||||||
|
stdin().read_line(&mut strokes)?;
|
||||||
|
|
||||||
|
match strokes.trim().parse::<u8>() {
|
||||||
|
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();
|
||||||
|
stdin().read_line(&mut strokes)?;
|
||||||
|
|
||||||
|
match strokes.trim().parse::<usize>() {
|
||||||
|
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}") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn get_radkfile_path() -> Option<PathBuf> {
|
fn get_radkfile_path() -> Option<PathBuf> {
|
||||||
#[allow(deprecated)] /* obviously no windows problem here */
|
#[allow(deprecated)] /* obviously no windows problem here */
|
||||||
|
@ -100,62 +155,3 @@ fn get_radkfile_path() -> Option<PathBuf> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_by_strokes(query: &mut String, radk_list: &[radk::Membership], n: usize) -> Result<char, std::io::Error> {
|
|
||||||
|
|
||||||
let mut strokes = String::new();
|
|
||||||
let mut radicals: Vec<char> = 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::<u8>() {
|
|
||||||
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::<usize>() {
|
|
||||||
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}") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
270
src/main.rs
270
src/main.rs
|
@ -1,15 +1,17 @@
|
||||||
|
mod word_search;
|
||||||
mod kanji_search;
|
mod kanji_search;
|
||||||
|
mod aux;
|
||||||
use std::{
|
use std::{
|
||||||
io::{stdin, stdout, Write},
|
io::{stdin, stdout, Write},
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
error::Error,
|
|
||||||
env,
|
env,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use word_search::word_search;
|
||||||
use kanji_search::search_by_radical;
|
use kanji_search::search_by_radical;
|
||||||
|
|
||||||
|
|
||||||
use argparse::{ArgumentParser, List, Print, Store, StoreTrue};
|
use argparse::{ArgumentParser, List, Print, Store, StoreTrue};
|
||||||
use colored::*;
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use atty::Stream;
|
use atty::Stream;
|
||||||
|
|
||||||
|
@ -19,14 +21,9 @@ macro_rules! JISHO_URL {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
|
||||||
struct Options {
|
|
||||||
limit: usize,
|
|
||||||
query: String,
|
|
||||||
interactive: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), ureq::Error> {
|
||||||
|
|
||||||
let term_size = if atty::is(Stream::Stdout) {
|
let term_size = if atty::is(Stream::Stdout) {
|
||||||
terminal_size().unwrap_or(0)
|
terminal_size().unwrap_or(0)
|
||||||
|
@ -59,56 +56,31 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
query = query.trim().to_string();
|
query = query.trim().to_string();
|
||||||
|
|
||||||
let mut lines_output = 0;
|
let mut lines_output = 0;
|
||||||
let mut output = String::with_capacity(5242880); /* Give output 5MB of buffer; Should be enough to avoid reallocs*/
|
let mut output = String::with_capacity(5242880); /* Give output 5MiB of buffer; Should be enough to avoid reallocs*/
|
||||||
|
|
||||||
if query.starts_with(':') || query.starts_with(':') {
|
if query.starts_with(':') || query.starts_with(':') { /* Kanji search */
|
||||||
search_by_radical(&mut query);
|
search_by_radical(&mut query);
|
||||||
} else {
|
} else {
|
||||||
|
} else { /* Word search */
|
||||||
// Do API request
|
// Do API request
|
||||||
let body: Value = ureq::get(&format!(JISHO_URL!(), query))
|
let body: Value = ureq::get(&format!(JISHO_URL!(), query))
|
||||||
.call()?
|
.call()?
|
||||||
.into_json()?;
|
.into_json()?;
|
||||||
|
|
||||||
// Try to get the data json-object
|
if let Some(r) = word_search(&options, body, &query, &mut output) {
|
||||||
let body = value_to_arr({
|
lines_output += r;
|
||||||
let body = body.get("data");
|
|
||||||
|
|
||||||
if body.is_none() {
|
|
||||||
eprintln!("Error! Invalid response");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
body.unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
if options.interactive {
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Iterate over meanings and print them */
|
|
||||||
for (i, entry) in body.iter().enumerate() {
|
|
||||||
if i >= options.limit && options.limit != 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if let Some(r) = print_item(&query, entry, &mut output) {
|
|
||||||
lines_output += r;
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push('\n');
|
|
||||||
lines_output += 1;
|
|
||||||
}
|
|
||||||
output.pop();
|
|
||||||
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 {
|
} else {
|
||||||
print!("{}", output);
|
eprintln!("Error: invalid json returned");
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
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 {
|
if !options.interactive {
|
||||||
break;
|
break;
|
||||||
|
@ -117,208 +89,8 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_item(query: &str, value: &Value, output: &mut String) -> Option<usize> {
|
fn parse_args() -> aux::Options {
|
||||||
let japanese = value_to_arr(value.get("japanese")?);
|
let mut options = aux::Options::default();
|
||||||
let main_form = japanese.get(0)?;
|
|
||||||
let mut num_of_lines = 0;
|
|
||||||
|
|
||||||
*output += &format!("{} {}\n", format_form(query, main_form)?, format_result_tags(value));
|
|
||||||
|
|
||||||
/* 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, 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 += &format!(" {}\n", sense_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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<String> {
|
|
||||||
let reading = form
|
|
||||||
.get("reading")
|
|
||||||
.map(value_to_str)
|
|
||||||
.unwrap_or(query);
|
|
||||||
|
|
||||||
let word = value_to_str(form.get("word").unwrap_or(form.get("reading")?));
|
|
||||||
|
|
||||||
Some(format!("{}[{}]", word, reading))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_sense(value: &Value, index: usize, prev_parts_of_speech: &mut String) -> (String, bool) {
|
|
||||||
let english_definitons = value.get("english_definitions");
|
|
||||||
let parts_of_speech = value.get("parts_of_speech");
|
|
||||||
if english_definitons.is_none() {
|
|
||||||
return ("".to_owned(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let english_definiton = value_to_arr(english_definitons.unwrap());
|
|
||||||
|
|
||||||
let parts_of_speech = if let Some(parts_of_speech) = parts_of_speech {
|
|
||||||
let parts = value_to_arr(parts_of_speech)
|
|
||||||
.iter()
|
|
||||||
.map(value_to_str)
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.join(", ");
|
|
||||||
|
|
||||||
/* 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())
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_part_of_speech = !parts_of_speech.is_empty();
|
|
||||||
|
|
||||||
let index_str = format!("{}.",(index + 1));
|
|
||||||
let mut tags = format_sense_tags(value);
|
|
||||||
let info = format_sense_info(value);
|
|
||||||
|
|
||||||
if !info.is_empty() && !tags.is_empty() {
|
|
||||||
tags.push(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
(format!(
|
|
||||||
"{}{} {}{}{}",
|
|
||||||
parts_of_speech,
|
|
||||||
index_str.bright_black(),
|
|
||||||
english_definiton
|
|
||||||
.iter()
|
|
||||||
.map(value_to_str)
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.join(", "),
|
|
||||||
tags.bright_black(),
|
|
||||||
info.bright_black(),
|
|
||||||
), new_part_of_speech)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format tags from a whole meaning
|
|
||||||
fn format_result_tags(value: &Value) -> String {
|
|
||||||
let mut builder = String::new();
|
|
||||||
|
|
||||||
let is_common_val = value.get("is_common");
|
|
||||||
if is_common_val.is_some() && value_to_bool(is_common_val.unwrap()) {
|
|
||||||
builder.push_str(&"(common) ".bright_green().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(jlpt) = value.get("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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format tags from a single sense entry
|
|
||||||
fn format_sense_tags(value: &Value) -> String {
|
|
||||||
let mut builder = String::new();
|
|
||||||
|
|
||||||
if let Some(tags) = value.get("tags") {
|
|
||||||
let tags = value_to_arr(tags);
|
|
||||||
|
|
||||||
if let Some(tag) = tags.get(0) {
|
|
||||||
let t = format_sense_tag(value_to_str(tag));
|
|
||||||
builder += &format!(" {}", t.as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
for tag in tags.get(1).iter() {
|
|
||||||
let t = format_sense_tag(value_to_str(tag));
|
|
||||||
builder += &format!(", {}", t.as_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
builder
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_sense_tag(tag: &str) -> String {
|
|
||||||
match tag {
|
|
||||||
"Usually written using kana alone" => "UK".to_string(),
|
|
||||||
s => s.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_sense_info(value: &Value) -> String {
|
|
||||||
let mut builder = String::new();
|
|
||||||
if let Some(all_info) = value.get("info") {
|
|
||||||
let all_info = value_to_arr(all_info);
|
|
||||||
|
|
||||||
if let Some(info) = all_info.get(0) {
|
|
||||||
builder += &format!(" {}", value_to_str(info));
|
|
||||||
}
|
|
||||||
|
|
||||||
for info in all_info.get(1).iter() {
|
|
||||||
builder += &format!(", {}", value_to_str(info));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
builder
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// --- Value helper
|
|
||||||
//
|
|
||||||
|
|
||||||
fn value_to_bool(value: &Value) -> bool {
|
|
||||||
match value {
|
|
||||||
Value::Bool(b) => *b,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value_to_str(value: &Value) -> &str {
|
|
||||||
match value {
|
|
||||||
Value::String(s) => s,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value_to_arr(value: &Value) -> &Vec<Value> {
|
|
||||||
match value {
|
|
||||||
Value::Array(a) => a,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_args() -> Options {
|
|
||||||
let mut options = Options::default();
|
|
||||||
let mut query_vec: Vec<String> = Vec::new();
|
let mut query_vec: Vec<String> = Vec::new();
|
||||||
{
|
{
|
||||||
let mut ap = ArgumentParser::new();
|
let mut ap = ArgumentParser::new();
|
||||||
|
|
211
src/word_search.rs
Normal file
211
src/word_search.rs
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
use crate::aux::*;
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
|
use colored::*;
|
||||||
|
|
||||||
|
pub fn word_search(options: &Options, body: Value, query: &String, mut output: &mut String) -> Option<usize> {
|
||||||
|
let mut lines_output = 0;
|
||||||
|
|
||||||
|
// Try to get the data json-object
|
||||||
|
let body = value_to_arr({
|
||||||
|
let body = body.get("data");
|
||||||
|
|
||||||
|
if body.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Iterate over meanings and print them */
|
||||||
|
for (i, entry) in body.iter().enumerate() {
|
||||||
|
if i >= options.limit && options.limit != 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if let Some(r) = print_item(&query, entry, &mut output) {
|
||||||
|
lines_output += r;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push('\n');
|
||||||
|
lines_output += 1;
|
||||||
|
}
|
||||||
|
output.pop();
|
||||||
|
lines_output = lines_output.saturating_sub(1);
|
||||||
|
|
||||||
|
Some(lines_output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_item(query: &str, value: &Value, output: &mut String) -> Option<usize> {
|
||||||
|
let japanese = value_to_arr(value.get("japanese")?);
|
||||||
|
let main_form = japanese.get(0)?;
|
||||||
|
let mut num_of_lines = 0;
|
||||||
|
|
||||||
|
*output += &format!("{} {}\n", format_form(query, main_form)?, format_result_tags(value));
|
||||||
|
|
||||||
|
/* 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, 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 += &format!(" {}\n", sense_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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<String> {
|
||||||
|
let reading = form
|
||||||
|
.get("reading")
|
||||||
|
.map(value_to_str)
|
||||||
|
.unwrap_or(query);
|
||||||
|
|
||||||
|
let word = value_to_str(form.get("word").unwrap_or(form.get("reading")?));
|
||||||
|
|
||||||
|
Some(format!("{}[{}]", word, reading))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_sense(value: &Value, index: usize, prev_parts_of_speech: &mut String) -> (String, bool) {
|
||||||
|
let english_definitons = value.get("english_definitions");
|
||||||
|
let parts_of_speech = value.get("parts_of_speech");
|
||||||
|
if english_definitons.is_none() {
|
||||||
|
return ("".to_owned(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let english_definiton = value_to_arr(english_definitons.unwrap());
|
||||||
|
|
||||||
|
let parts_of_speech = if let Some(parts_of_speech) = parts_of_speech {
|
||||||
|
let parts = value_to_arr(parts_of_speech)
|
||||||
|
.iter()
|
||||||
|
.map(value_to_str)
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
/* 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())
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_part_of_speech = !parts_of_speech.is_empty();
|
||||||
|
|
||||||
|
let index_str = format!("{}.",(index + 1));
|
||||||
|
let mut tags = format_sense_tags(value);
|
||||||
|
let info = format_sense_info(value);
|
||||||
|
|
||||||
|
if !info.is_empty() && !tags.is_empty() {
|
||||||
|
tags.push(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
(format!(
|
||||||
|
"{}{} {}{}{}",
|
||||||
|
parts_of_speech,
|
||||||
|
index_str.bright_black(),
|
||||||
|
english_definiton
|
||||||
|
.iter()
|
||||||
|
.map(value_to_str)
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join(", "),
|
||||||
|
tags.bright_black(),
|
||||||
|
info.bright_black(),
|
||||||
|
), new_part_of_speech)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format tags from a whole meaning
|
||||||
|
fn format_result_tags(value: &Value) -> String {
|
||||||
|
let mut builder = String::new();
|
||||||
|
|
||||||
|
let is_common_val = value.get("is_common");
|
||||||
|
if is_common_val.is_some() && value_to_bool(is_common_val.unwrap()) {
|
||||||
|
builder.push_str(&"(common) ".bright_green().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(jlpt) = value.get("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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format tags from a single sense entry
|
||||||
|
fn format_sense_tags(value: &Value) -> String {
|
||||||
|
let mut builder = String::new();
|
||||||
|
|
||||||
|
if let Some(tags) = value.get("tags") {
|
||||||
|
let tags = value_to_arr(tags);
|
||||||
|
|
||||||
|
if let Some(tag) = tags.get(0) {
|
||||||
|
let t = format_sense_tag(value_to_str(tag));
|
||||||
|
builder += &format!(" {}", t.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
for tag in tags.get(1).iter() {
|
||||||
|
let t = format_sense_tag(value_to_str(tag));
|
||||||
|
builder += &format!(", {}", t.as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_sense_tag(tag: &str) -> String {
|
||||||
|
match tag {
|
||||||
|
"Usually written using kana alone" => "UK".to_string(),
|
||||||
|
s => s.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_sense_info(value: &Value) -> String {
|
||||||
|
let mut builder = String::new();
|
||||||
|
if let Some(all_info) = value.get("info") {
|
||||||
|
let all_info = value_to_arr(all_info);
|
||||||
|
|
||||||
|
if let Some(info) = all_info.get(0) {
|
||||||
|
builder += &format!(" {}", value_to_str(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
for info in all_info.get(1).iter() {
|
||||||
|
builder += &format!(", {}", value_to_str(info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue