mirror of
				https://github.com/go-task/task.git
				synced 2025-10-30 23:58:01 +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.ListJson, | ||||
| 		flags.NoStatus, | ||||
| 		flags.Nested, | ||||
| 	) | ||||
| 	if listOptions.ShouldListTasks() { | ||||
| 		if flags.Silent { | ||||
|   | ||||
							
								
								
									
										65
									
								
								help.go
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								help.go
									
									
									
									
									
								
							| @@ -24,15 +24,17 @@ type ListOptions struct { | ||||
| 	ListAllTasks                  bool | ||||
| 	FormatTaskListAsJSON          bool | ||||
| 	NoStatus                      bool | ||||
| 	Nested                        bool | ||||
| } | ||||
|  | ||||
| // 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{ | ||||
| 		ListOnlyTasksWithDescriptions: list, | ||||
| 		ListAllTasks:                  listAll, | ||||
| 		FormatTaskListAsJSON:          listAsJson, | ||||
| 		NoStatus:                      noStatus, | ||||
| 		Nested:                        nested, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -63,7 +65,7 @@ func (e *Executor) ListTasks(o ListOptions) (bool, error) { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	if o.FormatTaskListAsJSON { | ||||
| 		output, err := e.ToEditorOutput(tasks, o.NoStatus) | ||||
| 		output, err := e.ToEditorOutput(tasks, o.NoStatus, o.Nested) | ||||
| 		if err != nil { | ||||
| 			return false, err | ||||
| 		} | ||||
| @@ -135,33 +137,17 @@ func (e *Executor) ListTaskNames(allTasks bool) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Taskfile, error) { | ||||
| 	o := &editors.Taskfile{ | ||||
| 		Tasks:    make([]editors.Task, len(tasks)), | ||||
| 		Location: e.Taskfile.Location, | ||||
| 	} | ||||
| func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool, nested bool) (*editors.Namespace, error) { | ||||
| 	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 { | ||||
| 		aliases := []string{} | ||||
| 		if len(tasks[i].Aliases) > 0 { | ||||
| 			aliases = tasks[i].Aliases | ||||
| 		} | ||||
| 		g.Go(func() error { | ||||
| 			o.Tasks[i] = editors.Task{ | ||||
| 				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, | ||||
| 				}, | ||||
| 			} | ||||
| 			editorTask := editors.NewTask(tasks[i]) | ||||
|  | ||||
| 			if noStatus { | ||||
| 				editorTasks[i] = editorTask | ||||
| 				return nil | ||||
| 			} | ||||
|  | ||||
| @@ -180,10 +166,35 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			o.Tasks[i].UpToDate = upToDate | ||||
|  | ||||
| 			editorTask.UpToDate = &upToDate | ||||
| 			editorTasks[i] = editorTask | ||||
| 			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 | ||||
|  | ||||
| import ( | ||||
| 	"github.com/go-task/task/v3/taskfile/ast" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	// Taskfile wraps task list output for use in editor integrations (e.g. VSCode, etc) | ||||
| 	Taskfile struct { | ||||
| 		Tasks    []Task `json:"tasks"` | ||||
| 		Location string `json:"location"` | ||||
| 	// Namespace wraps task list output for use in editor integrations (e.g. VSCode, etc) | ||||
| 	Namespace struct { | ||||
| 		Tasks      []Task                `json:"tasks"` | ||||
| 		Namespaces map[string]*Namespace `json:"namespaces,omitempty"` | ||||
| 		Location   string                `json:"location,omitempty"` | ||||
| 	} | ||||
| 	// Task describes a single task | ||||
| 	Task struct { | ||||
| @@ -13,7 +18,7 @@ type ( | ||||
| 		Desc     string    `json:"desc"` | ||||
| 		Summary  string    `json:"summary"` | ||||
| 		Aliases  []string  `json:"aliases"` | ||||
| 		UpToDate bool      `json:"up_to_date"` | ||||
| 		UpToDate *bool     `json:"up_to_date,omitempty"` | ||||
| 		Location *Location `json:"location"` | ||||
| 	} | ||||
| 	// Location describes a task's location in a taskfile | ||||
| @@ -23,3 +28,59 @@ type ( | ||||
| 		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 | ||||
| 	Status              bool | ||||
| 	NoStatus            bool | ||||
| 	Nested              bool | ||||
| 	Insecure            bool | ||||
| 	Force               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.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(&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.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.") | ||||
| 	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") | ||||
| 	} | ||||
|  | ||||
| 	if Nested && !ListJson { | ||||
| 		return errors.New("task: --nested only applies to --json with --list or --list-all") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user