summaryrefslogtreecommitdiff
path: root/strings/strings.go
blob: cf80f39c74d9168b5b00320652d9b84e0a206ea2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// Package strings contains functions to manipulate strings
package strings

import (
	"fmt"
	"math"
	"regexp"
	"sort"
	"strings"

	"github.com/Masterminds/goutils"
	"github.com/hairyhenderson/gomplate/v4/conv"
	"golang.org/x/text/cases"
	"golang.org/x/text/language"
)

// Indent - indent each line of the string with the given indent string.
// Any indent characters are permitted, except for '\n'.
func Indent(width int, indent, s string) (string, error) {
	if width <= 0 {
		return "", fmt.Errorf("width must be > 0")
	}

	if strings.Contains(indent, "\n") {
		return "", fmt.Errorf("indent must not contain '\\n'")
	}

	if width > 1 {
		indent = strings.Repeat(indent, width)
	}

	lines := strings.Count(s, "\n")

	res := make([]byte, 0, len(s)+len(indent)*lines)
	bol := true
	for i := range len(s) {
		c := s[i]
		if bol && c != '\n' {
			res = append(res, indent...)
		}
		res = append(res, c)
		bol = c == '\n'
	}

	return string(res), nil
}

// ShellQuote - generate a POSIX shell literal evaluating to a string
func ShellQuote(s string) string {
	return "'" + strings.ReplaceAll(s, "'", "'\"'\"'") + "'"
}

// Trunc - truncate a string to the given length
func Trunc(length int, s string) string {
	if length < 0 {
		return s
	}
	if len(s) <= length {
		return s
	}
	return s[0:length]
}

// Sort - return an alphanumerically-sorted list of strings
//
// Deprecated: use coll.Sort instead
func Sort(list []string) []string {
	sorted := sort.StringSlice(list)
	sorted.Sort()
	return sorted
}

var (
	spaces      = regexp.MustCompile(`\s+`)
	nonAlphaNum = regexp.MustCompile(`[^\pL\pN]+`)
)

// SnakeCase -
func SnakeCase(in string) string {
	s := casePrepare(in)
	return spaces.ReplaceAllString(s, "_")
}

// KebabCase -
func KebabCase(in string) string {
	s := casePrepare(in)
	return spaces.ReplaceAllString(s, "-")
}

func casePrepare(in string) string {
	in = strings.TrimSpace(in)
	s := strings.ToLower(in)
	// make sure the first letter remains lower- or upper-cased
	s = strings.Replace(s, string(s[0]), string(in[0]), 1)
	s = nonAlphaNum.ReplaceAllString(s, " ")
	return strings.TrimSpace(s)
}

// CamelCase -
func CamelCase(in string) string {
	in = strings.TrimSpace(in)
	tag := language.Und
	s := cases.Title(tag).String(conv.ToString(in))
	// make sure the first letter remains lower- or upper-cased
	s = strings.Replace(s, string(s[0]), string(in[0]), 1)
	return nonAlphaNum.ReplaceAllString(s, "")
}

// WordWrapOpts defines the options to apply to the WordWrap function
type WordWrapOpts struct {
	// Line-break sequence to insert (defaults to "\n")
	LBSeq string

	// The desired maximum line length in characters (defaults to 80)
	Width uint32
}

// applies default options
func wwDefaults(opts WordWrapOpts) WordWrapOpts {
	if opts.Width == 0 {
		opts.Width = 80
	}
	if opts.LBSeq == "" {
		opts.LBSeq = "\n"
	}
	return opts
}

// WordWrap - insert line-breaks into the string, before it reaches the given
// width.
func WordWrap(in string, opts WordWrapOpts) string {
	opts = wwDefaults(opts)

	// if we're on a 32-bit system, we need to check for overflow. If the width
	// is greater than maxint, we'll just use maxint.
	width := int(opts.Width)
	if width == -1 {
		width = int(math.MaxInt)
	}

	return goutils.WrapCustom(in, width, opts.LBSeq, false)
}

// SkipLines - skip the given number of lines (ending with \n) from the string.
// If skip is greater than the number of lines in the string, an empty string is
// returned.
func SkipLines(skip int, in string) (string, error) {
	if skip < 0 {
		return "", fmt.Errorf("skip must be >= 0")
	}
	if skip == 0 {
		return in, nil
	}

	lines := strings.SplitN(in, "\n", skip+1)
	if skip >= len(lines) {
		return "", nil
	}

	return lines[skip], nil
}