aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/cli/src/run.rs67
-rw-r--r--lib/mihi/src/lib.rs67
-rw-r--r--testdata/test.sqlite3bin2916352 -> 2916352 bytes
3 files changed, 112 insertions, 22 deletions
diff --git a/crates/cli/src/run.rs b/crates/cli/src/run.rs
index 45149ac..beafc72 100644
--- a/crates/cli/src/run.rs
+++ b/crates/cli/src/run.rs
@@ -33,6 +33,7 @@ fn help(msg: Option<&str>) {
println!(" -h, --help\t\t\tPrint this message.");
println!(" -i, --inflection\t\tOnly practice word inflections (completing enunciates, declensions and conjugations.");
println!(" -k, --kind <KIND>\t\tOnly ask for exercises for the given <KIND>.");
+ println!(" -t, --tag <NAME>\t\tFilter words which match the given tag NAME. Multiple tags can be provided to match words with any of the tags provided.");
}
// Run the quiz for all the given `words` while expecting answers to be
@@ -350,14 +351,39 @@ fn run_inflect_words(words: &Vec<Word>, locale: &Locale) -> bool {
// Returns a vector of words which contain a randomized set of words from
// different categories.
-fn select_general_words(flags: &Vec<String>) -> Result<Vec<Word>, String> {
- let mut res = select_relevant_words(Category::Noun, flags, 4)?;
- res.append(&mut select_relevant_words(Category::Adjective, flags, 2)?);
- res.append(&mut select_relevant_words(Category::Verb, flags, 4)?);
- res.append(&mut select_relevant_words(Category::Pronoun, flags, 1)?);
- res.append(&mut select_relevant_words(Category::Adverb, flags, 2)?);
- res.append(&mut select_relevant_words(Category::Preposition, flags, 1)?);
- res.append(&mut select_relevant_words(Category::Conjunction, flags, 1)?);
+fn select_general_words(flags: &[String], tags: &[String]) -> Result<Vec<Word>, String> {
+ let mut res = select_relevant_words(Category::Noun, flags, tags, 4)?;
+ res.append(&mut select_relevant_words(
+ Category::Adjective,
+ flags,
+ tags,
+ 2,
+ )?);
+ res.append(&mut select_relevant_words(Category::Verb, flags, tags, 4)?);
+ res.append(&mut select_relevant_words(
+ Category::Pronoun,
+ flags,
+ tags,
+ 1,
+ )?);
+ res.append(&mut select_relevant_words(
+ Category::Adverb,
+ flags,
+ tags,
+ 2,
+ )?);
+ res.append(&mut select_relevant_words(
+ Category::Preposition,
+ flags,
+ tags,
+ 1,
+ )?);
+ res.append(&mut select_relevant_words(
+ Category::Conjunction,
+ flags,
+ tags,
+ 1,
+ )?);
Ok(res)
}
@@ -494,6 +520,7 @@ pub fn run(args: Vec<String>) {
let mut inflection_only = false;
let mut endless = false;
let mut flags: Vec<String> = vec![];
+ let mut tags: Vec<String> = vec![];
while let Some(first) = it.next() {
match first.as_str() {
@@ -585,6 +612,22 @@ pub fn run(args: Vec<String>) {
}
}
}
+ "-t" | "--tag" => match it.next() {
+ Some(t) => {
+ let name = t.trim().to_string();
+ if let Ok(results) = mihi::select_tag_names(&Some(name.clone())) {
+ if results.is_empty() {
+ println!("warning: practice: the tag '{}' does not exist.", name);
+ } else {
+ tags.push(name)
+ }
+ }
+ }
+ None => {
+ help(Some("error: practice: you have to provide a tag name"));
+ std::process::exit(1);
+ }
+ },
_ => {
help(Some(
format!("error: practice: unknown flag or command '{first}'").as_str(),
@@ -597,9 +640,10 @@ pub fn run(args: Vec<String>) {
let locale = current_locale();
loop {
+ // Select the words depending on the selected category, flags, etc.
let words = match category {
- Some(cat) => select_relevant_words(cat, &flags, 15),
- None => select_general_words(&flags),
+ Some(cat) => select_relevant_words(cat, &flags, &tags, 15),
+ None => select_general_words(&flags, &tags),
};
if !exercises_only {
@@ -611,7 +655,8 @@ pub fn run(args: Vec<String>) {
Some(cat) => vec![cat],
None => vec![Category::Noun, Category::Adjective],
};
- if let Ok(words_to_inflect) = mihi::select_words_except(&list, &cats, &flags) {
+ if let Ok(words_to_inflect) = mihi::select_words_except(&list, &cats, &flags, &tags)
+ {
if !run_inflect_words(&words_to_inflect, &locale) {
break;
}
diff --git a/lib/mihi/src/lib.rs b/lib/mihi/src/lib.rs
index c15e1ed..f563645 100644
--- a/lib/mihi/src/lib.rs
+++ b/lib/mihi/src/lib.rs
@@ -782,7 +782,7 @@ pub fn find_by(enunciated: &str) -> Result<Word, String> {
// `flags` are set for a row. If no flags are given, then an empty string is
// returned. Otherwise the string is prepended by an "AND" clause, meaning that
// it expects the caller to have other clauses before this one.
-fn flags_clause(flags: &Vec<String>) -> String {
+fn flags_clause(flags: &[String]) -> String {
if flags.is_empty() {
return "".to_string();
}
@@ -796,15 +796,18 @@ fn flags_clause(flags: &Vec<String>) -> String {
}
// Select a maximum of `number` words which match a given word `category` and
-// have set one of the given boolean `flags`.
+// have set one of the given boolean `flags`. You may also pass a `tags` vector
+// which contains the name of the tags for which each word must have at least
+// one match.
pub fn select_relevant_words(
category: Category,
- flags: &Vec<String>,
+ flags: &[String],
+ tags: &[String],
number: isize,
) -> Result<Vec<Word>, String> {
let conn = get_connection()?;
- let mut stmt = conn
- .prepare(
+ let mut stmt = if tags.is_empty() {
+ conn.prepare(
format!(
"SELECT id, enunciated, particle, language_id, declension_id, conjugation_id, \
kind, category, regular, locative, gender, suffix, translation, \
@@ -817,7 +820,26 @@ pub fn select_relevant_words(
)
.as_str(),
)
- .unwrap();
+ .unwrap()
+ } else {
+ conn.prepare(
+ format!(
+ "SELECT w.id, w.enunciated, w.particle, w.language_id, w.declension_id, w.conjugation_id, \
+ w.kind, w.category, w.regular, w.locative, w.gender, w.suffix, w.translation, \
+ w.succeeded, w.steps, w.flags, w.weight \
+ FROM words w \
+ JOIN tag_associations ta ON w.id = ta.word_id \
+ JOIN tags t ON t.id = ta.tag_id \
+ WHERE w.category = ?1 AND t.name IN ({}) AND w.translation != '{{}}' {} \
+ ORDER BY w.weight DESC, w.succeeded ASC, w.updated_at DESC
+ LIMIT ?2",
+ tags.iter().map(|t| format!("'{}'", t)).collect::<Vec<_>>().join(", "),
+ flags_clause(flags)
+ )
+ .as_str(),
+ )
+ .unwrap()
+ };
let mut it = stmt.query([category as isize, number]).unwrap();
let mut res = vec![];
@@ -848,11 +870,13 @@ pub fn select_relevant_words(
/// Select a set of words except for the ones passed in the `excluded`
/// vector. You have to pass the categories to be selected via the `categories`
/// parameter, which cannot be empty. It also accepts a set of boolean `flags`
-/// as with functions like `select_relevant_words`.
+/// as with functions like `select_relevant_words`; and the `tags` filtering
+/// option.
pub fn select_words_except(
excluded: &[Word],
categories: &[Category],
- flags: &Vec<String>,
+ flags: &[String],
+ tags: &[String],
) -> Result<Vec<Word>, String> {
assert!(!categories.is_empty());
@@ -865,8 +889,8 @@ pub fn select_words_except(
.join(", ");
let conn = get_connection()?;
- let mut stmt = conn
- .prepare(
+ let mut stmt = if tags.is_empty() {
+ conn.prepare(
format!(
"SELECT id, enunciated, particle, language_id, declension_id, conjugation_id, \
kind, category, regular, locative, gender, suffix, translation, \
@@ -881,7 +905,28 @@ pub fn select_words_except(
)
.as_str(),
)
- .unwrap();
+ .unwrap()
+ } else {
+ conn.prepare(
+ format!(
+ "SELECT w.id, w.enunciated, w.particle, w.language_id, w.declension_id, w.conjugation_id, \
+ w.kind, w.category, w.regular, w.locative, w.gender, w.suffix, w.translation, \
+ w.succeeded, w.steps, w.flags, w.weight \
+ FROM words w \
+ JOIN tag_associations ta ON w.id = ta.word_id \
+ JOIN tags t ON t.id = ta.tag_id \
+ WHERE w.id NOT IN ({}) AND t.name IN ({}) AND w.category IN ({}) AND w.translation != '{{}}' {} \
+ ORDER BY w.weight DESC, w.succeeded ASC, w.updated_at DESC
+ LIMIT 5",
+ placeholders,
+ tags.iter().map(|t| format!("'{}'", t)).collect::<Vec<_>>().join(", "),
+ cats,
+ flags_clause(flags)
+ )
+ .as_str(),
+ )
+ .unwrap()
+ };
let mut it = stmt.query(rusqlite::params_from_iter(ids)).unwrap();
let mut res = vec![];
diff --git a/testdata/test.sqlite3 b/testdata/test.sqlite3
index 96132a1..43d7ca0 100644
--- a/testdata/test.sqlite3
+++ b/testdata/test.sqlite3
Binary files differ