/pkg/cmd/util.go

https://gitlab.com/vitalii.dr/chezmoi · Go · 190 lines · 156 code · 17 blank · 17 comment · 35 complexity · 11641610cade44e0c88360c7acf7f048 MD5 · raw file

  1. package cmd
  2. import (
  3. "fmt"
  4. "regexp"
  5. "strconv"
  6. "strings"
  7. "unicode"
  8. "github.com/coreos/go-semver/semver"
  9. )
  10. var (
  11. goVersionRx = regexp.MustCompile(`\Ago(\d+)(?:\.(\d+)(?:\.(\d+))?)?\z`)
  12. wellKnownAbbreviations = map[string]struct{}{
  13. "ANSI": {},
  14. "CPE": {},
  15. "ID": {},
  16. "URL": {},
  17. }
  18. choicesYesNoAllQuit = []string{
  19. "yes",
  20. "no",
  21. "all",
  22. "quit",
  23. }
  24. )
  25. func ParseGoVersion(goVersion string) (*semver.Version, error) {
  26. m := goVersionRx.FindStringSubmatch(goVersion)
  27. if m == nil {
  28. return nil, fmt.Errorf("%s: invalid Go version", goVersion)
  29. }
  30. major, _ := strconv.ParseInt(m[1], 10, 64)
  31. minor, _ := strconv.ParseInt(m[2], 10, 64)
  32. patch, _ := strconv.ParseInt(m[3], 10, 64)
  33. return &semver.Version{
  34. Major: major,
  35. Minor: minor,
  36. Patch: patch,
  37. }, nil
  38. }
  39. // englishList returns ss formatted as a list, including an Oxford comma.
  40. func englishList(ss []string) string {
  41. switch n := len(ss); n {
  42. case 0:
  43. return ""
  44. case 1:
  45. return ss[0]
  46. case 2:
  47. return ss[0] + " and " + ss[1]
  48. default:
  49. return strings.Join(ss[:n-1], ", ") + ", and " + ss[n-1]
  50. }
  51. }
  52. // englishListWithNoun returns ss formatted as an English list, including an Oxford
  53. // comma.
  54. func englishListWithNoun(ss []string, singular, plural string) string {
  55. if len(ss) == 1 {
  56. return ss[0] + " " + singular
  57. }
  58. if plural == "" {
  59. plural = pluralize(singular)
  60. }
  61. switch n := len(ss); n {
  62. case 0:
  63. return "no " + plural
  64. default:
  65. return englishList(ss) + " " + plural
  66. }
  67. }
  68. // firstNonEmptyString returns its first non-empty argument, or "" if all
  69. // arguments are empty.
  70. func firstNonEmptyString(ss ...string) string {
  71. for _, s := range ss {
  72. if s != "" {
  73. return s
  74. }
  75. }
  76. return ""
  77. }
  78. // isWellKnownAbbreviation returns true if word is a well known abbreviation.
  79. func isWellKnownAbbreviation(word string) bool {
  80. _, ok := wellKnownAbbreviations[word]
  81. return ok
  82. }
  83. // parseBool is like strconv.ParseBool but also accepts on, ON, y, Y, yes, YES,
  84. // n, N, no, NO, off, and OFF.
  85. func parseBool(str string) (bool, error) {
  86. switch strings.ToLower(strings.TrimSpace(str)) {
  87. case "n", "no", "off":
  88. return false, nil
  89. case "on", "y", "yes":
  90. return true, nil
  91. default:
  92. return strconv.ParseBool(str)
  93. }
  94. }
  95. // pluralize returns the English plural form of singular.
  96. func pluralize(singular string) string {
  97. if strings.HasSuffix(singular, "y") {
  98. return strings.TrimSuffix(singular, "y") + "ies"
  99. }
  100. return singular + "s"
  101. }
  102. // titleize returns s with its first rune titleized.
  103. func titleize(s string) string {
  104. if s == "" {
  105. return s
  106. }
  107. runes := []rune(s)
  108. return string(append([]rune{unicode.ToTitle(runes[0])}, runes[1:]...))
  109. }
  110. // upperSnakeCaseToCamelCase converts a string in UPPER_SNAKE_CASE to
  111. // camelCase.
  112. func upperSnakeCaseToCamelCase(s string) string {
  113. words := strings.Split(s, "_")
  114. for i, word := range words {
  115. if i == 0 {
  116. words[i] = strings.ToLower(word)
  117. } else if !isWellKnownAbbreviation(word) {
  118. words[i] = titleize(strings.ToLower(word))
  119. }
  120. }
  121. return strings.Join(words, "")
  122. }
  123. // uniqueAbbreviations returns a map of unique abbreviations of values to
  124. // values. Values always map to themselves.
  125. func uniqueAbbreviations(values []string) map[string]string {
  126. abbreviations := make(map[string][]string)
  127. for _, value := range values {
  128. for i := 1; i <= len(value); i++ {
  129. abbreviation := value[:i]
  130. abbreviations[abbreviation] = append(abbreviations[abbreviation], value)
  131. }
  132. }
  133. uniqueAbbreviations := make(map[string]string)
  134. for abbreviation, values := range abbreviations {
  135. if len(values) == 1 {
  136. uniqueAbbreviations[abbreviation] = values[0]
  137. }
  138. }
  139. for _, value := range values {
  140. uniqueAbbreviations[value] = value
  141. }
  142. return uniqueAbbreviations
  143. }
  144. // upperSnakeCaseToCamelCaseKeys returns m with all keys converted from
  145. // UPPER_SNAKE_CASE to camelCase.
  146. func upperSnakeCaseToCamelCaseMap(m map[string]interface{}) map[string]interface{} {
  147. result := make(map[string]interface{})
  148. for k, v := range m {
  149. result[upperSnakeCaseToCamelCase(k)] = v
  150. }
  151. return result
  152. }
  153. // validateKeys ensures that all keys in data match re.
  154. func validateKeys(data interface{}, re *regexp.Regexp) error {
  155. switch data := data.(type) {
  156. case map[string]interface{}:
  157. for key, value := range data {
  158. if !re.MatchString(key) {
  159. return fmt.Errorf("%s: invalid key", key)
  160. }
  161. if err := validateKeys(value, re); err != nil {
  162. return err
  163. }
  164. }
  165. case []interface{}:
  166. for _, value := range data {
  167. if err := validateKeys(value, re); err != nil {
  168. return err
  169. }
  170. }
  171. }
  172. return nil
  173. }