Skip to content

Commit

Permalink
Extract & improve task list formatting (oya tasks).
Browse files Browse the repository at this point in the history
  • Loading branch information
bilus committed Oct 26, 2021
1 parent c541416 commit 16c60c1
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 39 deletions.
132 changes: 132 additions & 0 deletions cmd/internal/printers/tasks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package printers

import (
"fmt"
"io"
"path"
"path/filepath"
"sort"
"strings"

"github.com/tooploox/oya/pkg/task"
"github.com/tooploox/oya/pkg/types"
)

type taskInfo struct {
taskName task.Name
alias types.Alias
bareTaskName string
meta task.Meta
relOyafilePath string
}

type TaskList struct {
workDir string
tasks []taskInfo
}

func NewTaskList(workDir string) *TaskList {
return &TaskList{
workDir: workDir,
}
}

func (p *TaskList) AddTask(taskName task.Name, meta task.Meta, oyafilePath string) error {
alias, bareTaskName := taskName.Split()
relPath, err := filepath.Rel(p.workDir, oyafilePath)
if err != nil {
return err
}

p.tasks = append(p.tasks, taskInfo{taskName, alias, bareTaskName, meta, relPath})
return nil
}

func (p *TaskList) Print(w io.Writer) {
sortTasks(p.tasks)

printTask := p.taskPrinter()

lastRelPath := ""
first := true
for _, t := range p.tasks {
if t.relOyafilePath != lastRelPath {
if !first {
fmt.Fprintln(w)
} else {
first = false
}

fmt.Fprintf(w, "# in ./%s\n", t.relOyafilePath)
}
printTask(w, t.taskName, t.meta)
lastRelPath = t.relOyafilePath
}
}

func (p *TaskList) taskPrinter() func(io.Writer, task.Name, task.Meta) {
docOffset := maxTaskWidth(p.tasks)
return func(w io.Writer, taskName task.Name, meta task.Meta) {
fmt.Fprintf(w, "oya run %s", taskName)
if len(meta.Doc) > 0 {
padding := strings.Repeat(" ", docOffset-len(taskName))
fmt.Fprintf(w, "%s # %s", padding, meta.Doc)
}
fmt.Fprintln(w)
}
}

func maxTaskWidth(tasks []taskInfo) int {
w := 0
for _, t := range tasks {
l := len(string(t.taskName))
if l > w {
w = l
}
}
return w
}

func isParentPath(p1, p2 string) bool {
relPath, _ := filepath.Rel(p2, p1)
return strings.Contains(relPath, "../")
}

func sortTasks(tasks []taskInfo) {
sort.SliceStable(tasks, func(i, j int) bool {
lt := tasks[i]
rt := tasks[j]

ldir := path.Dir(lt.relOyafilePath)
rdir := path.Dir(rt.relOyafilePath)

// Top-level tasks go before tasks in subdirectories.
if isParentPath(ldir, rdir) {
return true
}
if isParentPath(rdir, ldir) {
return false
}

if rdir == ldir {
if len(lt.alias) == 0 && len(rt.alias) != 0 {
return true
}
if len(lt.alias) != 0 && len(rt.alias) == 0 {
return false
}
// Tasks w/o alias before tasks with alias,
// sort aliases alphabetically.
if lt.alias < rt.alias {
return true
}

// Sort tasks alphabetically.
if lt.bareTaskName < rt.bareTaskName {
return true
}
}

return false
})
}
153 changes: 153 additions & 0 deletions cmd/internal/printers/tasks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package printers_test

import (
"strings"
"testing"

"github.com/tooploox/oya/cmd/internal/printers"
"github.com/tooploox/oya/pkg/task"
"github.com/tooploox/oya/pkg/types"
tu "github.com/tooploox/oya/testutil"
)

type mockWriter struct {
bs []byte
}

func (w *mockWriter) Write(p []byte) (n int, err error) {
w.bs = append(w.bs, p...)
return len(p), nil
}

func (w *mockWriter) Lines() []string {
if len(w.bs) == 0 {
return []string{}
}
return strings.Split(string(w.bs), "\n")
}

func TestTaskList(t *testing.T) {
type taskDef struct {
name task.Name
meta task.Meta
oyafilePath string
}
testCases := []struct {
desc string
workDir string
tasks []taskDef
expectedOutput []string
}{
{
desc: "no tasks",
workDir: "/project/",
tasks: nil,
expectedOutput: []string{},
},
{
desc: "one global task",
workDir: "/project/",
tasks: []taskDef{
{task.Name("task1"), task.Meta{}, "/project/Oyafile"},
},
expectedOutput: []string{
"# in ./Oyafile",
"oya run task1",
"",
},
},
{
desc: "global tasks before imported tasks",
workDir: "/project/",
tasks: []taskDef{
{task.Name("task1"), task.Meta{}, "/project/Oyafile"},
{task.Name("othertask"), task.Meta{}, "/project/Oyafile"},
{task.Name("task2").Aliased(types.Alias("pack")), task.Meta{}, "/project/Oyafile"},
{task.Name("task3").Aliased(types.Alias("pack")), task.Meta{}, "/project/Oyafile"},
},
expectedOutput: []string{
"# in ./Oyafile",
"oya run othertask",
"oya run task1",
"oya run pack.task2",
"oya run pack.task3",
"",
},
},
{
desc: "top-level tasks before tasks in subdirectories",
workDir: "/project/",
tasks: []taskDef{
{task.Name("task1"), task.Meta{}, "/project/Oyafile"},
{task.Name("otherTask"), task.Meta{}, "/project/Oyafile"},
{task.Name("aTask"), task.Meta{}, "/project/subdir/Oyafile"},
{task.Name("task3"), task.Meta{}, "/project/subdir/Oyafile"},
},
expectedOutput: []string{
"# in ./Oyafile",
"oya run otherTask",
"oya run task1",
"",
"# in ./subdir/Oyafile",
"oya run aTask",
"oya run task3",
"",
},
},
{
desc: "sort aliases and task names alphabetically",
workDir: "/project/",
tasks: []taskDef{
{task.Name("yyy"), task.Meta{}, "/project/Oyafile"},
{task.Name("zzz"), task.Meta{}, "/project/Oyafile"},
{task.Name("aaa").Aliased(types.Alias("BBB")), task.Meta{}, "/project/Oyafile"},
{task.Name("ddd").Aliased(types.Alias("AAA")), task.Meta{}, "/project/Oyafile"},
{task.Name("ccc").Aliased(types.Alias("AAA")), task.Meta{}, "/project/Oyafile"},
},
expectedOutput: []string{
"# in ./Oyafile",
"oya run yyy",
"oya run zzz",
"oya run AAA.ccc",
"oya run AAA.ddd",
"oya run BBB.aaa",
"",
},
},
{
desc: "task descriptions are aligned",
workDir: "/project/",
tasks: []taskDef{
{task.Name("y"), task.Meta{Doc: "a description"}, "/project/Oyafile"},
{task.Name("zzz"), task.Meta{Doc: "another description"}, "/project/Oyafile"},
{task.Name("aaa").Aliased(types.Alias("BBB")), task.Meta{Doc: "task aa"}, "/project/Oyafile"},
{task.Name("ddddd").Aliased(types.Alias("AAA")), task.Meta{}, "/project/Oyafile"},
{task.Name("ccc").Aliased(types.Alias("AAA")), task.Meta{}, "/project/Oyafile"},
},
expectedOutput: []string{
"# in ./Oyafile",
"oya run y # a description",
"oya run zzz # another description",
"oya run AAA.ccc",
"oya run AAA.ddddd",
"oya run BBB.aaa # task aa",
"",
},
},
// NEXT: Exposed
}

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
tl := printers.NewTaskList(tc.workDir)
for _, task := range tc.tasks {
tu.AssertNoErr(t, tl.AddTask(task.name, task.meta, task.oyafilePath), "Adding task failed")

}
w := mockWriter{}
tl.Print(&w)
tu.AssertObjectsEqual(t, tc.expectedOutput, w.Lines())

})
}
}
29 changes: 4 additions & 25 deletions cmd/internal/tasks.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package internal

import (
"fmt"
"io"
"path/filepath"
"text/tabwriter"

"github.com/tooploox/oya/cmd/internal/printers"
"github.com/tooploox/oya/pkg/project"
"github.com/tooploox/oya/pkg/task"
)
Expand All @@ -28,32 +27,12 @@ func Tasks(workDir string, recurse, changeset bool, stdout, stderr io.Writer) er
return err
}

first := true

printer := printers.NewTaskList(workDir)
err = p.ForEachTask(workDir, recurse, changeset, false, // No built-ins.
func(i int, oyafilePath string, taskName task.Name, task task.Task, meta task.Meta) error {
if i == 0 {
relPath, err := filepath.Rel(workDir, oyafilePath)
if err != nil {
return err
}
if !first {
fmt.Fprintln(w)
} else {
first = false
}

fmt.Fprintf(w, "# in ./%s\n", relPath)
}

if len(meta.Doc) > 0 {
fmt.Fprintf(w, "oya run %s\t# %s\n", taskName, meta.Doc)
} else {
fmt.Fprintf(w, "oya run %s\t\n", taskName)
}

return nil
return printer.AddTask(taskName, meta, oyafilePath)
})
printer.Print(w)
w.Flush()
return err
}
Loading

0 comments on commit 16c60c1

Please sign in to comment.