Skip to content

feat: add global .taskrc in HOME #2247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions experiments/experiments.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
17 changes: 17 additions & 0 deletions taskrc/ast/taskrc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
35 changes: 35 additions & 0 deletions taskrc/taskrc.go
Original file line number Diff line number Diff line change
@@ -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
}
89 changes: 89 additions & 0 deletions taskrc/taskrc_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
2 changes: 1 addition & 1 deletion website/docs/reference/environment.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
slug: /reference/environment
sidebar_position: 5
sidebar_position: 6
---

# Environment Reference
Expand Down
31 changes: 31 additions & 0 deletions website/docs/reference/schema-taskrc.mdx
Original file line number Diff line number Diff line change
@@ -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. |

2 changes: 1 addition & 1 deletion website/docs/reference/templating.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
slug: /reference/templating/
sidebar_position: 4
sidebar_position: 5
toc_min_heading_level: 2
toc_max_heading_level: 5
---
Expand Down
Loading