From fb85253f66b3a916d90d4a490c946f21c88c7283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dobros=C5=82aw=20=C5=BBybort?= Date: Thu, 13 Dec 2018 22:18:24 +0100 Subject: [PATCH 1/3] Add possibility to set custom separator --- slug.go | 27 +++++++++++++++-------- slug_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 9 deletions(-) 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..9f9f573 100644 --- a/slug_test.go +++ b/slug_test.go @@ -155,6 +155,36 @@ func TestSlugMakeSubstituteOrderLang(t *testing.T) { } } +func TestSlugSeparator(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 TestSubstituteLang(t *testing.T) { var testCases = []struct { cSub map[string]string @@ -208,6 +238,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 +297,36 @@ func TestIsSlug(t *testing.T) { }) } +func TestIsSlugSeparator(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++ { From b4fc4d2cdc02aa6e4af0537065c614925485af3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dobros=C5=82aw=20=C5=BBybort?= Date: Thu, 13 Dec 2018 22:40:11 +0100 Subject: [PATCH 2/3] Use custom separator for defaultSub --- languages_substitution.go | 9 +++++---- slug_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) 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_test.go b/slug_test.go index 9f9f573..61fa198 100644 --- a/slug_test.go +++ b/slug_test.go @@ -185,6 +185,35 @@ func TestSlugSeparator(t *testing.T) { Separator = '-' } +func TestSlugMakeLangSeparator(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 TestSubstituteLang(t *testing.T) { var testCases = []struct { cSub map[string]string From ed7dcbd7d943adccc9bde273ae19196cde3dc7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dobros=C5=82aw=20=C5=BBybort?= Date: Thu, 13 Dec 2018 22:42:45 +0100 Subject: [PATCH 3/3] Stick separator tests together --- slug_test.go | 120 +++++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/slug_test.go b/slug_test.go index 61fa198..2ebf4c4 100644 --- a/slug_test.go +++ b/slug_test.go @@ -155,65 +155,6 @@ func TestSlugMakeSubstituteOrderLang(t *testing.T) { } } -func TestSlugSeparator(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 TestSlugMakeLangSeparator(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 TestSubstituteLang(t *testing.T) { var testCases = []struct { cSub map[string]string @@ -326,7 +267,66 @@ func TestIsSlug(t *testing.T) { }) } -func TestIsSlugSeparator(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