2023-12-29 20:32:03 +00:00
package ast
2018-02-17 14:22:18 -02:00
import (
2023-11-30 01:56:30 +00:00
"strings"
2020-03-29 16:54:59 -03:00
"gopkg.in/yaml.v3"
2023-04-06 12:07:57 +01:00
2024-05-16 02:24:02 +01:00
"github.com/go-task/task/v3/errors"
2023-11-29 16:24:56 +00:00
"github.com/go-task/task/v3/internal/experiments"
2024-01-04 00:31:24 +00:00
"github.com/go-task/task/v3/internal/omap"
2018-02-17 14:22:18 -02:00
)
// Vars is a string[string] variables map.
2020-03-29 16:54:59 -03:00
type Vars struct {
2024-01-04 00:31:24 +00:00
omap . OrderedMap [ string , Var ]
2020-03-29 16:54:59 -03:00
}
2018-02-17 14:22:18 -02:00
2019-08-25 13:16:59 -07:00
// ToCacheMap converts Vars to a map containing only the static
2018-02-17 14:22:18 -02:00
// variables
2023-03-30 20:03:59 +00:00
func ( vs * Vars ) ToCacheMap ( ) ( m map [ string ] any ) {
m = make ( map [ string ] any , vs . Len ( ) )
2022-05-14 21:00:15 -03:00
_ = vs . Range ( func ( k string , v Var ) error {
2018-02-17 14:22:18 -02:00
if v . Sh != "" {
// Dynamic variable is not yet resolved; trigger
// <no value> to be used in templates.
2020-03-29 16:54:59 -03:00
return nil
2018-02-17 14:22:18 -02:00
}
2019-08-25 13:16:59 -07:00
if v . Live != nil {
m [ k ] = v . Live
} else {
2023-11-28 18:18:28 +00:00
m [ k ] = v . Value
2019-08-25 13:16:59 -07:00
}
2020-03-29 16:54:59 -03:00
return nil
} )
2018-02-17 14:22:18 -02:00
return
}
2023-04-06 12:07:57 +01:00
// Wrapper around OrderedMap.Set to ensure we don't get nil pointer errors
func ( vs * Vars ) Range ( f func ( k string , v Var ) error ) error {
if vs == nil {
return nil
}
return vs . OrderedMap . Range ( f )
}
// Wrapper around OrderedMap.Merge to ensure we don't get nil pointer errors
2024-03-19 15:02:32 +00:00
func ( vs * Vars ) Merge ( other * Vars , include * Include ) {
2023-04-06 12:07:57 +01:00
if vs == nil || other == nil {
return
}
2024-03-25 21:07:37 +00:00
_ = other . Range ( func ( key string , value Var ) error {
2024-03-19 15:02:32 +00:00
if include != nil && include . AdvancedImport {
value . Dir = include . Dir
}
vs . Set ( key , value )
return nil
} )
2023-04-06 12:07:57 +01:00
}
// Wrapper around OrderedMap.Len to ensure we don't get nil pointer errors
2021-03-20 11:56:19 -03:00
func ( vs * Vars ) Len ( ) int {
if vs == nil {
return 0
}
2023-04-06 12:07:57 +01:00
return vs . OrderedMap . Len ( )
}
// DeepCopy creates a new instance of Vars and copies
// data by value from the source struct.
func ( vs * Vars ) DeepCopy ( ) * Vars {
if vs == nil {
return nil
}
return & Vars {
OrderedMap : vs . OrderedMap . DeepCopy ( ) ,
}
2021-03-20 11:56:19 -03:00
}
2018-02-17 14:22:18 -02:00
// Var represents either a static or dynamic variable.
type Var struct {
2023-11-29 16:21:21 +00:00
Value any
2023-11-28 18:18:28 +00:00
Live any
Sh string
2023-12-29 03:49:12 +00:00
Ref string
2023-12-23 04:59:44 +00:00
Json string
Yaml string
2023-11-28 18:18:28 +00:00
Dir string
2018-02-17 14:22:18 -02:00
}
2022-12-19 01:11:31 +00:00
func ( v * Var ) UnmarshalYAML ( node * yaml . Node ) error {
2024-04-09 12:14:14 +01:00
if experiments . MapVariables . Enabled {
2023-12-23 04:59:10 +00:00
// This implementation is not backwards-compatible and replaces the 'sh' key with map variables
2024-04-09 12:14:14 +01:00
if experiments . MapVariables . Value == "1" {
2023-12-23 04:59:10 +00:00
var value any
if err := node . Decode ( & value ) ; err != nil {
2024-05-16 02:24:02 +01:00
return errors . NewTaskfileDecodeError ( err , node )
2023-12-23 04:59:10 +00:00
}
// If the value is a string and it starts with $, then it's a shell command
if str , ok := value . ( string ) ; ok {
if str , ok = strings . CutPrefix ( str , "$" ) ; ok {
v . Sh = str
return nil
}
}
v . Value = value
return nil
2023-11-29 16:24:56 +00:00
}
2023-12-23 04:59:10 +00:00
// This implementation IS backwards-compatible and keeps the 'sh' key and allows map variables to be added under the `map` key
2024-04-09 12:14:14 +01:00
if experiments . MapVariables . Value == "2" {
2023-12-23 04:59:10 +00:00
switch node . Kind {
case yaml . MappingNode :
key := node . Content [ 0 ] . Value
switch key {
2023-12-29 03:49:12 +00:00
case "sh" , "ref" , "map" , "json" , "yaml" :
2023-12-23 04:59:10 +00:00
var m struct {
2023-12-23 04:59:44 +00:00
Sh string
2023-12-29 03:49:12 +00:00
Ref string
2023-12-23 04:59:44 +00:00
Map any
Json string
Yaml string
2023-12-23 04:59:10 +00:00
}
if err := node . Decode ( & m ) ; err != nil {
2024-05-16 02:24:02 +01:00
return errors . NewTaskfileDecodeError ( err , node )
2023-12-23 04:59:10 +00:00
}
v . Sh = m . Sh
2023-12-29 03:49:12 +00:00
v . Ref = m . Ref
2023-12-23 04:59:10 +00:00
v . Value = m . Map
2023-12-23 04:59:44 +00:00
v . Json = m . Json
v . Yaml = m . Yaml
2023-12-23 04:59:10 +00:00
return nil
default :
2024-05-16 02:24:02 +01:00
return errors . NewTaskfileDecodeError ( nil , node ) . WithMessage ( ` %q is not a valid variable type. Try "sh", "ref", "map", "json", "yaml" or using a scalar value ` , key )
2023-12-23 04:59:10 +00:00
}
default :
var value any
if err := node . Decode ( & value ) ; err != nil {
2024-05-16 02:24:02 +01:00
return errors . NewTaskfileDecodeError ( err , node )
2023-12-23 04:59:10 +00:00
}
v . Value = value
2023-11-30 01:56:30 +00:00
return nil
}
}
2023-11-29 16:24:56 +00:00
}
2022-12-19 01:11:31 +00:00
switch node . Kind {
case yaml . MappingNode :
2024-04-09 12:14:14 +01:00
if len ( node . Content ) > 2 || node . Content [ 0 ] . Value != "sh" {
2024-05-16 02:24:02 +01:00
return errors . NewTaskfileDecodeError ( nil , node ) . WithMessage ( "maps cannot be assigned to variables" )
2024-04-09 12:14:14 +01:00
}
2022-12-19 01:11:31 +00:00
var sh struct {
Sh string
}
if err := node . Decode ( & sh ) ; err != nil {
2024-05-16 02:24:02 +01:00
return errors . NewTaskfileDecodeError ( err , node )
2022-12-19 01:11:31 +00:00
}
v . Sh = sh . Sh
return nil
2024-04-09 12:14:14 +01:00
default :
var value any
if err := node . Decode ( & value ) ; err != nil {
2024-05-16 02:24:02 +01:00
return errors . NewTaskfileDecodeError ( err , node )
2024-04-09 12:14:14 +01:00
}
v . Value = value
return nil
}
2018-02-17 14:22:18 -02:00
}