strings.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. package sprig
  2. import (
  3. "encoding/base32"
  4. "encoding/base64"
  5. "fmt"
  6. "golang.org/x/text/cases"
  7. "golang.org/x/text/language"
  8. "reflect"
  9. "strconv"
  10. "strings"
  11. )
  12. // base64encode encodes a string to base64 using standard encoding.
  13. //
  14. // Parameters:
  15. // - v: The string to encode
  16. //
  17. // Returns:
  18. // - string: The base64 encoded string
  19. func base64encode(v string) string {
  20. return base64.StdEncoding.EncodeToString([]byte(v))
  21. }
  22. // base64decode decodes a base64 encoded string.
  23. // If the input is not valid base64, it returns the error message as a string.
  24. //
  25. // Parameters:
  26. // - v: The base64 encoded string to decode
  27. //
  28. // Returns:
  29. // - string: The decoded string, or an error message if decoding fails
  30. func base64decode(v string) string {
  31. data, err := base64.StdEncoding.DecodeString(v)
  32. if err != nil {
  33. return err.Error()
  34. }
  35. return string(data)
  36. }
  37. // base32encode encodes a string to base32 using standard encoding.
  38. //
  39. // Parameters:
  40. // - v: The string to encode
  41. //
  42. // Returns:
  43. // - string: The base32 encoded string
  44. func base32encode(v string) string {
  45. return base32.StdEncoding.EncodeToString([]byte(v))
  46. }
  47. // base32decode decodes a base32 encoded string.
  48. // If the input is not valid base32, it returns the error message as a string.
  49. //
  50. // Parameters:
  51. // - v: The base32 encoded string to decode
  52. //
  53. // Returns:
  54. // - string: The decoded string, or an error message if decoding fails
  55. func base32decode(v string) string {
  56. data, err := base32.StdEncoding.DecodeString(v)
  57. if err != nil {
  58. return err.Error()
  59. }
  60. return string(data)
  61. }
  62. // quote adds double quotes around each non-nil string in the input and joins them with spaces.
  63. // This uses Go's %q formatter which handles escaping special characters.
  64. //
  65. // Parameters:
  66. // - str: A variadic list of values to quote
  67. //
  68. // Returns:
  69. // - string: The quoted strings joined with spaces
  70. func quote(str ...any) string {
  71. out := make([]string, 0, len(str))
  72. for _, s := range str {
  73. if s != nil {
  74. out = append(out, fmt.Sprintf("%q", strval(s)))
  75. }
  76. }
  77. return strings.Join(out, " ")
  78. }
  79. // squote adds single quotes around each non-nil value in the input and joins them with spaces.
  80. // Unlike quote, this doesn't escape special characters.
  81. //
  82. // Parameters:
  83. // - str: A variadic list of values to quote
  84. //
  85. // Returns:
  86. // - string: The single-quoted values joined with spaces
  87. func squote(str ...any) string {
  88. out := make([]string, 0, len(str))
  89. for _, s := range str {
  90. if s != nil {
  91. out = append(out, fmt.Sprintf("'%v'", s))
  92. }
  93. }
  94. return strings.Join(out, " ")
  95. }
  96. // cat concatenates all non-nil values into a single string.
  97. // Nil values are removed before concatenation.
  98. //
  99. // Parameters:
  100. // - v: A variadic list of values to concatenate
  101. //
  102. // Returns:
  103. // - string: The concatenated string
  104. func cat(v ...any) string {
  105. v = removeNilElements(v)
  106. r := strings.TrimSpace(strings.Repeat("%v ", len(v)))
  107. return fmt.Sprintf(r, v...)
  108. }
  109. // indent adds a specified number of spaces at the beginning of each line in a string.
  110. //
  111. // Parameters:
  112. // - spaces: The number of spaces to add
  113. // - v: The string to indent
  114. //
  115. // Returns:
  116. // - string: The indented string
  117. func indent(spaces int, v string) string {
  118. pad := strings.Repeat(" ", spaces)
  119. return pad + strings.Replace(v, "\n", "\n"+pad, -1)
  120. }
  121. // nindent adds a newline followed by an indented string.
  122. // It's a shorthand for "\n" + indent(spaces, v).
  123. //
  124. // Parameters:
  125. // - spaces: The number of spaces to add
  126. // - v: The string to indent
  127. //
  128. // Returns:
  129. // - string: A newline followed by the indented string
  130. func nindent(spaces int, v string) string {
  131. return "\n" + indent(spaces, v)
  132. }
  133. // replace replaces all occurrences of a substring with another substring.
  134. //
  135. // Parameters:
  136. // - old: The substring to replace
  137. // - new: The replacement substring
  138. // - src: The source string
  139. //
  140. // Returns:
  141. // - string: The resulting string after all replacements
  142. func replace(old, new, src string) string {
  143. return strings.Replace(src, old, new, -1)
  144. }
  145. // plural returns the singular or plural form of a word based on the count.
  146. // If count is 1, it returns the singular form, otherwise it returns the plural form.
  147. //
  148. // Parameters:
  149. // - one: The singular form of the word
  150. // - many: The plural form of the word
  151. // - count: The count to determine which form to use
  152. //
  153. // Returns:
  154. // - string: Either the singular or plural form based on the count
  155. func plural(one, many string, count int) string {
  156. if count == 1 {
  157. return one
  158. }
  159. return many
  160. }
  161. // strslice converts a value to a slice of strings.
  162. // It handles various input types:
  163. // - []string: returned as is
  164. // - []any: converted to []string, skipping nil values
  165. // - arrays and slices: converted to []string, skipping nil values
  166. // - nil: returns an empty slice
  167. // - anything else: returns a single-element slice with the string representation
  168. //
  169. // Parameters:
  170. // - v: The value to convert to a string slice
  171. //
  172. // Returns:
  173. // - []string: A slice of strings
  174. func strslice(v any) []string {
  175. switch v := v.(type) {
  176. case []string:
  177. return v
  178. case []any:
  179. b := make([]string, 0, len(v))
  180. for _, s := range v {
  181. if s != nil {
  182. b = append(b, strval(s))
  183. }
  184. }
  185. return b
  186. default:
  187. val := reflect.ValueOf(v)
  188. switch val.Kind() {
  189. case reflect.Array, reflect.Slice:
  190. l := val.Len()
  191. b := make([]string, 0, l)
  192. for i := 0; i < l; i++ {
  193. value := val.Index(i).Interface()
  194. if value != nil {
  195. b = append(b, strval(value))
  196. }
  197. }
  198. return b
  199. default:
  200. if v == nil {
  201. return []string{}
  202. }
  203. return []string{strval(v)}
  204. }
  205. }
  206. }
  207. // removeNilElements creates a new slice with all nil elements removed.
  208. // This is a helper function used by other functions like cat.
  209. //
  210. // Parameters:
  211. // - v: The slice to process
  212. //
  213. // Returns:
  214. // - []any: A new slice with all nil elements removed
  215. func removeNilElements(v []any) []any {
  216. newSlice := make([]any, 0, len(v))
  217. for _, i := range v {
  218. if i != nil {
  219. newSlice = append(newSlice, i)
  220. }
  221. }
  222. return newSlice
  223. }
  224. // strval converts any value to a string.
  225. // It handles various types:
  226. // - string: returned as is
  227. // - []byte: converted to string
  228. // - error: returns the error message
  229. // - fmt.Stringer: calls the String() method
  230. // - anything else: uses fmt.Sprintf("%v", v)
  231. //
  232. // Parameters:
  233. // - v: The value to convert to a string
  234. //
  235. // Returns:
  236. // - string: The string representation of the value
  237. func strval(v any) string {
  238. switch v := v.(type) {
  239. case string:
  240. return v
  241. case []byte:
  242. return string(v)
  243. case error:
  244. return v.Error()
  245. case fmt.Stringer:
  246. return v.String()
  247. default:
  248. return fmt.Sprintf("%v", v)
  249. }
  250. }
  251. // trunc truncates a string to a specified length.
  252. // If c is positive, it returns the first c characters.
  253. // If c is negative, it returns the last |c| characters.
  254. // If the string is shorter than the requested length, it returns the original string.
  255. //
  256. // Parameters:
  257. // - c: The number of characters to keep (positive from start, negative from end)
  258. // - s: The string to truncate
  259. //
  260. // Returns:
  261. // - string: The truncated string
  262. func trunc(c int, s string) string {
  263. if c < 0 && len(s)+c > 0 {
  264. return s[len(s)+c:]
  265. }
  266. if c >= 0 && len(s) > c {
  267. return s[:c]
  268. }
  269. return s
  270. }
  271. // title converts a string to title case.
  272. // This uses the English language rules for capitalization.
  273. //
  274. // Parameters:
  275. // - s: The string to convert
  276. //
  277. // Returns:
  278. // - string: The string in title case
  279. func title(s string) string {
  280. return cases.Title(language.English).String(s)
  281. }
  282. // join concatenates the elements of a slice with a separator.
  283. // The input is first converted to a string slice using strslice.
  284. //
  285. // Parameters:
  286. // - sep: The separator to use between elements
  287. // - v: The value to join (will be converted to a string slice)
  288. //
  289. // Returns:
  290. // - string: The joined string
  291. func join(sep string, v any) string {
  292. return strings.Join(strslice(v), sep)
  293. }
  294. // split splits a string by a separator and returns a map.
  295. // The keys in the map are "_0", "_1", etc., corresponding to the position of each part.
  296. //
  297. // Parameters:
  298. // - sep: The separator to split on
  299. // - orig: The string to split
  300. //
  301. // Returns:
  302. // - map[string]string: A map with keys "_0", "_1", etc. and values being the split parts
  303. func split(sep, orig string) map[string]string {
  304. parts := strings.Split(orig, sep)
  305. res := make(map[string]string, len(parts))
  306. for i, v := range parts {
  307. res["_"+strconv.Itoa(i)] = v
  308. }
  309. return res
  310. }
  311. // splitList splits a string by a separator and returns a slice.
  312. // This is a simple wrapper around strings.Split.
  313. //
  314. // Parameters:
  315. // - sep: The separator to split on
  316. // - orig: The string to split
  317. //
  318. // Returns:
  319. // - []string: A slice containing the split parts
  320. func splitList(sep, orig string) []string {
  321. return strings.Split(orig, sep)
  322. }
  323. // splitn splits a string by a separator with a limit and returns a map.
  324. // The keys in the map are "_0", "_1", etc., corresponding to the position of each part.
  325. // It will split the string into at most n parts.
  326. //
  327. // Parameters:
  328. // - sep: The separator to split on
  329. // - n: The maximum number of parts to return
  330. // - orig: The string to split
  331. //
  332. // Returns:
  333. // - map[string]string: A map with keys "_0", "_1", etc. and values being the split parts
  334. func splitn(sep string, n int, orig string) map[string]string {
  335. parts := strings.SplitN(orig, sep, n)
  336. res := make(map[string]string, len(parts))
  337. for i, v := range parts {
  338. res["_"+strconv.Itoa(i)] = v
  339. }
  340. return res
  341. }
  342. // substring creates a substring of the given string.
  343. // It extracts a portion of a string based on start and end indices.
  344. //
  345. // Parameters:
  346. // - start: The starting index (inclusive)
  347. // - end: The ending index (exclusive)
  348. // - s: The source string
  349. //
  350. // Behavior:
  351. // - If start < 0, returns s[:end]
  352. // - If start >= 0 and end < 0 or end > len(s), returns s[start:]
  353. // - Otherwise, returns s[start:end]
  354. //
  355. // Returns:
  356. // - string: The extracted substring
  357. func substring(start, end int, s string) string {
  358. if start < 0 {
  359. return s[:end]
  360. }
  361. if end < 0 || end > len(s) {
  362. return s[start:]
  363. }
  364. return s[start:end]
  365. }
  366. // repeat creates a new string by repeating the input string a specified number of times.
  367. // It has safety limits to prevent excessive memory usage or infinite loops.
  368. //
  369. // Parameters:
  370. // - count: The number of times to repeat the string
  371. // - str: The string to repeat
  372. //
  373. // Returns:
  374. // - string: The repeated string
  375. //
  376. // Panics:
  377. // - If count exceeds loopExecutionLimit
  378. // - If the resulting string length would exceed stringLengthLimit
  379. func repeat(count int, str string) string {
  380. if count > loopExecutionLimit {
  381. panic(fmt.Sprintf("repeat count %d exceeds limit of %d", count, loopExecutionLimit))
  382. } else if count*len(str) >= stringLengthLimit {
  383. panic(fmt.Sprintf("repeat count %d with string length %d exceeds limit of %d", count, len(str), stringLengthLimit))
  384. }
  385. return strings.Repeat(str, count)
  386. }
  387. // trimAll removes all leading and trailing characters contained in the cutset.
  388. // Note that the parameter order is reversed from the standard strings.Trim function.
  389. //
  390. // Parameters:
  391. // - a: The cutset of characters to remove
  392. // - b: The string to trim
  393. //
  394. // Returns:
  395. // - string: The trimmed string
  396. func trimAll(a, b string) string {
  397. return strings.Trim(b, a)
  398. }
  399. // trimPrefix removes the specified prefix from a string.
  400. // If the string doesn't start with the prefix, it returns the original string.
  401. // Note that the parameter order is reversed from the standard strings.TrimPrefix function.
  402. //
  403. // Parameters:
  404. // - a: The prefix to remove
  405. // - b: The string to trim
  406. //
  407. // Returns:
  408. // - string: The string with the prefix removed, or the original string if it doesn't start with the prefix
  409. func trimPrefix(a, b string) string {
  410. return strings.TrimPrefix(b, a)
  411. }
  412. // trimSuffix removes the specified suffix from a string.
  413. // If the string doesn't end with the suffix, it returns the original string.
  414. // Note that the parameter order is reversed from the standard strings.TrimSuffix function.
  415. //
  416. // Parameters:
  417. // - a: The suffix to remove
  418. // - b: The string to trim
  419. //
  420. // Returns:
  421. // - string: The string with the suffix removed, or the original string if it doesn't end with the suffix
  422. func trimSuffix(a, b string) string {
  423. return strings.TrimSuffix(b, a)
  424. }
  425. // contains checks if a string contains a substring.
  426. //
  427. // Parameters:
  428. // - substr: The substring to search for
  429. // - str: The string to search in
  430. //
  431. // Returns:
  432. // - bool: True if str contains substr, false otherwise
  433. func contains(substr string, str string) bool {
  434. return strings.Contains(str, substr)
  435. }
  436. // hasPrefix checks if a string starts with a specified prefix.
  437. //
  438. // Parameters:
  439. // - substr: The prefix to check for
  440. // - str: The string to check
  441. //
  442. // Returns:
  443. // - bool: True if str starts with substr, false otherwise
  444. func hasPrefix(substr string, str string) bool {
  445. return strings.HasPrefix(str, substr)
  446. }
  447. // hasSuffix checks if a string ends with a specified suffix.
  448. //
  449. // Parameters:
  450. // - substr: The suffix to check for
  451. // - str: The string to check
  452. //
  453. // Returns:
  454. // - bool: True if str ends with substr, false otherwise
  455. func hasSuffix(substr string, str string) bool {
  456. return strings.HasSuffix(str, substr)
  457. }