mirror of
https://github.com/photoprism/photoprism.git
synced 2026-01-23 02:24:24 +00:00
Backend: Move string shortening functions to /pkg/txt/clip
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
fd571f70b0
commit
a2fd10fddd
26 changed files with 246 additions and 73 deletions
|
|
@ -75,6 +75,8 @@ func (c *Config) WallpaperUri() string {
|
|||
wallpaperUri = c.StaticAssetUri(path.Join(wallpaperPath, fileName))
|
||||
} else if fs.FileExists(c.CustomStaticFile(path.Join(wallpaperPath, fileName))) {
|
||||
wallpaperUri = c.CustomStaticAssetUri(path.Join(wallpaperPath, fileName))
|
||||
} else if fs.FileExists(path.Join(c.ThemePath(), fileName)) {
|
||||
wallpaperUri = c.BaseUri("/" + path.Join("_theme", fileName))
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -241,3 +241,8 @@ func (c *Config) BuildPath() string {
|
|||
func (c *Config) ImgPath() string {
|
||||
return filepath.Join(c.StaticPath(), "img")
|
||||
}
|
||||
|
||||
// ThemePath returns the path to static theme files.
|
||||
func (c *Config) ThemePath() string {
|
||||
return filepath.Join(c.ConfigPath(), "theme")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -265,6 +265,13 @@ func TestConfig_ImgPath(t *testing.T) {
|
|||
assert.Equal(t, "/go/src/github.com/photoprism/photoprism/assets/static/img", path)
|
||||
}
|
||||
|
||||
func TestConfig_ThemePath(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
path := c.ThemePath()
|
||||
assert.Equal(t, "/go/src/github.com/photoprism/photoprism/storage/testdata/config/theme", path)
|
||||
}
|
||||
|
||||
func TestConfig_IndexWorkers(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ func TestFile_SetProjection(t *testing.T) {
|
|||
p := projection.New(" 幸福 Hanzi are logograms developed for the writing of Chinese! Expressions in an index may not ...!")
|
||||
m.SetProjection(p.String())
|
||||
assert.Equal(t, p.String(), m.FileProjection)
|
||||
assert.GreaterOrEqual(t, clean.ClipType, len(m.FileProjection))
|
||||
assert.GreaterOrEqual(t, clean.LengthType, len(m.FileProjection))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,6 @@ package clean
|
|||
|
||||
import "strings"
|
||||
|
||||
const MaxLength = 4096
|
||||
|
||||
func reject(s string, maxLength int) bool {
|
||||
if maxLength > 0 && len(s) > maxLength {
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
package clean
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
ClipShortType = 8
|
||||
ClipIPv6 = 39
|
||||
ClipType = 64
|
||||
)
|
||||
|
||||
// Clip shortens a string to the given number of characters, and removes all leading and trailing white space.
|
||||
func Clip(s string, maxLen int) string {
|
||||
s = strings.TrimSpace(s)
|
||||
l := len(s)
|
||||
|
||||
if l <= maxLen {
|
||||
return s
|
||||
} else {
|
||||
return strings.TrimSpace(s[:maxLen])
|
||||
}
|
||||
}
|
||||
|
|
@ -10,8 +10,8 @@ func Error(err error) string {
|
|||
return "unknown error"
|
||||
} else {
|
||||
// Limit error message length.
|
||||
if len(s) > MaxLength {
|
||||
s = s[:MaxLength]
|
||||
if len(s) > LengthLimit {
|
||||
s = s[:LengthLimit]
|
||||
}
|
||||
|
||||
// Remove non-printable and other potentially problematic characters.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package clean
|
|||
|
||||
// Header sanitizes a string for use in request or response headers.
|
||||
func Header(s string) string {
|
||||
if s == "" || len(s) > MaxLength {
|
||||
if s == "" || len(s) > LengthLimit {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ var IpRegExp = regexp.MustCompile(`[^a-zA-Z0-9:.]`)
|
|||
// IP returns the sanitized and normalized network address if it is valid, or the default otherwise.
|
||||
func IP(s, defaultIp string) string {
|
||||
// Return default if invalid.
|
||||
if s == "" || len(s) > MaxLength || s == defaultIp {
|
||||
if s == "" || len(s) > LengthLimit || s == defaultIp {
|
||||
return defaultIp
|
||||
}
|
||||
|
||||
|
|
@ -21,8 +21,8 @@ func IP(s, defaultIp string) string {
|
|||
}
|
||||
|
||||
// Limit string length to 39 characters.
|
||||
if len(s) > ClipIPv6 {
|
||||
s = s[:ClipIPv6]
|
||||
if len(s) > LengthIPv6 {
|
||||
s = s[:LengthIPv6]
|
||||
}
|
||||
|
||||
// Parse IP address and return it as string.
|
||||
|
|
|
|||
9
pkg/clean/length.go
Normal file
9
pkg/clean/length.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package clean
|
||||
|
||||
const (
|
||||
LengthType = 64
|
||||
LengthShortType = 8
|
||||
LengthIPv6 = 39
|
||||
LengthLog = 512
|
||||
LengthLimit = 4096
|
||||
)
|
||||
|
|
@ -3,13 +3,19 @@ package clean
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/txt/clip"
|
||||
)
|
||||
|
||||
// Log sanitizes strings created from user input in response to the log4j debacle.
|
||||
func Log(s string) string {
|
||||
if s == "" {
|
||||
return "''"
|
||||
} else if reject(s, 512) {
|
||||
}
|
||||
|
||||
s = clip.Shorten(s, LengthLog, clip.Ellipsis)
|
||||
|
||||
if reject(s, LengthLimit) {
|
||||
return "?"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/txt/clip"
|
||||
)
|
||||
|
||||
func TestLog(t *testing.T) {
|
||||
|
|
@ -25,6 +27,14 @@ func TestLog(t *testing.T) {
|
|||
t.Run("SpecialChars", func(t *testing.T) {
|
||||
assert.Equal(t, "' The ?quick? ''brown 'fox. '", Log(" The <quick>\n\r ''brown \"fox. \t "))
|
||||
})
|
||||
t.Run("LoremIpsum", func(t *testing.T) {
|
||||
assert.Equal(t, "'It is a long established fact that a reader will be distracted by the readable "+
|
||||
"content of a pagewhen looking at its layout. The point of using Lorem Ipsum is that it has a "+
|
||||
"more-or-less normal distribution of letters,as opposed to using 'Content here, content here', making it "+
|
||||
"look like readable English.Many desktop publishing packages and web page editors now use Lorem Ipsum as "+
|
||||
"their default model text, and a search for'lorem ipsum' will uncover many web sites still in their "+
|
||||
"infancy. Various versions…'", Log(clip.LoremIpsum))
|
||||
})
|
||||
}
|
||||
|
||||
func TestLogQuote(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ func replace(subject string, search string, replace string) string {
|
|||
|
||||
// SearchString replaces search operator with default symbols.
|
||||
func SearchString(s string) string {
|
||||
if s == "" || reject(s, MaxLength) {
|
||||
if s == "" || reject(s, LengthLimit) {
|
||||
return Empty
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ func SearchString(s string) string {
|
|||
|
||||
// SearchQuery replaces search operator with default symbols.
|
||||
func SearchQuery(s string) string {
|
||||
if s == "" || reject(s, MaxLength) {
|
||||
if s == "" || reject(s, LengthLimit) {
|
||||
return Empty
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
|
||||
// Token returns the sanitized token string with a length of up to 4096 characters.
|
||||
func Token(s string) string {
|
||||
if s == "" || reject(s, MaxLength) {
|
||||
if s == "" || reject(s, LengthLimit) {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package clean
|
|||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/txt/clip"
|
||||
)
|
||||
|
||||
// Type omits invalid runes, ensures a maximum length of 32 characters, and returns the result.
|
||||
|
|
@ -10,7 +12,7 @@ func Type(s string) string {
|
|||
return s
|
||||
}
|
||||
|
||||
return Clip(ASCII(s), ClipType)
|
||||
return clip.Chars(ASCII(s), LengthType)
|
||||
}
|
||||
|
||||
// TypeLower converts a type string to lowercase, omits invalid runes, and shortens it if needed.
|
||||
|
|
@ -37,7 +39,7 @@ func ShortType(s string) string {
|
|||
return s
|
||||
}
|
||||
|
||||
return Clip(ASCII(s), ClipShortType)
|
||||
return clip.Chars(ASCII(s), LengthShortType)
|
||||
}
|
||||
|
||||
// ShortTypeLower converts a short type string to lowercase, omits invalid runes, and shortens it if needed.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/txt/clip"
|
||||
)
|
||||
|
||||
func TestToASCII(t *testing.T) {
|
||||
|
|
@ -14,27 +16,27 @@ func TestToASCII(t *testing.T) {
|
|||
|
||||
func TestClip(t *testing.T) {
|
||||
t.Run("Foo", func(t *testing.T) {
|
||||
result := Clip("Foo", 16)
|
||||
result := clip.Chars("Foo", 16)
|
||||
assert.Equal(t, "Foo", result)
|
||||
assert.Equal(t, 3, len(result))
|
||||
})
|
||||
t.Run("TrimFoo", func(t *testing.T) {
|
||||
result := Clip(" Foo ", 16)
|
||||
result := clip.Chars(" Foo ", 16)
|
||||
assert.Equal(t, "Foo", result)
|
||||
assert.Equal(t, 3, len(result))
|
||||
})
|
||||
t.Run("TooLong", func(t *testing.T) {
|
||||
result := Clip(" 幸福 Hanzi are logograms developed for the writing of Chinese! ", 16)
|
||||
result := clip.Chars(" 幸福 Hanzi are logograms developed for the writing of Chinese! ", 16)
|
||||
assert.Equal(t, "幸福 Hanzi are", result)
|
||||
assert.Equal(t, 16, len(result))
|
||||
})
|
||||
t.Run("ToASCII", func(t *testing.T) {
|
||||
result := Clip(ASCII(strings.ToLower(" 幸福 Hanzi are logograms developed for the writing of Chinese! Expressions in an index may not ...!")), ClipType)
|
||||
result := clip.Chars(ASCII(strings.ToLower(" 幸福 Hanzi are logograms developed for the writing of Chinese! Expressions in an index may not ...!")), LengthType)
|
||||
assert.Equal(t, "hanzi are logograms developed for the writing of chinese! expres", result)
|
||||
assert.Equal(t, 64, len(result))
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
result := Clip("", 999)
|
||||
result := clip.Chars("", 999)
|
||||
assert.Equal(t, "", result)
|
||||
assert.Equal(t, 0, len(result))
|
||||
})
|
||||
|
|
@ -44,7 +46,7 @@ func TestType(t *testing.T) {
|
|||
t.Run("Clip", func(t *testing.T) {
|
||||
result := Type(" 幸福 Hanzi are logograms developed for the writing of Chinese! Expressions in an index may not ...!")
|
||||
assert.Equal(t, "Hanzi are logograms developed for the writing of Chinese! Expres", result)
|
||||
assert.Equal(t, ClipType, len(result))
|
||||
assert.Equal(t, LengthType, len(result))
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
assert.Equal(t, "", Type(""))
|
||||
|
|
@ -55,7 +57,7 @@ func TestTypeLower(t *testing.T) {
|
|||
t.Run("Clip", func(t *testing.T) {
|
||||
result := TypeLower(" 幸福 Hanzi are logograms developed for the writing of Chinese! Expressions in an index may not ...!")
|
||||
assert.Equal(t, "hanzi are logograms developed for the writing of chinese! expres", result)
|
||||
assert.Equal(t, ClipType, len(result))
|
||||
assert.Equal(t, LengthType, len(result))
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
assert.Equal(t, "", TypeLower(""))
|
||||
|
|
@ -84,7 +86,7 @@ func TestShortType(t *testing.T) {
|
|||
t.Run("Clip", func(t *testing.T) {
|
||||
result := ShortType(" 幸福 Hanzi are logograms developed for the writing of Chinese! Expressions in an index may not ...!")
|
||||
assert.Equal(t, "Hanzi ar", result)
|
||||
assert.Equal(t, ClipShortType, len(result))
|
||||
assert.Equal(t, LengthShortType, len(result))
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
assert.Equal(t, "", ShortType(""))
|
||||
|
|
@ -95,7 +97,7 @@ func TestShortTypeLower(t *testing.T) {
|
|||
t.Run("Clip", func(t *testing.T) {
|
||||
result := ShortTypeLower(" 幸福 Hanzi are logograms developed for the writing of Chinese! Expressions in an index may not ...!")
|
||||
assert.Equal(t, "hanzi ar", result)
|
||||
assert.Equal(t, ClipShortType, len(result))
|
||||
assert.Equal(t, LengthShortType, len(result))
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
assert.Equal(t, "", ShortTypeLower(""))
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
// Uri removes invalid character from an uri string.
|
||||
func Uri(s string) string {
|
||||
if s == "" || len(s) > MaxLength {
|
||||
if s == "" || len(s) > LengthLimit {
|
||||
return ""
|
||||
} else if strings.Contains(s, "..") {
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package txt
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"github.com/photoprism/photoprism/pkg/txt/clip"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -31,34 +31,12 @@ const (
|
|||
ClipLongText = 4096
|
||||
)
|
||||
|
||||
// Clip shortens a string to the given number of runes, and removes all leading and trailing white space.
|
||||
// Clip limits a string to the given number of runes and removes all leading and trailing spaces.
|
||||
func Clip(s string, size int) string {
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
if s == "" || size <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
runes := []rune(s)
|
||||
|
||||
if len(runes) > size {
|
||||
s = string(runes[0:size])
|
||||
}
|
||||
|
||||
return strings.TrimSpace(s)
|
||||
return clip.Runes(s, size)
|
||||
}
|
||||
|
||||
// Shorten shortens a string with suffix.
|
||||
// Shorten limits a character string to the specified number of runes and adds a suffix if it has been shortened.
|
||||
func Shorten(s string, size int, suffix string) string {
|
||||
if suffix == "" {
|
||||
suffix = Ellipsis
|
||||
}
|
||||
|
||||
l := len(suffix)
|
||||
|
||||
if len(s) < size || size < l+1 {
|
||||
return s
|
||||
}
|
||||
|
||||
return Clip(s, size-l) + suffix
|
||||
return clip.Shorten(s, size, suffix)
|
||||
}
|
||||
|
|
|
|||
15
pkg/txt/clip/chars.go
Normal file
15
pkg/txt/clip/chars.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package clip
|
||||
|
||||
import "strings"
|
||||
|
||||
// Chars limits a string to the specified number of characters and removes all leading and trailing spaces.
|
||||
func Chars(s string, maxLen int) string {
|
||||
s = strings.TrimSpace(s)
|
||||
l := len(s)
|
||||
|
||||
if l <= maxLen {
|
||||
return s
|
||||
} else {
|
||||
return strings.TrimSpace(s[:maxLen])
|
||||
}
|
||||
}
|
||||
30
pkg/txt/clip/chars_test.go
Normal file
30
pkg/txt/clip/chars_test.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package clip
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestChars(t *testing.T) {
|
||||
t.Run("Foo", func(t *testing.T) {
|
||||
result := Chars("Foo", 16)
|
||||
assert.Equal(t, "Foo", result)
|
||||
assert.Equal(t, 3, len(result))
|
||||
})
|
||||
t.Run("TrimFoo", func(t *testing.T) {
|
||||
result := Chars(" Foo ", 16)
|
||||
assert.Equal(t, "Foo", result)
|
||||
assert.Equal(t, 3, len(result))
|
||||
})
|
||||
t.Run("TooLong", func(t *testing.T) {
|
||||
result := Chars(" 幸福 Hanzi are logograms developed for the writing of Chinese! ", 16)
|
||||
assert.Equal(t, "幸福 Hanzi are", result)
|
||||
assert.Equal(t, 16, len(result))
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
result := Chars("", 999)
|
||||
assert.Equal(t, "", result)
|
||||
assert.Equal(t, 0, len(result))
|
||||
})
|
||||
}
|
||||
25
pkg/txt/clip/clip.go
Normal file
25
pkg/txt/clip/clip.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Package clip provides functions for limiting the length of character strings.
|
||||
|
||||
Copyright (c) 2018 - 2024 PhotoPrism UG. All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under Version 3 of the GNU Affero General Public License (the "AGPL"):
|
||||
<https://docs.photoprism.app/license/agpl>
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
The AGPL is supplemented by our Trademark and Brand Guidelines,
|
||||
which describe how our Brand Assets may be used:
|
||||
<https://www.photoprism.app/trademark>
|
||||
|
||||
Feel free to send an email to hello@photoprism.app if you have questions,
|
||||
want to support our work, or just want to say hello.
|
||||
|
||||
Additional information can be found in our Developer Guide:
|
||||
<https://docs.photoprism.app/developer-guide/>
|
||||
*/
|
||||
package clip
|
||||
17
pkg/txt/clip/const.go
Normal file
17
pkg/txt/clip/const.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package clip
|
||||
|
||||
const Ellipsis = "…"
|
||||
|
||||
const LoremIpsum = `It is a long established fact that a reader will be distracted by the readable content of a page
|
||||
when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters,
|
||||
as opposed to using 'Content here, content here', making it look like readable English.
|
||||
Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for
|
||||
'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years,
|
||||
sometimes by accident, sometimes on purpose (injected humour and the like).
|
||||
There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form,
|
||||
by injected humour, or randomised words which don't look even slightly believable.
|
||||
If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the
|
||||
middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making
|
||||
this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of
|
||||
model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always
|
||||
free from repetition, injected humour, or non-characteristic words etc.`
|
||||
20
pkg/txt/clip/runes.go
Normal file
20
pkg/txt/clip/runes.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package clip
|
||||
|
||||
import "strings"
|
||||
|
||||
// Runes limits a string to the given number of runes and removes all leading and trailing spaces.
|
||||
func Runes(s string, size int) string {
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
if s == "" || size <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
runes := []rune(s)
|
||||
|
||||
if len(runes) > size {
|
||||
s = string(runes[0:size])
|
||||
}
|
||||
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
30
pkg/txt/clip/runes_test.go
Normal file
30
pkg/txt/clip/runes_test.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package clip
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRunes(t *testing.T) {
|
||||
t.Run("Foo", func(t *testing.T) {
|
||||
result := Runes("Foo", 16)
|
||||
assert.Equal(t, "Foo", result)
|
||||
assert.Equal(t, 3, len(result))
|
||||
})
|
||||
t.Run("TrimFoo", func(t *testing.T) {
|
||||
result := Runes(" Foo ", 16)
|
||||
assert.Equal(t, "Foo", result)
|
||||
assert.Equal(t, 3, len(result))
|
||||
})
|
||||
t.Run("TooLong", func(t *testing.T) {
|
||||
result := Runes(" 幸福 Hanzi are logograms developed for the writing of Chinese! ", 16)
|
||||
assert.Equal(t, "幸福 Hanzi are log", result)
|
||||
assert.Equal(t, 20, len(result))
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
result := Runes("", 999)
|
||||
assert.Equal(t, "", result)
|
||||
assert.Equal(t, 0, len(result))
|
||||
})
|
||||
}
|
||||
16
pkg/txt/clip/shorten.go
Normal file
16
pkg/txt/clip/shorten.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package clip
|
||||
|
||||
// Shorten limits a character string to the specified number of runes and adds a suffix if it has been shortened.
|
||||
func Shorten(s string, size int, suffix string) string {
|
||||
if suffix == "" {
|
||||
suffix = Ellipsis
|
||||
}
|
||||
|
||||
l := len(suffix)
|
||||
|
||||
if len(s) < size || size < l+1 {
|
||||
return s
|
||||
}
|
||||
|
||||
return Runes(s, size-l) + suffix
|
||||
}
|
||||
22
pkg/txt/clip/shorten_test.go
Normal file
22
pkg/txt/clip/shorten_test.go
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package clip
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestShorten(t *testing.T) {
|
||||
t.Run("ShortEnough", func(t *testing.T) {
|
||||
assert.Equal(t, "fox!", Shorten("fox!", 6, "..."))
|
||||
})
|
||||
t.Run("CustomSuffix", func(t *testing.T) {
|
||||
assert.Equal(t, "I'm ä...", Shorten("I'm ä lazy BRoWN fox!", 8, "..."))
|
||||
})
|
||||
t.Run("DefaultSuffix", func(t *testing.T) {
|
||||
assert.Equal(t, "I'm…", Shorten("I'm ä lazy BRoWN fox!", 7, ""))
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
assert.Equal(t, "", Shorten("", -1, ""))
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue