diff --git a/experiments/experiments.go b/experiments/experiments.go index 9e646c57b7..f4deeb3415 100644 --- a/experiments/experiments.go +++ b/experiments/experiments.go @@ -34,12 +34,7 @@ func Parse(dir string) { // Read any .env files readDotEnv(dir) - // Create a node for the Task config reader - node, _ := taskrc.NewNode("", dir) - - // Read the Task config file - reader := taskrc.NewReader() - config, _ := reader.Read(node) + config, _ := taskrc.GetConfig(dir) // Initialize the experiments GentleForce = New("GENTLE_FORCE", config, 1) diff --git a/taskrc/ast/taskrc.go b/taskrc/ast/taskrc.go index f82452a94d..18df8c8a6d 100644 --- a/taskrc/ast/taskrc.go +++ b/taskrc/ast/taskrc.go @@ -6,3 +6,20 @@ type TaskRC struct { Version *semver.Version `yaml:"version"` Experiments map[string]int `yaml:"experiments"` } + +// Merge combines the current TaskRC with another TaskRC, prioritizing non-nil fields from the other TaskRC. +func (t *TaskRC) Merge(other *TaskRC) { + if other == nil { + return + } + if t.Version == nil && other.Version != nil { + t.Version = other.Version + } + if t.Experiments == nil && other.Experiments != nil { + t.Experiments = other.Experiments + } else if t.Experiments != nil && other.Experiments != nil { + for k, v := range other.Experiments { + t.Experiments[k] = v + } + } +} diff --git a/taskrc/taskrc.go b/taskrc/taskrc.go index af99305553..8511b2a040 100644 --- a/taskrc/taskrc.go +++ b/taskrc/taskrc.go @@ -1,6 +1,41 @@ package taskrc +import ( + "os" + + "github.com/go-task/task/v3/taskrc/ast" +) + var defaultTaskRCs = []string{ ".taskrc.yml", ".taskrc.yaml", } + +// GetConfig loads and merges local and global Task configuration files +func GetConfig(dir string) (*ast.TaskRC, error) { + reader := NewReader() + + // LocalNode is the node for the local Task configuration file + localNode, _ := NewNode("", dir) + + home, err := os.UserHomeDir() + if err != nil { + return nil, err + } + + // GlobalNode is the node for the global Task configuration file (~/.taskrc.yml) + globalNode, _ := NewNode("", home) + + localConfig, _ := reader.Read(localNode) + + globalConfig, _ := reader.Read(globalNode) + + if globalConfig == nil { + return localConfig, nil + } + + // Merge the global configuration into the local configuration + globalConfig.Merge(localConfig) + + return globalConfig, nil +} diff --git a/taskrc/taskrc_test.go b/taskrc/taskrc_test.go new file mode 100644 index 0000000000..09844d518b --- /dev/null +++ b/taskrc/taskrc_test.go @@ -0,0 +1,89 @@ +package taskrc + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/go-task/task/v3/taskrc/ast" +) + +const ( + localConfigYAML = ` +experiments: + GENTLE_FORCE: 1 + ENV_PRECEDENCE: 0 +` + + globalConfigYAML = ` +experiments: + GENTLE_FORCE: 0 + REMOTE_TASKFILES: 1 + ENV_PRECEDENCE: 1 + +` +) + +func setupDirs(t *testing.T) (localDir, globalDir string) { + t.Helper() + localDir = t.TempDir() + globalDir = t.TempDir() + + t.Setenv("HOME", globalDir) + + return localDir, globalDir +} + +func writeFile(t *testing.T, dir, filename, content string) { + t.Helper() + err := os.WriteFile(filepath.Join(dir, filename), []byte(content), 0o644) + assert.NoError(t, err) +} + +func TestGetConfig_MergesGlobalAndLocal(t *testing.T) { //nolint:paralleltest // cannot run in parallel + localDir, globalDir := setupDirs(t) + + // Write local config + writeFile(t, localDir, ".taskrc.yml", localConfigYAML) + + // Write global config + writeFile(t, globalDir, ".taskrc.yml", globalConfigYAML) + + cfg, err := GetConfig(localDir) + assert.NoError(t, err) + assert.NotNil(t, cfg) + fmt.Printf("cfg : %#v\n", cfg) + assert.Equal(t, &ast.TaskRC{Version: nil, Experiments: map[string]int{"GENTLE_FORCE": 1, "ENV_PRECEDENCE": 0, "REMOTE_TASKFILES": 1}}, cfg) +} + +func TestGetConfig_NoConfigFiles(t *testing.T) { //nolint:paralleltest // cannot run in parallel + localDir, _ := setupDirs(t) + + cfg, err := GetConfig(localDir) + fmt.Printf("cfg : %#v\n", cfg) + assert.NoError(t, err) + assert.Nil(t, cfg) +} + +func TestGetConfig_OnlyGlobal(t *testing.T) { //nolint:paralleltest // cannot run in parallel + localDir, globalDir := setupDirs(t) + + writeFile(t, globalDir, ".taskrc.yml", globalConfigYAML) + + cfg, err := GetConfig(localDir) + assert.NoError(t, err) + assert.Equal(t, &ast.TaskRC{Version: nil, Experiments: map[string]int{"GENTLE_FORCE": 0, "ENV_PRECEDENCE": 1, "REMOTE_TASKFILES": 1}}, cfg) +} + +func TestGetConfig_OnlyLocal(t *testing.T) { //nolint:paralleltest // cannot run in parallel + localDir, _ := setupDirs(t) + + writeFile(t, localDir, ".taskrc.yml", localConfigYAML) + + cfg, err := GetConfig(localDir) + assert.NoError(t, err) + assert.Equal(t, &ast.TaskRC{Version: nil, Experiments: map[string]int{"GENTLE_FORCE": 1, "ENV_PRECEDENCE": 0}}, cfg) +} diff --git a/website/docs/reference/environment.mdx b/website/docs/reference/environment.mdx index d6772620c6..5a5b932a0b 100644 --- a/website/docs/reference/environment.mdx +++ b/website/docs/reference/environment.mdx @@ -1,6 +1,6 @@ --- slug: /reference/environment -sidebar_position: 5 +sidebar_position: 6 --- # Environment Reference diff --git a/website/docs/reference/schema-taskrc.mdx b/website/docs/reference/schema-taskrc.mdx new file mode 100644 index 0000000000..fc7a69a56c --- /dev/null +++ b/website/docs/reference/schema-taskrc.mdx @@ -0,0 +1,31 @@ +--- +slug: /reference/schema-taskrc +sidebar_position: 4 +toc_min_heading_level: 2 +toc_max_heading_level: 5 +--- + +# Schema taskrc Reference + +The `.taskrc` file is the configuration file used by Task. It can have either a `.yml` or `.yaml` extension and can be located in two places: + +- **Local** – at the root of the project. +- **Global** – in the user's HOME directory. + +When both local and global `.taskrc.yml` files are present, their contents are merged. If the same key exists in both files, the value from the local file takes precedence over the global one. + +## Schema + +| Attribute | Type | Default | Description | +|---------------|-------------------------------------|---------|------------------------------------------------------| +| `version` | `string` | | Version of the Taskfile. The current version is `3`. | +| `experiments` | [`map[string]number`](#experiments) | | Experiments to enable or disable | + +## Experiments + +| Attribute | Type | Default | Description | +|--------------------|----------|---------|----------------------------------------------------------| +| `REMOTE_TASKFILES` | `number` | | Enable (1) or disable (0) the remote taskfile experiment | +| `ENV_PRECEDENCE` | `number` | | Enable (1) or disable (0) the env precedence experiment. | +| `GENTLE_FORCE` | `number` | | Enable (1) or disable (0) the gentle force experiment. | + diff --git a/website/docs/reference/templating.mdx b/website/docs/reference/templating.mdx index b471b134ca..9894289667 100644 --- a/website/docs/reference/templating.mdx +++ b/website/docs/reference/templating.mdx @@ -1,6 +1,6 @@ --- slug: /reference/templating/ -sidebar_position: 4 +sidebar_position: 5 toc_min_heading_level: 2 toc_max_heading_level: 5 ---