Skip to content

Commit

Permalink
Make slug a structure for re-usability
Browse files Browse the repository at this point in the history
  • Loading branch information
System-Glitch committed Oct 30, 2020
1 parent 8535650 commit 8024231
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 33 deletions.
111 changes: 81 additions & 30 deletions slug.go
Expand Up @@ -38,21 +38,40 @@ var (

//=============================================================================

// Slug a structure containing local settings. Using it allows for concurrent slugging
// with different settings.
type Slug struct {
CustomSub map[string]string
CustomRuneSub map[rune]string
MaxLength int
Lowercase bool
}

// New create a new Slug structure using the default settings.
func New() *Slug {
return &Slug{
CustomSub: cloneStringMap(CustomSub),
CustomRuneSub: cloneRuneMap(CustomRuneSub),
MaxLength: MaxLength,
Lowercase: Lowercase,
}
}

// Make returns slug generated from provided string. Will use "en" as language
// substitution.
func Make(s string) (slug string) {
return MakeLang(s, "en")
func (sl *Slug) Make(s string) (slug string) {
return sl.MakeLang(s, "en")
}

// MakeLang returns slug generated from provided string and will use provided
// language for chars substitution.
func MakeLang(s string, lang string) (slug string) {
func (sl *Slug) MakeLang(s string, lang string) (slug string) {
slug = strings.TrimSpace(s)

// Custom substitutions
// Always substitute runes first
slug = SubstituteRune(slug, CustomRuneSub)
slug = Substitute(slug, CustomSub)
slug = SubstituteRune(slug, sl.CustomRuneSub)
slug = Substitute(slug, sl.CustomSub)

// Process string with selected substitution language.
// Catch ISO 3166-1, ISO 639-1:2002 and ISO 639-3:2007.
Expand Down Expand Up @@ -84,7 +103,7 @@ func MakeLang(s string, lang string) (slug string) {
// Process all non ASCII symbols
slug = unidecode.Unidecode(slug)

if Lowercase {
if sl.Lowercase {
slug = strings.ToLower(slug)
}

Expand All @@ -93,13 +112,51 @@ func MakeLang(s string, lang string) (slug string) {
slug = regexpMultipleDashes.ReplaceAllString(slug, "-")
slug = strings.Trim(slug, "-_")

if MaxLength > 0 {
slug = smartTruncate(slug)
if sl.MaxLength > 0 {
slug = sl.smartTruncate(slug)
}

return slug
}

func (sl *Slug) smartTruncate(text string) string {
if len(text) < sl.MaxLength {
return text
}

var truncated string
words := strings.SplitAfter(text, "-")
// If MaxLength is smaller than length of the first word return word
// truncated after MaxLength.
if len(words[0]) > sl.MaxLength {
return words[0][:sl.MaxLength]
}
for _, word := range words {
if len(truncated)+len(word)-1 <= sl.MaxLength {
truncated = truncated + word
} else {
break
}
}
return strings.Trim(truncated, "-")
}

//=============================================================================

// Make returns slug generated from provided string. Will use "en" as language
// substitution.
// Global settings will be used.
func Make(s string) (slug string) {
return MakeLang(s, "en")
}

// MakeLang returns slug generated from provided string and will use provided
// language for chars substitution.
// Global settings will be used.
func MakeLang(s string, lang string) (slug string) {
return New().MakeLang(s, lang)
}

// Substitute returns string with superseded all substrings from
// provided substitution map. Substitution map will be applied in alphabetic
// order. Many passes, on one substitution another one could apply.
Expand Down Expand Up @@ -131,28 +188,6 @@ func SubstituteRune(s string, sub map[rune]string) string {
return buf.String()
}

func smartTruncate(text string) string {
if len(text) < MaxLength {
return text
}

var truncated string
words := strings.SplitAfter(text, "-")
// If MaxLength is smaller than length of the first word return word
// truncated after MaxLength.
if len(words[0]) > MaxLength {
return words[0][:MaxLength]
}
for _, word := range words {
if len(truncated)+len(word)-1 <= MaxLength {
truncated = truncated + word
} else {
break
}
}
return strings.Trim(truncated, "-")
}

// IsSlug returns True if provided text does not contain white characters,
// punctuation, all letters are lower case and only from ASCII range.
// It could contain `-` and `_` but not at the beginning or end of the text.
Expand All @@ -172,3 +207,19 @@ func IsSlug(text string) bool {
}
return true
}

func cloneStringMap(m map[string]string) map[string]string {
new := make(map[string]string, len(m))
for k, v := range m {
new[k] = v
}
return new
}

func cloneRuneMap(m map[rune]string) map[rune]string {
new := make(map[rune]string, len(m))
for k, v := range m {
new[k] = v
}
return new
}
9 changes: 6 additions & 3 deletions slug_test.go
Expand Up @@ -344,11 +344,12 @@ func BenchmarkMakeLongAscii(b *testing.B) {
"nisl. Etiam varius imperdiet placerat. Aliquam euismod lacus arcu, " +
"ultrices hendrerit est pellentesque vel. Aliquam sit amet laoreet leo. " +
"Integer eros libero, mollis sed posuere."
slug := New()

b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
Make(longStr)
slug.Make(longStr)
}
}

Expand Down Expand Up @@ -399,11 +400,12 @@ func BenchmarkSubstituteRuneLong(b *testing.B) {
func BenchmarkSmartTruncateShort(b *testing.B) {
shortStr := "Hello-world"
MaxLength = 8
slug := New()

b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
smartTruncate(shortStr)
slug.smartTruncate(shortStr)
}
}

Expand All @@ -424,11 +426,12 @@ func BenchmarkSmartTruncateLong(b *testing.B) {
"ultrices-hendrerit-est-pellentesque-vel.-Aliquam-sit-amet-laoreet-leo.-" +
"Integer-eros-libero,-mollis-sed-posuere."
MaxLength = 256
slug := New()

b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
smartTruncate(longStr)
slug.smartTruncate(longStr)
}
}

Expand Down

0 comments on commit 8024231

Please sign in to comment.