Removed unnecessary mallocs.

The colored crate was being used for convinient coloring of the terminal
text, but after profiling I found it was responsible for almost 1/3 of
the memory allocations of the jisho searching feature.

Decided to write ANSI codes manually instead, along with using write! +
format_args! instead of format! for similar reasons, but not as
egregious.
This commit is contained in:
Hiers 2024-02-17 19:12:09 +00:00
parent a475790e63
commit 6fcd5489d5
5 changed files with 73 additions and 70 deletions

View file

@ -21,7 +21,6 @@ libc = "0.2.151"
[dependencies]
ureq = { version = "2.8.0", features = ["json"] }
serde_json = "1.0.108"
colored = "2.1.0"
argparse = "0.2.2"
atty = "0.2.14"
kradical_parsing = "0.1.0"

View file

@ -3,7 +3,6 @@ use std::{
collections::HashSet,
};
use colored::*;
use kradical_parsing::radk;
pub fn search_by_radical(query: &mut String, radk_list: &[radk::Membership]) -> Option<()> {
@ -105,7 +104,7 @@ fn search_by_strokes(query: &mut String, radk_list: &[radk::Membership], n: usiz
char_and_index.0 +
char_and_index.1.len_utf8(),
rad.to_string().as_str());
println!("{}", query.as_str().bright_black());
println!("\x1b[90m{}\x1b[m", query);
return Some(*rad);
}
},

View file

@ -48,7 +48,7 @@ fn main() -> Result<(), ureq::Error> {
let options = parse_args();
let mut output = String::with_capacity(51200); /* Give output 50KiB of buffer; Should be enough to avoid reallocs*/
let mut output = String::with_capacity(3096); /* Give output 3KiB of buffer; Should be enough to avoid reallocs*/
let mut query = options.query.trim().to_string().clone();
loop {

View file

@ -1,7 +1,7 @@
use std::fmt::Write;
use crate::util::*;
use serde_json::Value;
use colored::*;
pub fn sentence_search(options: &Options, body: Value, output: &mut String) -> Option<usize>{
@ -33,13 +33,12 @@ pub fn sentence_search(options: &Options, body: Value, output: &mut String) -> O
for translation in translations.iter() {
let index_str = format!("{}.", i).bright_black();
/* Prefer to keep japanese sentences on top */
if entry.get("lang")? == "eng" {
*output += &format!("{} {}\n {}\n\n", index_str, value_to_str(translation.get("text")?), value_to_str(entry.get("text")?));
write!(output, "{}", format_args!("\x1b[90m{}.\x1b[m {}\n {}\n\n", i, value_to_str(translation.get("text")?), value_to_str(entry.get("text")?))).unwrap();
} else {
*output += &format!("{} {}\n {}\n\n", index_str, value_to_str(entry.get("text")?), value_to_str(translation.get("text")?));
write!(output, "{}", format_args!("\x1b[90m{}.\x1b[m {}\n {}\n\n", i, value_to_str(entry.get("text")?), value_to_str(translation.get("text")?))).unwrap();
}
i += 1;

View file

@ -1,7 +1,7 @@
use std::fmt::Write;
use crate::util::*;
use serde_json::Value;
use colored::*;
pub fn word_search(options: &Options, body: Value, query: &str, output: &mut String) -> Option<usize> {
let mut lines_output = 0;
@ -36,15 +36,16 @@ fn print_item(query: &str, value: &Value, output: &mut String) -> Option<usize>
let main_form = japanese.get(0)?;
let mut num_of_lines = 0;
*output += &format!("{} {}\n", format_form(query, main_form)?, format_result_tags(value));
format_form(query, main_form, output)?;
format_result_tags(value, output);
output.push('\n');
/* 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() {
let new_part_of_speech = format_sense(sense, i+1, output, &mut prev_parts_of_speech);
/*
* 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
@ -54,28 +55,30 @@ fn print_item(query: &str, value: &Value, output: &mut String) -> Option<usize>
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)?);
output.push_str(" \x1b[94mOther forms\x1b[m\n ");
format_form(query, form, output)?;
for i in 2..japanese.len() {
*output += &format!(", {}", format_form(query, japanese.get(i)?)?);
output.push_str(", ");
format_form(query, japanese.get(i)?, output)?;
}
output.push('\n');
}
/* Clear any font effect or colour */
output.push_str("\x1b[m");
num_of_lines += senses.len() + 1;
Some(num_of_lines)
}
fn format_form(query: &str, form: &Value) -> Option<String> {
fn format_form(query: &str, form: &Value, output: &mut String) -> Option<()> {
let reading = form
.get("reading")
.map(value_to_str)
@ -83,19 +86,22 @@ fn format_form(query: &str, form: &Value) -> Option<String> {
let word = value_to_str(form.get("word").unwrap_or(form.get("reading")?));
Some(format!("{}[{}]", word, reading))
write!(output, "{}", format_args!("{}[{}]", word, reading)).ok()?;
Some(())
}
fn format_sense(value: &Value, index: usize, prev_parts_of_speech: &mut String) -> (String, bool) {
fn format_sense(value: &Value, index: usize, output: &mut String, prev_parts_of_speech: &mut 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);
return false;
}
let english_definiton = value_to_arr(english_definitons.unwrap());
let parts_of_speech = if let Some(parts_of_speech) = parts_of_speech {
let is_part_of_speech_new;
if let Some(parts_of_speech) = parts_of_speech {
let parts = value_to_arr(parts_of_speech)
.iter()
.map(value_to_str)
@ -105,45 +111,40 @@ fn format_sense(value: &Value, index: usize, prev_parts_of_speech: &mut String)
/* 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())
is_part_of_speech_new = true;
write!(output, "{}", format_args!(" \x1b[34m{}\x1b[m\n", parts)).unwrap();
} else {
String::new()
is_part_of_speech_new = false;
}
} 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(',');
is_part_of_speech_new = false;
}
(format!(
"{}{} {}{}{}",
parts_of_speech,
index_str.bright_black(),
write!(output, "{}",
format_args!(" \x1b[90m{}.\x1b[m {}",
index,
english_definiton
.iter()
.map(value_to_str)
.collect::<Vec<&str>>()
.join(", "),
tags.bright_black(),
info.bright_black(),
), new_part_of_speech)
)).unwrap();
let t = format_sense_tags(value, output);
format_sense_info(value, output, t);
output.push('\n');
return is_part_of_speech_new;
}
/// Format tags from a whole meaning
fn format_result_tags(value: &Value) -> String {
let mut builder = String::new();
fn format_result_tags(value: &Value, output: &mut String) {
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());
output.push_str("\x1b[92m(common)\x1b[m");
}
if let Some(jlpt) = value.get("jlpt") {
@ -156,52 +157,57 @@ fn format_result_tags(value: &Value) -> String {
let jlpt = value_to_str(jlpt.get(0).unwrap())
.replace("jlpt-", "")
.to_uppercase();
builder.push_str(&format!("({}) ", jlpt.bright_blue()));
write!(output, "{}", format_args!("\x1b[94m({})\x1b[m", jlpt)).unwrap();
}
}
builder
}
/// Format tags from a single sense entry
fn format_sense_tags(value: &Value) -> String {
let mut builder = String::new();
fn format_sense_tags(value: &Value, output: &mut String) -> bool {
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());
output.push_str(" \x1b[90m");
output.push_str(t);
} else {
return false;
}
for tag in tags.get(1).iter() {
let t = format_sense_tag(value_to_str(tag));
builder += &format!(", {}", t.as_str());
output.push_str(", ");
output.push_str(t);
}
return true;
}
builder
return false;
}
fn format_sense_tag(tag: &str) -> String {
fn format_sense_tag(tag: &str) -> &str{
match tag {
"Usually written using kana alone" => "UK".to_string(),
s => s.to_string(),
"Usually written using kana alone" => "UK",
s => s,
}
}
fn format_sense_info(value: &Value) -> String {
let mut builder = String::new();
fn format_sense_info(value: &Value, output: &mut String, t: bool) {
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));
if t {
output.push_str(",");
}
output.push_str(" \x1b[90m");
output.push_str(value_to_str(info));
}
for info in all_info.get(1).iter() {
builder += &format!(", {}", value_to_str(info));
output.push_str(", ");
output.push_str(value_to_str(info));
}
}
builder
}