Match post tag names exactly
This commit is contained in:
@@ -78,7 +78,9 @@ pub fn update_tag(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a tag: remove from all posts' tag arrays, delete from DB, rewrite tags.json.
|
/// Delete a tag: remove its exact name from posts, delete from DB, rewrite tags.json.
|
||||||
|
/// Tag entity lookup remains case-insensitive, but portable post tag arrays
|
||||||
|
/// follow bDS2 and Allium's exact string membership semantics.
|
||||||
pub fn delete_tag(
|
pub fn delete_tag(
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
data_dir: &Path,
|
data_dir: &Path,
|
||||||
@@ -96,7 +98,9 @@ pub fn delete_tag(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rename a tag: update all posts' tag arrays, update tag in DB, rewrite tags.json.
|
/// Rename a tag: replace its exact name in posts, update the DB and tags.json.
|
||||||
|
/// Tag entity lookup remains case-insensitive, but portable post tag arrays
|
||||||
|
/// follow bDS2 and Allium's exact string membership semantics.
|
||||||
pub fn rename_tag(
|
pub fn rename_tag(
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
data_dir: &Path,
|
data_dir: &Path,
|
||||||
@@ -113,12 +117,12 @@ pub fn rename_tag(
|
|||||||
let now = now_unix_ms();
|
let now = now_unix_ms();
|
||||||
let mut modified = Vec::new();
|
let mut modified = Vec::new();
|
||||||
for mut post in posts {
|
for mut post in posts {
|
||||||
if post.tags.iter().any(|t| t.eq_ignore_ascii_case(&old_name)) {
|
if post.tags.iter().any(|t| t == &old_name) {
|
||||||
post.tags = post
|
post.tags = post
|
||||||
.tags
|
.tags
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|t| {
|
.map(|t| {
|
||||||
if t.eq_ignore_ascii_case(&old_name) {
|
if t == old_name {
|
||||||
new_name.to_string()
|
new_name.to_string()
|
||||||
} else {
|
} else {
|
||||||
t
|
t
|
||||||
@@ -142,6 +146,8 @@ pub fn rename_tag(
|
|||||||
|
|
||||||
/// Merge multiple source tags into one target tag.
|
/// Merge multiple source tags into one target tag.
|
||||||
/// For each source: update posts (remove source name, add target name if not present), delete source.
|
/// For each source: update posts (remove source name, add target name if not present), delete source.
|
||||||
|
/// Source and target membership in portable post tag arrays is exact, matching
|
||||||
|
/// bDS2 and Allium; tag entity lookup and uniqueness remain case-insensitive.
|
||||||
pub fn merge_tags(
|
pub fn merge_tags(
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
data_dir: &Path,
|
data_dir: &Path,
|
||||||
@@ -161,20 +167,12 @@ pub fn merge_tags(
|
|||||||
let posts = post_q::list_posts_by_project(conn, project_id)?;
|
let posts = post_q::list_posts_by_project(conn, project_id)?;
|
||||||
let now = now_unix_ms();
|
let now = now_unix_ms();
|
||||||
for mut post in posts {
|
for mut post in posts {
|
||||||
let has_source = post
|
let has_source = post.tags.iter().any(|t| t == &source_tag.name);
|
||||||
.tags
|
|
||||||
.iter()
|
|
||||||
.any(|t| t.eq_ignore_ascii_case(&source_tag.name));
|
|
||||||
if has_source {
|
if has_source {
|
||||||
// Remove source tag name
|
// Remove source tag name
|
||||||
post.tags
|
post.tags.retain(|t| t != &source_tag.name);
|
||||||
.retain(|t| !t.eq_ignore_ascii_case(&source_tag.name));
|
|
||||||
// Add target tag name if not already present
|
// Add target tag name if not already present
|
||||||
if !post
|
if !post.tags.iter().any(|t| t == &target_tag.name) {
|
||||||
.tags
|
|
||||||
.iter()
|
|
||||||
.any(|t| t.eq_ignore_ascii_case(&target_tag.name))
|
|
||||||
{
|
|
||||||
post.tags.push(target_tag.name.clone());
|
post.tags.push(target_tag.name.clone());
|
||||||
}
|
}
|
||||||
post.updated_at = now;
|
post.updated_at = now;
|
||||||
@@ -354,8 +352,8 @@ fn remove_tag_name_from_posts(
|
|||||||
let now = now_unix_ms();
|
let now = now_unix_ms();
|
||||||
let mut modified = Vec::new();
|
let mut modified = Vec::new();
|
||||||
for mut post in posts {
|
for mut post in posts {
|
||||||
if post.tags.iter().any(|t| t.eq_ignore_ascii_case(tag_name)) {
|
if post.tags.iter().any(|t| t == tag_name) {
|
||||||
post.tags.retain(|t| !t.eq_ignore_ascii_case(tag_name));
|
post.tags.retain(|t| t != tag_name);
|
||||||
post.updated_at = now;
|
post.updated_at = now;
|
||||||
post_q::update_post(conn, &post)?;
|
post_q::update_post(conn, &post)?;
|
||||||
modified.push(post.id.clone());
|
modified.push(post.id.clone());
|
||||||
@@ -519,6 +517,57 @@ mod tests {
|
|||||||
assert_eq!(all[0].name, "target");
|
assert_eq!(all[0].name, "target");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tag_mutations_match_post_tag_names_exactly() {
|
||||||
|
let (db, dir) = setup();
|
||||||
|
let renamed = create_tag(db.conn(), dir.path(), "p1", "rust", None).unwrap();
|
||||||
|
insert_post(
|
||||||
|
db.conn(),
|
||||||
|
&make_post("rename", "rename", vec!["Rust".into(), "rust".into()]),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
rename_tag(db.conn(), dir.path(), "p1", &renamed.id, "golang").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
post_q::get_post_by_id(db.conn(), "rename").unwrap().tags,
|
||||||
|
vec!["Rust", "golang"]
|
||||||
|
);
|
||||||
|
|
||||||
|
let source = create_tag(db.conn(), dir.path(), "p1", "source", None).unwrap();
|
||||||
|
let target = create_tag(db.conn(), dir.path(), "p1", "target", None).unwrap();
|
||||||
|
insert_post(
|
||||||
|
db.conn(),
|
||||||
|
&make_post(
|
||||||
|
"merge",
|
||||||
|
"merge",
|
||||||
|
vec!["SOURCE".into(), "source".into(), "Target".into()],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
merge_tags(db.conn(), dir.path(), "p1", &[&source.id], &target.id).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
post_q::get_post_by_id(db.conn(), "merge").unwrap().tags,
|
||||||
|
vec!["SOURCE", "Target", "target"]
|
||||||
|
);
|
||||||
|
|
||||||
|
let deleted = create_tag(db.conn(), dir.path(), "p1", "delete", None).unwrap();
|
||||||
|
insert_post(
|
||||||
|
db.conn(),
|
||||||
|
&make_post("delete", "delete", vec!["DELETE".into(), "delete".into()]),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
delete_tag(db.conn(), dir.path(), "p1", &deleted.id).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
post_q::get_post_by_id(db.conn(), "delete").unwrap().tags,
|
||||||
|
vec!["DELETE"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sync_tags_from_posts_creates_missing() {
|
fn sync_tags_from_posts_creates_missing() {
|
||||||
let (db, _dir) = setup();
|
let (db, _dir) = setup();
|
||||||
|
|||||||
Reference in New Issue
Block a user