diff --git a/languages_substitution.go b/languages_substitution.go index d6664e2..a1932be 100644 --- a/languages_substitution.go +++ b/languages_substitution.go @@ -16,14 +16,15 @@ func init() { } } +// BUG: Initialized on init so also impossible to set custom separator. var defaultSub = map[rune]string{ '"': "", '\'': "", '’': "", - '‒': "-", // figure dash - '–': "-", // en dash - '—': "-", // em dash - '―': "-", // horizontal bar + '‒': string(Separator), // figure dash + '–': string(Separator), // en dash + '—': string(Separator), // em dash + '―': string(Separator), // horizontal bar } var deSub = map[rune]string{ diff --git a/slug.go b/slug.go index 0c6801f..92a40e6 100644 --- a/slug.go +++ b/slug.go @@ -28,8 +28,13 @@ var ( // after MaxLength. MaxLength int - regexpNonAuthorizedChars = regexp.MustCompile("[^a-z0-9-_]") - regexpMultipleDashes = regexp.MustCompile("-+") + // Separator configure default separator for all slugs. + // By default set to '-'. + Separator = '-' + + // BUG: Not possible to change separator from default. + regexpNonAuthorizedChars = regexp.MustCompile("[^a-z0-9-_" + string(Separator) + "]") + regexpMultipleSeparators = regexp.MustCompile(string(Separator) + "+") ) //============================================================================= @@ -76,14 +81,15 @@ func MakeLang(s string, lang string) (slug string) { slug = strings.ToLower(slug) // Process all remaining symbols - slug = regexpNonAuthorizedChars.ReplaceAllString(slug, "-") - slug = regexpMultipleDashes.ReplaceAllString(slug, "-") - slug = strings.Trim(slug, "-_") + slug = regexpNonAuthorizedChars.ReplaceAllString(slug, string(Separator)) + slug = regexpMultipleSeparators.ReplaceAllString(slug, string(Separator)) if MaxLength > 0 { slug = smartTruncate(slug) } + slug = strings.Trim(slug, "-_"+string(Separator)) + return slug } @@ -137,7 +143,7 @@ func smartTruncate(text string) string { break } } - return strings.Trim(truncated, "-") + return truncated } // IsSlug returns True if provided text does not contain white characters, @@ -148,12 +154,15 @@ func smartTruncate(text string) string { func IsSlug(text string) bool { if text == "" || (MaxLength > 0 && len(text) > MaxLength) || - text[0] == '-' || text[0] == '_' || - text[len(text)-1] == '-' || text[len(text)-1] == '_' { + strings.HasPrefix(text, "-") || strings.HasPrefix(text, "_") || + strings.HasPrefix(text, string(Separator)) || + strings.HasSuffix(text, "-") || strings.HasSuffix(text, "_") || + strings.HasSuffix(text, string(Separator)) { return false } for _, c := range text { - if (c < 'a' || c > 'z') && c != '-' && c != '_' && (c < '0' || c > '9') { + if (c < 'a' || c > 'z') && (c < '0' || c > '9') && + c != '-' && c != '_' && c != Separator { return false } } diff --git a/slug_test.go b/slug_test.go index a6795f8..2ebf4c4 100644 --- a/slug_test.go +++ b/slug_test.go @@ -208,6 +208,7 @@ func TestSlugMakeSmartTruncate(t *testing.T) { {"DOBROSLAWZYBORT", 100, "dobroslawzybort"}, {"Dobroslaw Zybort", 100, "dobroslaw-zybort"}, {"Dobroslaw Zybort", 12, "dobroslaw"}, + {"Dobroslaw-Zybort_-_-", 11, "dobroslaw"}, {" Dobroslaw Zybort ?", 12, "dobroslaw"}, {"Ala ma 6 kotów.", 10, "ala-ma-6"}, {"Dobrosław Żybort", 5, "dobro"}, @@ -266,6 +267,95 @@ func TestIsSlug(t *testing.T) { }) } +func TestSeparatorSlug(t *testing.T) { + MaxLength = 0 + type args struct { + separator rune + text string + } + tests := []struct { + name string + args args + want string + }{ + {"separator -", args{'-', "test---slug"}, "test-slug"}, + {"separator _", args{'_', "test___slug"}, "test_slug"}, + {"separator /", args{'/', "test///slug"}, "test/slug"}, + {"separator ☺", args{'☺', "test slug"}, "test☺slug"}, + {"remove ASCII first", args{'☺', "test☺☺slug ☺"}, "testslug"}, + } + for _, tt := range tests { + Separator = tt.args.separator + + t.Run(tt.name, func(t *testing.T) { + if got := Make(tt.args.text); got != tt.want { + t.Errorf("Make() = %v, want %v", got, tt.want) + } + }) + } + // Global state... + Separator = '-' +} + +func TestSeparatorSlugMakeLang(t *testing.T) { + var testCases = []struct { + separator rune + lang string + in string + want string + }{ + // & fun. + {'_', "de", "This & that", "this_und_that"}, + {'_', "en", "This & that", "this_and_that"}, + {'_', "test", "This & that", "this_and_that"}, // unknown lang, fallback to "en" + // Test defaultSub. + {'_', "de", "1\"2'3’4‒5–6—7―8", "1234_5_6_7_8"}, + {'_', "en", "1\"2'3’4‒5–6—7―8", "1234_5_6_7_8"}, + } + + for index, smlt := range testCases { + Separator = smlt.separator + got := MakeLang(smlt.in, smlt.lang) + if got != smlt.want { + t.Errorf( + "%d. MakeLang(%#v, %#v) = %#v; want %#v", + index, smlt.in, smlt.lang, got, smlt.want) + } + } + // Global state... + Separator = '-' +} + +func TestSeparatorIsSlug(t *testing.T) { + MaxLength = 0 + type args struct { + separator rune + text string + } + tests := []struct { + name string + args args + want bool + }{ + {"separator -", args{'-', "test slug"}, true}, + {"separator _", args{'_', "test slug"}, true}, + {"separator /", args{'/', "test slug"}, true}, + {"separator ☺", args{'☺', "test slug"}, true}, + } + for _, tt := range tests { + Separator = tt.args.separator + + t.Run(tt.name, func(t *testing.T) { + slug := Make(tt.args.text) + if got := IsSlug(slug); got != tt.want { + t.Errorf("IsSlug('%s') = %v, want %v", slug, got, tt.want) + } + }) + } + // Global state... + Separator = '-' +} + func BenchmarkMakeShortAscii(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ {