1
0
mirror of https://github.com/go-task/task.git synced 2025-02-03 13:22:11 +02:00

refactor: Create executor struct to get rid of global variables

Maybe eventually help on #17
This commit is contained in:
Andrey Nering 2017-06-04 16:02:04 -03:00
parent c40148a52e
commit f98bf6c4b1
9 changed files with 137 additions and 109 deletions

View File

@ -2,6 +2,7 @@ package main
import (
"fmt"
"log"
"github.com/go-task/task"
@ -9,6 +10,8 @@ import (
)
func main() {
log.SetFlags(0)
pflag.Usage = func() {
fmt.Println(`task [target1 target2 ...]: Runs commands under targets like make.
@ -25,9 +28,40 @@ hello:
`)
pflag.PrintDefaults()
}
pflag.BoolVarP(&task.Init, "init", "i", false, "creates a new Taskfile.yml in the current folder")
pflag.BoolVarP(&task.Force, "force", "f", false, "forces execution even when the task is up-to-date")
pflag.BoolVarP(&task.Watch, "watch", "w", false, "enables watch of the given task")
var (
init bool
force bool
watch bool
)
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yml in the current folder")
pflag.BoolVarP(&force, "force", "f", false, "forces execution even when the task is up-to-date")
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
pflag.Parse()
task.Run()
if init {
if err := task.InitTaskfile(); err != nil {
log.Fatal(err)
}
return
}
e := task.Executor{
Force: force,
Watch: watch,
}
if err := e.ReadTaskfile(); err != nil {
log.Fatal(err)
}
args := pflag.Args()
if len(args) == 0 {
log.Println("task: No argument given, trying default task")
args = []string{"default"}
}
if err := e.Run(args...); err != nil {
log.Fatal(err)
}
}

View File

@ -1,8 +1,8 @@
package task
// HasCyclicDep checks if a task tree has any cyclic dependency
func HasCyclicDep(m map[string]*Task) bool {
visits := make(map[string]struct{}, len(m))
func (e *Executor) HasCyclicDep() bool {
visits := make(map[string]struct{}, len(e.Tasks))
var checkCyclicDep func(string, *Task) bool
checkCyclicDep = func(name string, t *Task) bool {
@ -13,14 +13,14 @@ func HasCyclicDep(m map[string]*Task) bool {
defer delete(visits, name)
for _, d := range t.Deps {
if !checkCyclicDep(d, m[d]) {
if !checkCyclicDep(d, e.Tasks[d]) {
return false
}
}
return true
}
for k, v := range m {
for k, v := range e.Tasks {
if !checkCyclicDep(k, v) {
return true
}

View File

@ -7,30 +7,34 @@ import (
)
func TestCyclicDepCheck(t *testing.T) {
isCyclic := map[string]*task.Task{
"task-a": &task.Task{
Deps: []string{"task-b"},
},
"task-b": &task.Task{
Deps: []string{"task-a"},
isCyclic := &task.Executor{
Tasks: map[string]*task.Task{
"task-a": &task.Task{
Deps: []string{"task-b"},
},
"task-b": &task.Task{
Deps: []string{"task-a"},
},
},
}
if !task.HasCyclicDep(isCyclic) {
if !isCyclic.HasCyclicDep() {
t.Error("Task should be cyclic")
}
isNotCyclic := map[string]*task.Task{
"task-a": &task.Task{
Deps: []string{"task-c"},
isNotCyclic := &task.Executor{
Tasks: map[string]*task.Task{
"task-a": &task.Task{
Deps: []string{"task-c"},
},
"task-b": &task.Task{
Deps: []string{"task-c"},
},
"task-c": &task.Task{},
},
"task-b": &task.Task{
Deps: []string{"task-c"},
},
"task-c": &task.Task{},
}
if task.HasCyclicDep(isNotCyclic) {
if isNotCyclic.HasCyclicDep() {
t.Error("Task should not be cyclic")
}
}

View File

@ -1,9 +1,17 @@
package task
import (
"errors"
"fmt"
)
var (
// ErrCyclicDependencyDetected is returned when a cyclic dependency was found in the Taskfile
ErrCyclicDependencyDetected = errors.New("task: cyclic dependency detected")
// ErrTaskfileAlreadyExists is returned on creating a Taskfile if one already exists
ErrTaskfileAlreadyExists = errors.New("task: A Taskfile already exists")
)
type taskFileNotFound struct {
taskFile string
}

10
help.go
View File

@ -7,8 +7,8 @@ import (
"text/tabwriter"
)
func printExistingTasksHelp() {
tasks := tasksWithDesc()
func (e *Executor) printExistingTasksHelp() {
tasks := e.tasksWithDesc()
if len(tasks) == 0 {
return
}
@ -17,13 +17,13 @@ func printExistingTasksHelp() {
// Format in tab-separated columns with a tab stop of 8.
w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
for _, task := range tasks {
fmt.Fprintln(w, fmt.Sprintf("- %s:\t%s", task, Tasks[task].Desc))
fmt.Fprintln(w, fmt.Sprintf("- %s:\t%s", task, e.Tasks[task].Desc))
}
w.Flush()
}
func tasksWithDesc() (tasks []string) {
for name, task := range Tasks {
func (e *Executor) tasksWithDesc() (tasks []string) {
for name, task := range e.Tasks {
if task.Desc != "" {
tasks = append(tasks, name)
}

10
init.go
View File

@ -13,17 +13,17 @@ default:
- echo "Hello, World!"
`
func initTaskfile() {
// InitTaskfile Taskfile creates a new Taskfile
func InitTaskfile() error {
for _, f := range []string{"Taskfile.yml", "Taskfile.toml", "Taskfile.json"} {
if _, err := os.Stat(f); err == nil {
log.Printf("A Taskfile already exists")
os.Exit(1)
return
return ErrTaskfileAlreadyExists
}
}
if err := ioutil.WriteFile("Taskfile.yml", []byte(defaultTaskfile), 0666); err != nil {
log.Fatalf("%v", err)
return err
}
log.Printf("Taskfile.yml created in the current directory")
return nil
}

View File

@ -11,27 +11,30 @@ import (
"gopkg.in/yaml.v2"
)
func readTaskfile() (map[string]*Task, error) {
initialTasks, err := readTaskfileData(TaskFilePath)
// ReadTaskfile parses Taskfile from the disk
func (e *Executor) ReadTaskfile() error {
var err error
e.Tasks, err = e.readTaskfileData(TaskFilePath)
if err != nil {
return nil, err
return err
}
mergeTasks, err := readTaskfileData(fmt.Sprintf("%s_%s", TaskFilePath, runtime.GOOS))
osTasks, err := e.readTaskfileData(fmt.Sprintf("%s_%s", TaskFilePath, runtime.GOOS))
if err != nil {
switch err.(type) {
default:
return nil, err
case taskFileNotFound:
return initialTasks, nil
return nil
default:
return err
}
}
if err := mergo.MapWithOverwrite(&initialTasks, mergeTasks); err != nil {
return nil, err
if err := mergo.MapWithOverwrite(&e.Tasks, osTasks); err != nil {
return err
}
return initialTasks, nil
return nil
}
func readTaskfileData(path string) (tasks map[string]*Task, err error) {
func (e *Executor) readTaskfileData(path string) (tasks map[string]*Task, err error) {
if b, err := ioutil.ReadFile(path + ".yml"); err == nil {
return tasks, yaml.Unmarshal(b, &tasks)
}

85
task.go
View File

@ -10,24 +10,22 @@ import (
"github.com/go-task/task/execext"
"github.com/spf13/pflag"
"golang.org/x/sync/errgroup"
)
var (
const (
// TaskFilePath is the default Taskfile
TaskFilePath = "Taskfile"
)
// Init (--init or -f flag) creates a Taskfile.yml in the current folder if not exists
Init bool
// Force (--force or -f flag) forces a task to run even when it's up-to-date
// Executor executes a Taskfile
type Executor struct {
Tasks map[string]*Task
Force bool
// Watch (--watch or -w flag) enables watch of a task
Watch bool
// Tasks constains the tasks parsed from Taskfile
Tasks = make(map[string]*Task)
)
watchingFiles map[string]struct{}
}
// Task represents a task
type Task struct {
@ -44,67 +42,47 @@ type Task struct {
}
// Run runs Task
func Run() {
log.SetFlags(0)
args := pflag.Args()
if Init {
initTaskfile()
return
}
if len(args) == 0 {
log.Println("task: No argument given, trying default task")
args = []string{"default"}
}
var err error
Tasks, err = readTaskfile()
if err != nil {
log.Fatal(err)
}
if HasCyclicDep(Tasks) {
log.Fatal("Cyclic dependency detected")
func (e *Executor) Run(args ...string) error {
if e.HasCyclicDep() {
return ErrCyclicDependencyDetected
}
// check if given tasks exist
for _, a := range args {
if _, ok := Tasks[a]; !ok {
var err error = &taskNotFoundError{taskName: a}
fmt.Println(err)
printExistingTasksHelp()
return
if _, ok := e.Tasks[a]; !ok {
// FIXME: move to the main package
e.printExistingTasksHelp()
return &taskNotFoundError{taskName: a}
}
}
if Watch {
if err := WatchTasks(args); err != nil {
log.Fatal(err)
if e.Watch {
if err := e.watchTasks(args...); err != nil {
return err
}
return
return nil
}
for _, a := range args {
if err = RunTask(context.Background(), a); err != nil {
log.Fatal(err)
if err := e.RunTask(context.Background(), a); err != nil {
return err
}
}
return nil
}
// RunTask runs a task by its name
func RunTask(ctx context.Context, name string) error {
t, ok := Tasks[name]
func (e *Executor) RunTask(ctx context.Context, name string) error {
t, ok := e.Tasks[name]
if !ok {
return &taskNotFoundError{name}
}
if err := t.runDeps(ctx); err != nil {
if err := e.runDeps(ctx, name); err != nil {
return err
}
if !Force {
if !e.Force {
upToDate, err := t.isUpToDate(ctx)
if err != nil {
return err
@ -116,15 +94,16 @@ func RunTask(ctx context.Context, name string) error {
}
for i := range t.Cmds {
if err := t.runCommand(ctx, i); err != nil {
if err := e.runCommand(ctx, name, i); err != nil {
return &taskRunError{name, err}
}
}
return nil
}
func (t *Task) runDeps(ctx context.Context) error {
func (e *Executor) runDeps(ctx context.Context, task string) error {
g, ctx := errgroup.WithContext(ctx)
t := e.Tasks[task]
for _, d := range t.Deps {
dep := d
@ -135,7 +114,7 @@ func (t *Task) runDeps(ctx context.Context) error {
return err
}
if err = RunTask(ctx, dep); err != nil {
if err = e.RunTask(ctx, dep); err != nil {
return err
}
return nil
@ -195,7 +174,9 @@ func (t *Task) isUpToDate(ctx context.Context) (bool, error) {
return generatesMinTime.After(sourcesMaxTime), nil
}
func (t *Task) runCommand(ctx context.Context, i int) error {
func (e *Executor) runCommand(ctx context.Context, task string, i int) error {
t := e.Tasks[task]
c, err := t.ReplaceVariables(t.Cmds[i])
if err != nil {
return err
@ -203,7 +184,7 @@ func (t *Task) runCommand(ctx context.Context, i int) error {
if strings.HasPrefix(c, "^") {
c = strings.TrimPrefix(c, "^")
if err = RunTask(ctx, c); err != nil {
if err = e.RunTask(ctx, c); err != nil {
return err
}
return nil

View File

@ -11,13 +11,13 @@ import (
"github.com/mattn/go-zglob"
)
// WatchTasks start watching the given tasks
func WatchTasks(args []string) error {
// watchTasks start watching the given tasks
func (e *Executor) watchTasks(args ...string) error {
log.Printf("task: Started watching for tasks: %s", strings.Join(args, ", "))
// run tasks on init
for _, a := range args {
if err := RunTask(context.Background(), a); err != nil {
if err := e.RunTask(context.Background(), a); err != nil {
fmt.Println(err)
break
}
@ -31,7 +31,7 @@ func WatchTasks(args []string) error {
go func() {
for {
if err := registerWatchedFiles(watcher, args); err != nil {
if err := e.registerWatchedFiles(watcher, args); err != nil {
log.Printf("Error watching files: %v", err)
}
time.Sleep(time.Second * 2)
@ -43,7 +43,7 @@ loop:
select {
case <-watcher.Events:
for _, a := range args {
if err := RunTask(context.Background(), a); err != nil {
if err := e.RunTask(context.Background(), a); err != nil {
fmt.Println(err)
continue loop
}
@ -55,11 +55,9 @@ loop:
}
}
var watchingFiles map[string]struct{}
func registerWatchedFiles(w *fsnotify.Watcher, args []string) error {
oldWatchingFiles := watchingFiles
watchingFiles = make(map[string]struct{}, len(oldWatchingFiles))
func (e *Executor) registerWatchedFiles(w *fsnotify.Watcher, args []string) error {
oldWatchingFiles := e.watchingFiles
e.watchingFiles = make(map[string]struct{}, len(oldWatchingFiles))
for k := range oldWatchingFiles {
if err := w.Remove(k); err != nil {
@ -68,11 +66,11 @@ func registerWatchedFiles(w *fsnotify.Watcher, args []string) error {
}
for _, a := range args {
task, ok := Tasks[a]
task, ok := e.Tasks[a]
if !ok {
return &taskNotFoundError{a}
}
if err := registerWatchedFiles(w, task.Deps); err != nil {
if err := e.registerWatchedFiles(w, task.Deps); err != nil {
return err
}
for _, s := range task.Sources {
@ -84,7 +82,7 @@ func registerWatchedFiles(w *fsnotify.Watcher, args []string) error {
if err := w.Add(f); err != nil {
return err
}
watchingFiles[f] = struct{}{}
e.watchingFiles[f] = struct{}{}
// run if is new file
if oldWatchingFiles != nil {