Slug joiner normalization
This commit is contained in:
@@ -715,20 +715,7 @@ func normalizeWikiTitle(value string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func slugifyWikiTitle(value string) string {
|
func slugifyWikiTitle(value string) string {
|
||||||
var b strings.Builder
|
return slugifyWikiText(value, "")
|
||||||
lastDash := false
|
|
||||||
for _, r := range strings.ToLower(strings.TrimSpace(value)) {
|
|
||||||
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
|
|
||||||
b.WriteRune(r)
|
|
||||||
lastDash = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !lastDash && b.Len() > 0 {
|
|
||||||
b.WriteByte('-')
|
|
||||||
lastDash = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Trim(b.String(), "-")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDeployManifest(path string) wikiDeployManifest {
|
func loadDeployManifest(path string) wikiDeployManifest {
|
||||||
|
|||||||
@@ -168,6 +168,21 @@ func TestMatchExistingNodeBBPagePrefersGeneratedPublicSlug(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeploySlugifyWikiTitleMatchesGeneratedSlugPolicy(t *testing.T) {
|
||||||
|
tests := map[string]string{
|
||||||
|
`Grandmaster's Battle Momentum`: "grandmasters-battle-momentum",
|
||||||
|
`Bigby’s Clenched Fist`: "bigbys-clenched-fist",
|
||||||
|
`Hardiness vs. Enchantments`: "hardiness-vs-enchantments",
|
||||||
|
`Clairaudience/Clairvoyance`: "clairaudience-clairvoyance",
|
||||||
|
`Élite & Noble Houses`: "elite-noble-houses",
|
||||||
|
}
|
||||||
|
for input, want := range tests {
|
||||||
|
if got := slugifyWikiTitle(input); got != want {
|
||||||
|
t.Fatalf("slugifyWikiTitle(%q) = %q, want %q", input, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeployWikiCreatesNodeBBTopicAndWritesManifest(t *testing.T) {
|
func TestDeployWikiCreatesNodeBBTopicAndWritesManifest(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
sourceDir := filepath.Join(root, "pages")
|
sourceDir := filepath.Join(root, "pages")
|
||||||
|
|||||||
@@ -1682,25 +1682,7 @@ func wikiPublicTarget(namespace, slug string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func wikiSlugifyTitle(value string) string {
|
func wikiSlugifyTitle(value string) string {
|
||||||
value = html.UnescapeString(strings.TrimSpace(value))
|
return slugifyWikiText(value, "topic")
|
||||||
var b strings.Builder
|
|
||||||
lastDash := false
|
|
||||||
for _, r := range strings.ToLower(value) {
|
|
||||||
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
|
|
||||||
b.WriteRune(r)
|
|
||||||
lastDash = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if b.Len() > 0 && !lastDash {
|
|
||||||
b.WriteByte('-')
|
|
||||||
lastDash = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
slug := strings.Trim(b.String(), "-")
|
|
||||||
if slug == "" {
|
|
||||||
return "topic"
|
|
||||||
}
|
|
||||||
return slug
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func wikiPageIDToRelPath(pageID string) string {
|
func wikiPageIDToRelPath(pageID string) string {
|
||||||
|
|||||||
@@ -1239,6 +1239,25 @@ func TestWikiPageSlugOverridesChangeTargets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWikiSlugifyTitleNormalizesPunctuationAndSeparators(t *testing.T) {
|
||||||
|
tests := map[string]string{
|
||||||
|
`Grandmaster's Battle Momentum`: "grandmasters-battle-momentum",
|
||||||
|
`Bigby’s Clenched Fist`: "bigbys-clenched-fist",
|
||||||
|
`"Quoted" ‘Curly’ “Title”`: "quoted-curly-title",
|
||||||
|
`Hardiness vs. Enchantments`: "hardiness-vs-enchantments",
|
||||||
|
`Clairaudience/Clairvoyance`: "clairaudience-clairvoyance",
|
||||||
|
`Moon-on-a-Stick`: "moon-on-a-stick",
|
||||||
|
`Élite & Noble Houses`: "elite-noble-houses",
|
||||||
|
`Rock 'n' Roll`: "rock-n-roll",
|
||||||
|
`Melf's Acid Arrow`: "melfs-acid-arrow",
|
||||||
|
}
|
||||||
|
for input, want := range tests {
|
||||||
|
if got := wikiSlugifyTitle(input); got != want {
|
||||||
|
t.Fatalf("wikiSlugifyTitle(%q) = %q, want %q", input, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPublicWikiTargetUsesTargetTitleBeforeDisplayLabel(t *testing.T) {
|
func TestPublicWikiTargetUsesTargetTitleBeforeDisplayLabel(t *testing.T) {
|
||||||
ctx := &wikiContext{
|
ctx := &wikiContext{
|
||||||
rowsByKey: map[string]map[string]any{
|
rowsByKey: map[string]map[string]any{
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package topdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func slugifyWikiText(value string, fallback string) string {
|
||||||
|
value = html.UnescapeString(strings.TrimSpace(value))
|
||||||
|
var b strings.Builder
|
||||||
|
lastDash := false
|
||||||
|
for _, r := range norm.NFKD.String(strings.ToLower(value)) {
|
||||||
|
switch {
|
||||||
|
case r >= 'a' && r <= 'z':
|
||||||
|
b.WriteRune(r)
|
||||||
|
lastDash = false
|
||||||
|
case r >= '0' && r <= '9':
|
||||||
|
b.WriteRune(r)
|
||||||
|
lastDash = false
|
||||||
|
case unicode.Is(unicode.Mn, r):
|
||||||
|
continue
|
||||||
|
case isWikiSlugJoinerPunctuation(r):
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
if b.Len() > 0 && !lastDash {
|
||||||
|
b.WriteByte('-')
|
||||||
|
lastDash = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slug := strings.Trim(b.String(), "-")
|
||||||
|
if slug == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return slug
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWikiSlugJoinerPunctuation(r rune) bool {
|
||||||
|
switch r {
|
||||||
|
case '\'', '"', '`', '´', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '‹', '›', '«', '»', '′', '″', ''', '"':
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user