str.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. // Package str exposes functionality from Go's strings package as an Elvish
  2. // module.
  3. package str
  4. import (
  5. "bytes"
  6. "fmt"
  7. "strconv"
  8. "strings"
  9. "unicode"
  10. "unicode/utf8"
  11. "src.elv.sh/pkg/eval"
  12. "src.elv.sh/pkg/eval/errs"
  13. "src.elv.sh/pkg/eval/vals"
  14. )
  15. var Ns = eval.BuildNsNamed("str").
  16. AddGoFns(map[string]any{
  17. "compare": strings.Compare,
  18. "contains": strings.Contains,
  19. "contains-any": strings.ContainsAny,
  20. "count": strings.Count,
  21. "equal-fold": strings.EqualFold,
  22. // TODO: Fields, FieldsFunc
  23. "from-codepoints": fromCodepoints,
  24. "from-utf8-bytes": fromUtf8Bytes,
  25. "has-prefix": strings.HasPrefix,
  26. "has-suffix": strings.HasSuffix,
  27. "index": strings.Index,
  28. "index-any": strings.IndexAny,
  29. // TODO: IndexFunc
  30. "join": join,
  31. "last-index": strings.LastIndex,
  32. // TODO: LastIndexFunc, Map, Repeat
  33. "replace": replace,
  34. "split": split,
  35. // TODO: SplitAfter
  36. //lint:ignore SA1019 Elvish builtins need to be formally deprecated
  37. // before removal
  38. "title": strings.Title,
  39. "to-codepoints": toCodepoints,
  40. "to-lower": strings.ToLower,
  41. "to-title": strings.ToTitle,
  42. "to-upper": strings.ToUpper,
  43. "to-utf8-bytes": toUtf8Bytes,
  44. // TODO: ToLowerSpecial, ToTitleSpecial, ToUpperSpecial
  45. "trim": strings.Trim,
  46. "trim-left": strings.TrimLeft,
  47. "trim-right": strings.TrimRight,
  48. // TODO: TrimLeft,Right}Func
  49. "trim-space": strings.TrimSpace,
  50. "trim-prefix": strings.TrimPrefix,
  51. "trim-suffix": strings.TrimSuffix,
  52. }).Ns()
  53. func fromCodepoints(nums ...int) (string, error) {
  54. var b bytes.Buffer
  55. for _, num := range nums {
  56. if num < 0 || num > unicode.MaxRune {
  57. return "", errs.OutOfRange{
  58. What: "codepoint",
  59. ValidLow: "0", ValidHigh: strconv.Itoa(unicode.MaxRune),
  60. Actual: hex(num),
  61. }
  62. }
  63. if !utf8.ValidRune(rune(num)) {
  64. return "", errs.BadValue{
  65. What: "argument to str:from-codepoints",
  66. Valid: "valid Unicode codepoint",
  67. Actual: hex(num),
  68. }
  69. }
  70. b.WriteRune(rune(num))
  71. }
  72. return b.String(), nil
  73. }
  74. func hex(i int) string {
  75. if i < 0 {
  76. return "-0x" + strconv.FormatInt(-int64(i), 16)
  77. }
  78. return "0x" + strconv.FormatInt(int64(i), 16)
  79. }
  80. func fromUtf8Bytes(nums ...int) (string, error) {
  81. var b bytes.Buffer
  82. for _, num := range nums {
  83. if num < 0 || num > 255 {
  84. return "", errs.OutOfRange{
  85. What: "byte",
  86. ValidLow: "0", ValidHigh: "255",
  87. Actual: strconv.Itoa(num)}
  88. }
  89. b.WriteByte(byte(num))
  90. }
  91. if !utf8.Valid(b.Bytes()) {
  92. return "", errs.BadValue{
  93. What: "arguments to str:from-utf8-bytes",
  94. Valid: "valid UTF-8 sequence",
  95. Actual: fmt.Sprint(b.Bytes())}
  96. }
  97. return b.String(), nil
  98. }
  99. func join(sep string, inputs eval.Inputs) (string, error) {
  100. var buf bytes.Buffer
  101. var errJoin error
  102. first := true
  103. inputs(func(v any) {
  104. if errJoin != nil {
  105. return
  106. }
  107. if s, ok := v.(string); ok {
  108. if first {
  109. first = false
  110. } else {
  111. buf.WriteString(sep)
  112. }
  113. buf.WriteString(s)
  114. } else {
  115. errJoin = errs.BadValue{
  116. What: "input to str:join", Valid: "string", Actual: vals.Kind(v)}
  117. }
  118. })
  119. return buf.String(), errJoin
  120. }
  121. type maxOpt struct{ Max int }
  122. func (o *maxOpt) SetDefaultOptions() { o.Max = -1 }
  123. func replace(opts maxOpt, old, repl, s string) string {
  124. return strings.Replace(s, old, repl, opts.Max)
  125. }
  126. func split(fm *eval.Frame, opts maxOpt, sep, s string) error {
  127. out := fm.ValueOutput()
  128. parts := strings.SplitN(s, sep, opts.Max)
  129. for _, p := range parts {
  130. err := out.Put(p)
  131. if err != nil {
  132. return err
  133. }
  134. }
  135. return nil
  136. }
  137. func toCodepoints(fm *eval.Frame, s string) error {
  138. out := fm.ValueOutput()
  139. for _, r := range s {
  140. err := out.Put("0x" + strconv.FormatInt(int64(r), 16))
  141. if err != nil {
  142. return err
  143. }
  144. }
  145. return nil
  146. }
  147. func toUtf8Bytes(fm *eval.Frame, s string) error {
  148. out := fm.ValueOutput()
  149. for _, r := range []byte(s) {
  150. err := out.Put("0x" + strconv.FormatInt(int64(r), 16))
  151. if err != nil {
  152. return err
  153. }
  154. }
  155. return nil
  156. }