var.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package ast
  2. import (
  3. "strings"
  4. "gopkg.in/yaml.v3"
  5. "github.com/go-task/task/v3/errors"
  6. "github.com/go-task/task/v3/internal/experiments"
  7. "github.com/go-task/task/v3/internal/omap"
  8. )
  9. // Vars is a string[string] variables map.
  10. type Vars struct {
  11. omap.OrderedMap[string, Var]
  12. }
  13. // ToCacheMap converts Vars to a map containing only the static
  14. // variables
  15. func (vs *Vars) ToCacheMap() (m map[string]any) {
  16. m = make(map[string]any, vs.Len())
  17. _ = vs.Range(func(k string, v Var) error {
  18. if v.Sh != "" {
  19. // Dynamic variable is not yet resolved; trigger
  20. // <no value> to be used in templates.
  21. return nil
  22. }
  23. if v.Live != nil {
  24. m[k] = v.Live
  25. } else {
  26. m[k] = v.Value
  27. }
  28. return nil
  29. })
  30. return
  31. }
  32. // Wrapper around OrderedMap.Set to ensure we don't get nil pointer errors
  33. func (vs *Vars) Range(f func(k string, v Var) error) error {
  34. if vs == nil {
  35. return nil
  36. }
  37. return vs.OrderedMap.Range(f)
  38. }
  39. // Wrapper around OrderedMap.Merge to ensure we don't get nil pointer errors
  40. func (vs *Vars) Merge(other *Vars, include *Include) {
  41. if vs == nil || other == nil {
  42. return
  43. }
  44. _ = other.Range(func(key string, value Var) error {
  45. if include != nil && include.AdvancedImport {
  46. value.Dir = include.Dir
  47. }
  48. vs.Set(key, value)
  49. return nil
  50. })
  51. }
  52. // Wrapper around OrderedMap.Len to ensure we don't get nil pointer errors
  53. func (vs *Vars) Len() int {
  54. if vs == nil {
  55. return 0
  56. }
  57. return vs.OrderedMap.Len()
  58. }
  59. // DeepCopy creates a new instance of Vars and copies
  60. // data by value from the source struct.
  61. func (vs *Vars) DeepCopy() *Vars {
  62. if vs == nil {
  63. return nil
  64. }
  65. return &Vars{
  66. OrderedMap: vs.OrderedMap.DeepCopy(),
  67. }
  68. }
  69. // Var represents either a static or dynamic variable.
  70. type Var struct {
  71. Value any
  72. Live any
  73. Sh string
  74. Ref string
  75. Dir string
  76. }
  77. func (v *Var) UnmarshalYAML(node *yaml.Node) error {
  78. if experiments.MapVariables.Enabled {
  79. // This implementation is not backwards-compatible and replaces the 'sh' key with map variables
  80. if experiments.MapVariables.Value == "1" {
  81. var value any
  82. if err := node.Decode(&value); err != nil {
  83. return errors.NewTaskfileDecodeError(err, node)
  84. }
  85. // If the value is a string and it starts with $, then it's a shell command
  86. if str, ok := value.(string); ok {
  87. if str, ok = strings.CutPrefix(str, "$"); ok {
  88. v.Sh = str
  89. return nil
  90. }
  91. if str, ok = strings.CutPrefix(str, "#"); ok {
  92. v.Ref = str
  93. return nil
  94. }
  95. }
  96. v.Value = value
  97. return nil
  98. }
  99. // This implementation IS backwards-compatible and keeps the 'sh' key and allows map variables to be added under the `map` key
  100. if experiments.MapVariables.Value == "2" {
  101. switch node.Kind {
  102. case yaml.MappingNode:
  103. key := node.Content[0].Value
  104. switch key {
  105. case "sh", "ref", "map":
  106. var m struct {
  107. Sh string
  108. Ref string
  109. Map any
  110. }
  111. if err := node.Decode(&m); err != nil {
  112. return errors.NewTaskfileDecodeError(err, node)
  113. }
  114. v.Sh = m.Sh
  115. v.Ref = m.Ref
  116. v.Value = m.Map
  117. return nil
  118. default:
  119. return errors.NewTaskfileDecodeError(nil, node).WithMessage(`%q is not a valid variable type. Try "sh", "ref", "map" or using a scalar value`, key)
  120. }
  121. default:
  122. var value any
  123. if err := node.Decode(&value); err != nil {
  124. return errors.NewTaskfileDecodeError(err, node)
  125. }
  126. v.Value = value
  127. return nil
  128. }
  129. }
  130. }
  131. switch node.Kind {
  132. case yaml.MappingNode:
  133. key := node.Content[0].Value
  134. switch key {
  135. case "sh", "ref":
  136. var m struct {
  137. Sh string
  138. Ref string
  139. }
  140. if err := node.Decode(&m); err != nil {
  141. return errors.NewTaskfileDecodeError(err, node)
  142. }
  143. v.Sh = m.Sh
  144. v.Ref = m.Ref
  145. return nil
  146. default:
  147. return errors.NewTaskfileDecodeError(nil, node).WithMessage("maps cannot be assigned to variables")
  148. }
  149. default:
  150. var value any
  151. if err := node.Decode(&value); err != nil {
  152. return errors.NewTaskfileDecodeError(err, node)
  153. }
  154. v.Value = value
  155. return nil
  156. }
  157. }