Provide search functionality through the TUI #134
49
Cargo.lock
generated
49
Cargo.lock
generated
@ -235,6 +235,17 @@ version = "2.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
|
checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.28.1"
|
version = "0.28.1"
|
||||||
@ -412,6 +423,7 @@ dependencies = [
|
|||||||
"mockall",
|
"mockall",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"openssh",
|
"openssh",
|
||||||
|
"rand",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -420,6 +432,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -531,6 +544,12 @@ version = "0.2.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "predicates"
|
name = "predicates"
|
||||||
version = "3.1.0"
|
version = "3.1.0"
|
||||||
@ -599,6 +618,36 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui"
|
name = "ratatui"
|
||||||
version = "0.26.0"
|
version = "0.26.0"
|
||||||
|
@ -18,9 +18,13 @@ tokio = { version = "1.36.0", features = ["rt"], optional = true}
|
|||||||
url = { version = "2.5.0" }
|
url = { version = "2.5.0" }
|
||||||
uuid = { version = "1.7.0" }
|
uuid = { version = "1.7.0" }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
version_check = "0.9.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
mockall = "0.12.1"
|
mockall = "0.12.1"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
|
rand = "0.8.5"
|
||||||
tempfile = "3.10.0"
|
tempfile = "3.10.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
15
README.md
15
README.md
@ -45,3 +45,18 @@ Note that some changes may not be visible until `codecov/debug/coverage` is remo
|
|||||||
command is rerun.
|
command is rerun.
|
||||||
|
|
||||||
For most cases `cargo clean` can be replaced with `rm -rf ./codecov/debug/{coverage,profraw}`.
|
For most cases `cargo clean` can be replaced with `rm -rf ./codecov/debug/{coverage,profraw}`.
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
### Pre-requisites
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
rustup toolchain install nightly
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running benchmarks
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
env BEETSDIR=./ rustup run nightly cargo bench --all-features --all-targets
|
||||||
|
```
|
||||||
|
|
||||||
|
5
build.rs
Normal file
5
build.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
fn main() {
|
||||||
|
if let Some(true) = version_check::is_feature_flaggable() {
|
||||||
|
println!("cargo:rustc-cfg=nightly");
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,7 @@
|
|||||||
|
#![cfg_attr(nightly, feature(test))]
|
||||||
|
#[cfg(nightly)]
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
mod tui;
|
mod tui;
|
||||||
|
|
||||||
use std::{ffi::OsString, fs::OpenOptions, io, path::PathBuf};
|
use std::{ffi::OsString, fs::OpenOptions, io, path::PathBuf};
|
||||||
|
@ -18,9 +18,10 @@ use crate::tui::{
|
|||||||
//
|
//
|
||||||
// U+2010 hyphen, U+2012 figure dash, U+2013 en dash, U+2014 em dash, U+2015 horizontal bar, U+2018,
|
// U+2010 hyphen, U+2012 figure dash, U+2013 en dash, U+2014 em dash, U+2015 horizontal bar, U+2018,
|
||||||
// U+2019, U+201C, U+201D, U+2026, U+2212 minus sign
|
// U+2019, U+201C, U+201D, U+2026, U+2212 minus sign
|
||||||
static PATTERNS: [&'static str; 11] = ["‐", "‒", "–", "—", "―", "‘", "’", "“", "”", "…", "−"];
|
static SPECIAL: [char; 11] = ['‐', '‒', '–', '—', '―', '‘', '’', '“', '”', '…', '−'];
|
||||||
static REPLACE: [&'static str; 11] = ["-", "-", "-", "-", "-", "'", "'", "\"", "\"", "...", "-"];
|
static REPLACE: [&str; 11] = ["-", "-", "-", "-", "-", "'", "'", "\"", "\"", "...", "-"];
|
||||||
static AC: Lazy<AhoCorasick> = Lazy::new(|| AhoCorasick::new(&PATTERNS).unwrap());
|
static AC: Lazy<AhoCorasick> =
|
||||||
|
Lazy::new(|| AhoCorasick::new(SPECIAL.map(|ch| ch.to_string())).unwrap());
|
||||||
|
|
||||||
pub struct AppSearch {
|
pub struct AppSearch {
|
||||||
string: String,
|
string: String,
|
||||||
@ -173,20 +174,22 @@ impl<MH: IMusicHoard> IAppInteractSearchPrivate for AppMachine<MH, AppSearch> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_char_sensitive(artist_name: &str) -> bool {
|
fn is_char_sensitive(artist_name: &str) -> bool {
|
||||||
AC.find(artist_name).is_some()
|
// Benchmarking reveals that using AhoCorasick is slower. At a guess, this is likely due to
|
||||||
|
// a high constant cost of AhoCorasick and the otherwise simple nature of the task.
|
||||||
|
artist_name.chars().any(|ch| SPECIAL.contains(&ch))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_search(search: &str, lowercase: bool, asciify: bool) -> String {
|
fn normalize_search(search: &str, lowercase: bool, asciify: bool) -> String {
|
||||||
let normalized = if lowercase {
|
if lowercase {
|
||||||
|
if asciify {
|
||||||
|
AC.replace_all(&search.to_lowercase(), &REPLACE)
|
||||||
|
} else {
|
||||||
search.to_lowercase()
|
search.to_lowercase()
|
||||||
|
}
|
||||||
|
} else if asciify {
|
||||||
|
AC.replace_all(search, &REPLACE)
|
||||||
} else {
|
} else {
|
||||||
search.to_owned()
|
search.to_owned()
|
||||||
};
|
|
||||||
|
|
||||||
if asciify {
|
|
||||||
AC.replace_all(&normalized, &REPLACE)
|
|
||||||
} else {
|
|
||||||
normalized
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -439,3 +442,65 @@ mod tests {
|
|||||||
assert_eq!(browse.inner.selection.artist.state.list.selected(), None);
|
assert_eq!(browse.inner.selection.artist.state.list.selected(), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(nightly)]
|
||||||
|
#[cfg(test)]
|
||||||
|
mod benches {
|
||||||
|
// The purpose of these benches was to evaluate the benefit of AhoCorasick over std solutions.
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
use crate::tui::lib::MockIMusicHoard;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
type Search = AppMachine<MockIMusicHoard, AppSearch>;
|
||||||
|
|
||||||
|
fn random_utf8_string(len: usize) -> String {
|
||||||
|
rand::thread_rng()
|
||||||
|
.sample_iter::<char, _>(&rand::distributions::Standard)
|
||||||
|
.take(len)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_alpanumeric_string(len: usize) -> String {
|
||||||
|
rand::thread_rng()
|
||||||
|
.sample_iter(&rand::distributions::Alphanumeric)
|
||||||
|
.take(len)
|
||||||
|
.map(char::from)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_sample(f: fn(usize) -> String) -> Vec<String> {
|
||||||
|
(0..1000).map(|_| f(10)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn is_char_sensitive_alphanumeric(b: &mut Bencher) {
|
||||||
|
let strings = generate_sample(random_alpanumeric_string);
|
||||||
|
let mut iter = strings.iter().cycle();
|
||||||
|
b.iter(|| test::black_box(Search::is_char_sensitive(&iter.next().unwrap())))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn is_char_sensitive_utf8(b: &mut Bencher) {
|
||||||
|
let strings = generate_sample(random_utf8_string);
|
||||||
|
let mut iter = strings.iter().cycle();
|
||||||
|
b.iter(|| test::black_box(Search::is_char_sensitive(&iter.next().unwrap())))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn normalize_search_alphanumeric(b: &mut Bencher) {
|
||||||
|
let strings = generate_sample(random_alpanumeric_string);
|
||||||
|
let mut iter = strings.iter().cycle();
|
||||||
|
b.iter(|| test::black_box(Search::normalize_search(&iter.next().unwrap(), false, true)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn normalize_search_utf8(b: &mut Bencher) {
|
||||||
|
let strings = generate_sample(random_utf8_string);
|
||||||
|
let mut iter = strings.iter().cycle();
|
||||||
|
b.iter(|| test::black_box(Search::normalize_search(&iter.next().unwrap(), false, true)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user