mirror of
https://github.com/go-task/task.git
synced 2025-11-23 22:24:45 +02:00
feat: nested json (#2415)
* feat: nested json * feat: remove up_to_date from json output when --no-status flag is set * feat: restrict use of --nested with --json and --list/--list-all
This commit is contained in:
@@ -128,6 +128,7 @@ func run() error {
|
|||||||
flags.ListAll,
|
flags.ListAll,
|
||||||
flags.ListJson,
|
flags.ListJson,
|
||||||
flags.NoStatus,
|
flags.NoStatus,
|
||||||
|
flags.Nested,
|
||||||
)
|
)
|
||||||
if listOptions.ShouldListTasks() {
|
if listOptions.ShouldListTasks() {
|
||||||
if flags.Silent {
|
if flags.Silent {
|
||||||
|
|||||||
65
help.go
65
help.go
@@ -24,15 +24,17 @@ type ListOptions struct {
|
|||||||
ListAllTasks bool
|
ListAllTasks bool
|
||||||
FormatTaskListAsJSON bool
|
FormatTaskListAsJSON bool
|
||||||
NoStatus bool
|
NoStatus bool
|
||||||
|
Nested bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewListOptions creates a new ListOptions instance
|
// NewListOptions creates a new ListOptions instance
|
||||||
func NewListOptions(list, listAll, listAsJson, noStatus bool) ListOptions {
|
func NewListOptions(list, listAll, listAsJson, noStatus, nested bool) ListOptions {
|
||||||
return ListOptions{
|
return ListOptions{
|
||||||
ListOnlyTasksWithDescriptions: list,
|
ListOnlyTasksWithDescriptions: list,
|
||||||
ListAllTasks: listAll,
|
ListAllTasks: listAll,
|
||||||
FormatTaskListAsJSON: listAsJson,
|
FormatTaskListAsJSON: listAsJson,
|
||||||
NoStatus: noStatus,
|
NoStatus: noStatus,
|
||||||
|
Nested: nested,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +65,7 @@ func (e *Executor) ListTasks(o ListOptions) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if o.FormatTaskListAsJSON {
|
if o.FormatTaskListAsJSON {
|
||||||
output, err := e.ToEditorOutput(tasks, o.NoStatus)
|
output, err := e.ToEditorOutput(tasks, o.NoStatus, o.Nested)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -135,33 +137,17 @@ func (e *Executor) ListTaskNames(allTasks bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Taskfile, error) {
|
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool, nested bool) (*editors.Namespace, error) {
|
||||||
o := &editors.Taskfile{
|
|
||||||
Tasks: make([]editors.Task, len(tasks)),
|
|
||||||
Location: e.Taskfile.Location,
|
|
||||||
}
|
|
||||||
var g errgroup.Group
|
var g errgroup.Group
|
||||||
|
editorTasks := make([]editors.Task, len(tasks))
|
||||||
|
|
||||||
|
// Look over each task in parallel and turn it into an editor task
|
||||||
for i := range tasks {
|
for i := range tasks {
|
||||||
aliases := []string{}
|
|
||||||
if len(tasks[i].Aliases) > 0 {
|
|
||||||
aliases = tasks[i].Aliases
|
|
||||||
}
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
o.Tasks[i] = editors.Task{
|
editorTask := editors.NewTask(tasks[i])
|
||||||
Name: tasks[i].Name(),
|
|
||||||
Task: tasks[i].Task,
|
|
||||||
Desc: tasks[i].Desc,
|
|
||||||
Summary: tasks[i].Summary,
|
|
||||||
Aliases: aliases,
|
|
||||||
UpToDate: false,
|
|
||||||
Location: &editors.Location{
|
|
||||||
Line: tasks[i].Location.Line,
|
|
||||||
Column: tasks[i].Location.Column,
|
|
||||||
Taskfile: tasks[i].Location.Taskfile,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if noStatus {
|
if noStatus {
|
||||||
|
editorTasks[i] = editorTask
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,10 +166,35 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Tasks[i].UpToDate = upToDate
|
editorTask.UpToDate = &upToDate
|
||||||
|
editorTasks[i] = editorTask
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return o, g.Wait()
|
if err := g.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the root namespace
|
||||||
|
var tasksLen int
|
||||||
|
if !nested {
|
||||||
|
tasksLen = len(editorTasks)
|
||||||
|
}
|
||||||
|
rootNamespace := &editors.Namespace{
|
||||||
|
Tasks: make([]editors.Task, tasksLen),
|
||||||
|
Location: e.Taskfile.Location,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively add namespaces to the root namespace or if nesting is
|
||||||
|
// disabled add them all to the root namespace
|
||||||
|
for i, task := range editorTasks {
|
||||||
|
taskNamespacePath := strings.Split(task.Task, ast.NamespaceSeparator)
|
||||||
|
if nested {
|
||||||
|
rootNamespace.AddNamespace(taskNamespacePath, task)
|
||||||
|
} else {
|
||||||
|
rootNamespace.Tasks[i] = task
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootNamespace, g.Wait()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package editors
|
package editors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Taskfile wraps task list output for use in editor integrations (e.g. VSCode, etc)
|
// Namespace wraps task list output for use in editor integrations (e.g. VSCode, etc)
|
||||||
Taskfile struct {
|
Namespace struct {
|
||||||
Tasks []Task `json:"tasks"`
|
Tasks []Task `json:"tasks"`
|
||||||
Location string `json:"location"`
|
Namespaces map[string]*Namespace `json:"namespaces,omitempty"`
|
||||||
|
Location string `json:"location,omitempty"`
|
||||||
}
|
}
|
||||||
// Task describes a single task
|
// Task describes a single task
|
||||||
Task struct {
|
Task struct {
|
||||||
@@ -13,7 +18,7 @@ type (
|
|||||||
Desc string `json:"desc"`
|
Desc string `json:"desc"`
|
||||||
Summary string `json:"summary"`
|
Summary string `json:"summary"`
|
||||||
Aliases []string `json:"aliases"`
|
Aliases []string `json:"aliases"`
|
||||||
UpToDate bool `json:"up_to_date"`
|
UpToDate *bool `json:"up_to_date,omitempty"`
|
||||||
Location *Location `json:"location"`
|
Location *Location `json:"location"`
|
||||||
}
|
}
|
||||||
// Location describes a task's location in a taskfile
|
// Location describes a task's location in a taskfile
|
||||||
@@ -23,3 +28,59 @@ type (
|
|||||||
Taskfile string `json:"taskfile"`
|
Taskfile string `json:"taskfile"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func NewTask(task *ast.Task) Task {
|
||||||
|
aliases := []string{}
|
||||||
|
if len(task.Aliases) > 0 {
|
||||||
|
aliases = task.Aliases
|
||||||
|
}
|
||||||
|
return Task{
|
||||||
|
Name: task.Name(),
|
||||||
|
Task: task.Task,
|
||||||
|
Desc: task.Desc,
|
||||||
|
Summary: task.Summary,
|
||||||
|
Aliases: aliases,
|
||||||
|
Location: &Location{
|
||||||
|
Line: task.Location.Line,
|
||||||
|
Column: task.Location.Column,
|
||||||
|
Taskfile: task.Location.Taskfile,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parent *Namespace) AddNamespace(namespacePath []string, task Task) {
|
||||||
|
if len(namespacePath) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no child namespaces, then we have found a task and we can
|
||||||
|
// simply add it to the current namespace
|
||||||
|
if len(namespacePath) == 1 {
|
||||||
|
parent.Tasks = append(parent.Tasks, task)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the key of the current namespace in the path
|
||||||
|
namespaceKey := namespacePath[0]
|
||||||
|
|
||||||
|
// Add the namespace to the parent namespaces map using the namespace key
|
||||||
|
if parent.Namespaces == nil {
|
||||||
|
parent.Namespaces = make(map[string]*Namespace, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for the current namespace in the parent namespaces map
|
||||||
|
// If it doesn't exist, create it
|
||||||
|
namespace, ok := parent.Namespaces[namespaceKey]
|
||||||
|
if !ok {
|
||||||
|
namespace = &Namespace{}
|
||||||
|
parent.Namespaces[namespaceKey] = namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the current namespace key from the namespace path.
|
||||||
|
childNamespacePath := namespacePath[1:]
|
||||||
|
|
||||||
|
// If there are no child namespaces in the task name, then we have found the
|
||||||
|
// namespace of the task and we can add it to the current namespace.
|
||||||
|
// Otherwise, we need to go deeper
|
||||||
|
namespace.AddNamespace(childNamespacePath, task)
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ var (
|
|||||||
TaskSort string
|
TaskSort string
|
||||||
Status bool
|
Status bool
|
||||||
NoStatus bool
|
NoStatus bool
|
||||||
|
Nested bool
|
||||||
Insecure bool
|
Insecure bool
|
||||||
Force bool
|
Force bool
|
||||||
ForceAll bool
|
ForceAll bool
|
||||||
@@ -117,6 +118,7 @@ func init() {
|
|||||||
pflag.StringVar(&TaskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].")
|
pflag.StringVar(&TaskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].")
|
||||||
pflag.BoolVar(&Status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.")
|
pflag.BoolVar(&Status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.")
|
||||||
pflag.BoolVar(&NoStatus, "no-status", false, "Ignore status when listing tasks as JSON")
|
pflag.BoolVar(&NoStatus, "no-status", false, "Ignore status when listing tasks as JSON")
|
||||||
|
pflag.BoolVar(&Nested, "nested", false, "Nest namespaces when listing tasks as JSON")
|
||||||
pflag.BoolVar(&Insecure, "insecure", getConfig(config, config.Remote.Insecure, false), "Forces Task to download Taskfiles over insecure connections.")
|
pflag.BoolVar(&Insecure, "insecure", getConfig(config, config.Remote.Insecure, false), "Forces Task to download Taskfiles over insecure connections.")
|
||||||
pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.")
|
pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.")
|
||||||
pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, config.Verbose, false), "Enables verbose mode.")
|
pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, config.Verbose, false), "Enables verbose mode.")
|
||||||
@@ -194,6 +196,10 @@ func Validate() error {
|
|||||||
return errors.New("task: --no-status only applies to --json with --list or --list-all")
|
return errors.New("task: --no-status only applies to --json with --list or --list-all")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Nested && !ListJson {
|
||||||
|
return errors.New("task: --nested only applies to --json with --list or --list-all")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user