/env/types.go

https://github.com/xo/usql · Go · 289 lines · 230 code · 42 blank · 17 comment · 34 complexity · 76e9b2d012c7dc3fbc0c7b65a9761a37 MD5 · raw file

  1. package env
  2. import (
  3. "fmt"
  4. "regexp"
  5. "strconv"
  6. "time"
  7. "unicode"
  8. "github.com/xo/terminfo"
  9. "github.com/xo/usql/text"
  10. )
  11. // Vars is a map of variables.
  12. type Vars map[string]string
  13. // Set sets a variable name.
  14. func (v Vars) Set(name, value string) {
  15. v[name] = value
  16. }
  17. // Unset unsets a variable name.
  18. func (v Vars) Unset(name string) {
  19. delete(v, name)
  20. }
  21. // All returns all variables as a map.
  22. func (v Vars) All() map[string]string {
  23. return map[string]string(v)
  24. }
  25. var vars, pvars Vars
  26. func init() {
  27. // get USQL_* variables
  28. enableHostInformation := "true"
  29. if v := Getenv("USQL_SHOW_HOST_INFORMATION"); v != "" {
  30. enableHostInformation = v
  31. }
  32. timefmt := "RFC3339Nano"
  33. if v := Getenv("USQL_TIME_FORMAT"); v != "" {
  34. timefmt = v
  35. }
  36. // get color level
  37. colorLevel, _ := terminfo.ColorLevelFromEnv()
  38. enableSyntaxHL := "true"
  39. if colorLevel < terminfo.ColorLevelBasic {
  40. enableSyntaxHL = "false"
  41. }
  42. vars = Vars{
  43. // usql related logic
  44. "SHOW_HOST_INFORMATION": enableHostInformation,
  45. "TIME_FORMAT": timefmt,
  46. // syntax highlighting variables
  47. "SYNTAX_HL": enableSyntaxHL,
  48. "SYNTAX_HL_FORMAT": colorLevel.ChromaFormatterName(),
  49. "SYNTAX_HL_STYLE": "monokai",
  50. "SYNTAX_HL_OVERRIDE_BG": "true",
  51. }
  52. pvars = Vars{
  53. "border": "1",
  54. "columns": "0",
  55. "expanded": "off",
  56. "fieldsep": "|",
  57. "fieldsep_zero": "off",
  58. "footer": "on",
  59. "format": "aligned",
  60. "linestyle": "ascii",
  61. "null": "",
  62. "numericlocale": "off",
  63. "pager": "1",
  64. "pager_min_lines": "0",
  65. "recordsep": "\n",
  66. "recordsep_zero": "off",
  67. "tableattr": "",
  68. "title": "",
  69. "tuples_only": "off",
  70. "unicode_border_linestyle": "single",
  71. "unicode_column_linestyle": "single",
  72. "unicode_header_linestyle": "single",
  73. }
  74. }
  75. // ValidIdentifier returns an error when n is not a valid identifier.
  76. func ValidIdentifier(n string) error {
  77. r := []rune(n)
  78. rlen := len(r)
  79. if rlen < 1 {
  80. return text.ErrInvalidIdentifier
  81. }
  82. for i := 0; i < rlen; i++ {
  83. if c := r[i]; c != '_' && !unicode.IsLetter(c) && !unicode.IsNumber(c) {
  84. return text.ErrInvalidIdentifier
  85. }
  86. }
  87. return nil
  88. }
  89. // Set sets a variable.
  90. func Set(name, value string) error {
  91. if err := ValidIdentifier(name); err != nil {
  92. return err
  93. }
  94. vars.Set(name, value)
  95. return nil
  96. }
  97. // Unset unsets a variable.
  98. func Unset(name string) error {
  99. if err := ValidIdentifier(name); err != nil {
  100. return err
  101. }
  102. vars.Unset(name)
  103. return nil
  104. }
  105. // All returns all variables.
  106. func All() map[string]string {
  107. return vars
  108. }
  109. // Pall returns all p variables.
  110. func Pall() map[string]string {
  111. return pvars
  112. }
  113. var onRE = regexp.MustCompile(`(?i)^(t|tr|tru|true|on)$`)
  114. var offRE = regexp.MustCompile(`(?i)^(f|fa|fal|fals|false|of|off)$`)
  115. var formatRE = regexp.MustCompile(`^(unaligned|aligned|wrapped|html|asciidoc|latex|latex-longtable|troff-ms|csv|json)$`)
  116. var linestlyeRE = regexp.MustCompile(`^(ascii|old-ascii|unicode)$`)
  117. var borderRE = regexp.MustCompile(`^(single|double)$`)
  118. func Pget(name string) (string, error) {
  119. v, ok := pvars[name]
  120. if !ok {
  121. return "", fmt.Errorf(text.UnknownFormatFieldName, name)
  122. }
  123. return v, nil
  124. }
  125. // Ptoggle toggles a p variable.
  126. func Ptoggle(name, extra string) (string, error) {
  127. _, ok := pvars[name]
  128. if !ok {
  129. return "", fmt.Errorf(text.UnknownFormatFieldName, name)
  130. }
  131. switch name {
  132. case "border", "columns", "pager", "pager_min_lines":
  133. case "expanded":
  134. switch pvars[name] {
  135. case "on", "auto":
  136. pvars[name] = "off"
  137. case "off":
  138. pvars[name] = "on"
  139. default:
  140. panic(fmt.Sprintf("invalid state for field %s", name))
  141. }
  142. case "fieldsep_zero", "footer", "numericlocale", "recordsep_zero", "tuples_only":
  143. switch pvars[name] {
  144. case "on":
  145. pvars[name] = "off"
  146. case "off":
  147. pvars[name] = "on"
  148. default:
  149. panic(fmt.Sprintf("invalid state for field %s", name))
  150. }
  151. case "format":
  152. switch {
  153. case extra != "" && pvars[name] != extra:
  154. pvars[name] = extra
  155. case pvars[name] == "aligned":
  156. pvars[name] = "unaligned"
  157. default:
  158. pvars[name] = "aligned"
  159. }
  160. case "linestyle":
  161. case "fieldsep", "null", "recordsep":
  162. case "tableattr", "title":
  163. pvars[name] = ""
  164. case "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle":
  165. default:
  166. panic(fmt.Sprintf("field %s was defined in package pvars variable, but not in switch", name))
  167. }
  168. return pvars[name], nil
  169. }
  170. // Pset sets a p variable.
  171. func Pset(name, value string) (string, error) {
  172. _, ok := pvars[name]
  173. if !ok {
  174. return "", fmt.Errorf(text.UnknownFormatFieldName, name)
  175. }
  176. switch name {
  177. case "border", "columns", "pager", "pager_min_lines":
  178. i, _ := strconv.Atoi(value)
  179. pvars[name] = fmt.Sprintf("%d", i)
  180. case "expanded":
  181. switch {
  182. case value == "auto":
  183. pvars[name] = "auto"
  184. case onRE.MatchString(value):
  185. pvars[name] = "on"
  186. case offRE.MatchString(value):
  187. pvars[name] = "off"
  188. default:
  189. return "", text.ErrInvalidFormatExpandedType
  190. }
  191. case "fieldsep_zero", "footer", "numericlocale", "recordsep_zero", "tuples_only":
  192. switch {
  193. case onRE.MatchString(value):
  194. pvars[name] = "on"
  195. case offRE.MatchString(value):
  196. pvars[name] = "off"
  197. default:
  198. return "", fmt.Errorf(text.FormatFieldInvalidValue, value, name, "Boolean")
  199. }
  200. case "format":
  201. if !formatRE.MatchString(value) {
  202. return "", text.ErrInvalidFormatType
  203. }
  204. pvars[name] = value
  205. case "linestyle":
  206. if !linestlyeRE.MatchString(value) {
  207. return "", text.ErrInvalidFormatLineStyle
  208. }
  209. pvars[name] = value
  210. case "fieldsep", "null", "recordsep", "tableattr", "title":
  211. pvars[name] = value
  212. case "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle":
  213. if !borderRE.MatchString(value) {
  214. return "", text.ErrInvalidFormatBorderLineStyle
  215. }
  216. pvars[name] = value
  217. default:
  218. panic(fmt.Sprintf("field %s was defined in package pvars variable, but not in switch", name))
  219. }
  220. return pvars[name], nil
  221. }
  222. // timeConstMap is the time const name to value map.
  223. var timeConstMap = map[string]string{
  224. "ANSIC": time.ANSIC,
  225. "UnixDate": time.UnixDate,
  226. "RubyDate": time.RubyDate,
  227. "RFC822": time.RFC822,
  228. "RFC822Z": time.RFC822Z,
  229. "RFC850": time.RFC850,
  230. "RFC1123": time.RFC1123,
  231. "RFC1123Z": time.RFC1123Z,
  232. "RFC3339": time.RFC3339,
  233. "RFC3339Nano": time.RFC3339Nano,
  234. "Kitchen": time.Kitchen,
  235. "Stamp": time.Stamp,
  236. "StampMilli": time.StampMilli,
  237. "StampMicro": time.StampMicro,
  238. "StampNano": time.StampNano,
  239. }
  240. // Timefmt returns the environment TIME_FORMAT.
  241. func Timefmt() string {
  242. tfmt := vars["TIME_FORMAT"]
  243. if s, ok := timeConstMap[tfmt]; ok {
  244. return s
  245. }
  246. return tfmt
  247. }