Make tags match their parents case

https://github.com/ankitects/anki/pull/900/#issuecomment-762018745
This commit is contained in:
abdo 2021-01-19 02:29:09 +03:00
parent 318cc01c73
commit 3159cf4ab6
2 changed files with 54 additions and 10 deletions

View File

@ -2,7 +2,8 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use super::SqliteStorage;
use crate::{err::Result, tags::Tag, types::Usn};
use crate::err::{AnkiError, Result};
use crate::{tags::Tag, types::Usn};
use rusqlite::{params, Row, NO_PARAMS};
use std::collections::HashMap;
@ -15,6 +16,10 @@ fn row_to_tag(row: &Row) -> Result<Tag> {
})
}
fn immediate_parent_name(tag_name: &str) -> Option<&str> {
tag_name.rsplitn(2, "::").nth(1)
}
impl SqliteStorage {
/// All tags in the collection, in alphabetical order.
pub(crate) fn all_tags(&self) -> Result<Vec<Tag>> {
@ -56,12 +61,43 @@ impl SqliteStorage {
Ok(())
}
pub(crate) fn preferred_tag_case(&self, tag: &str) -> Result<Option<String>> {
/// If parent tag(s) exist, rewrite name to match their case.
fn match_parents(&self, tag: &str) -> Result<String> {
let child_split: Vec<_> = tag.split("::").collect();
let t = if let Some(parent_tag) = self.first_existing_parent(&tag)? {
let parent_count = parent_tag.matches("::").count() + 1;
format!(
"{}::{}",
parent_tag,
&child_split[parent_count..].join("::")
)
} else {
tag.into()
};
Ok(t)
}
fn first_existing_parent(&self, mut tag: &str) -> Result<Option<String>> {
while let Some(parent_name) = immediate_parent_name(tag) {
if let Some(parent_tag) = self.get_tag(parent_name)? {
return Ok(Some(parent_tag.name));
}
tag = parent_name;
}
Ok(None)
}
// Get stored tag name or the same passed name if it doesn't exist, rewritten to match parents case.
// Returns a tuple of the preferred name and a boolean indicating if the tag exists.
pub(crate) fn preferred_tag_case(&self, tag: &str) -> Result<(Result<String>, bool)> {
self.db
.prepare_cached("select tag from tags where tag = ?")?
.query_and_then(params![tag], |row| row.get(0))?
.next()
.transpose()
.query_row(params![tag], |row| {
Ok((self.match_parents(row.get_raw(0).as_str()?), true))
})
.or_else::<AnkiError, _>(|_| Ok((self.match_parents(tag), false)))
.map_err(Into::into)
}

View File

@ -225,13 +225,13 @@ impl Collection {
if normalized_name.is_empty() {
return Ok((t, false));
}
if let Some(preferred) = self.storage.preferred_tag_case(&normalized_name)? {
t.name = preferred;
Ok((t, false))
} else {
let (preferred, exists) = self.storage.preferred_tag_case(&normalized_name)?;
t.name = preferred?;
if !exists {
self.storage.register_tag(&t)?;
Ok((t, true))
}
Ok((t, !exists))
}
pub fn clear_unused_tags(&self) -> Result<()> {
@ -533,6 +533,14 @@ mod test {
)
);
// children should match the case of their parents
col.storage.clear_tags()?;
*(&mut note.tags[0]) = "FOO".into();
*(&mut note.tags[1]) = "foo::BAR".into();
*(&mut note.tags[2]) = "foo::bar::baz".into();
col.update_note(&mut note)?;
assert_eq!(note.tags, vec!["FOO", "FOO::BAR", "FOO::BAR::baz"]);
Ok(())
}