-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract & improve task list formatting (
oya tasks
).
- Loading branch information
Showing
5 changed files
with
315 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
|
||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.