goauto

package module
v0.0.0-...-6d5fb71 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 29, 2015 License: MIT Imports: 10 Imported by: 1

README

GoAuto GoDoc

"What makes you so ashamed of being a grownup?" - The War Doctor

Overview

Task automation for grownups. GoAuto is a package that makes building a native executable tailored to a specific work flow, simple.

Here is a complete example of a Go build and test process triggered by any source file in a project changing.

NOTE If you are using OS X consider turning on the experimental OSX flag for Pipelines to avoid the "To many Open Files" error.

package main

import (
	"path/filepath"

	"github.com/dshills/goauto"
	"github.com/dshills/goauto/gotask"
)

func main() {
	// Create a pipeline (Develop using Verbose, Change to Silent after testing)
	p := goauto.NewPipeline("Go Pipeline", goauto.Verbose)
	defer p.Stop()

	// Watch directories recursively, ignoring hidden directories
	wd := filepath.Join("src", "github.com", "me", "myproject")
	if err := p.WatchRecursive(wd, goauto.IgnoreHidden); err != nil {
		panic(err)
	}

	// Create a workflow
	wf := goauto.NewWorkflow(
					gotask.NewGoVetTask(), 
					gotask.NewGoLintTask(), 
					gotask.NewGoTestTask(),
					gotask.NewGoInstallTask())

	// Add a file pattern to match
	if err := wf.WatchPattern(".*\\.go$"); err != nil {
		panic(err)
	}

	// Add workflow to pipeline
	p.Add(wf)

	// start the pipeline, it will block
	p.Start()
}

Features

  • No config files
  • Built in support for Go projects
  • No new syntax just Go
  • No external dependencies
  • Highly customizable
  • Fast
  • Small
  • Tool for building tools

Building a general purpose build tool with GoAuto that used config files would be a fairly trivial project. Feel free if that is your thing.

Installation

go get github.com/dshills/goauto

Concepts

Pipelines

A Pipeline monitors one or more file system directories for changes. When it detects a change it asks each Workflow if the specific file is a match and if it is launches the Workflow. Output and Error io can be set for a Pipeline. If not specified it will use StdIn and StdErr. One or more Pipelines can be declared. Running them concurrently is a choice left to the developer.

GoAuto follows the go tools convention of no news is good news. It will silently watch for file changes, launch workflows and tasks without any output other than the output from the task itself. If running a task like a go tool that has the same philosophy, no output will be generated at all. When first writing a set of tasks this can be a little disconcerting. Did it run? Did it work?

goauto.Verbose
goauto.Silent

Verbose will print debug information about what events are being received by a Pipeline, starting a workflow, and tasks being run. At some point an option may be added to just show specific output. When writing your own tasks you always have the choice of outputting whatever you wish.

Watches can be absolute or $GOPATH relative.

// Create a pipeline
p := goauto.NewPipeline("Go Pipeline", goauto.Verbose)

// watch directories recursively, ignoring hidden directories
wd := filepath.Join("src", "github.com", "myprojects")
if err := p.WatchRecursive(wd, goauto.IgnoreHidden); err != nil {
	panic(err)
}

Watch directories can be added with Watch to add a single directory. The absolute path of the added path will be returned.

func (p *Pipeline) Watch(watchDir string) (string, error)

To add directories recursively use WatchRecursive optionally ignoring hidden directories

func (p *Pipeline) WatchRecursive(watchDir string, ignoreHidden bool) error 

Adding Workflows are added using Add

func (p *Pipeline) Add(ws ...Workflower)

After adding Workflows and Tasks to your pipeline simply tell the Pipeline to Start watching. The Pipeline will block. To stop a running Pipeline call Stop.

p.Start()

Or

go p.Start()

	do stuff

p.Stop()

UPDATE Pipeline now includes an experimental flag OSX. If you are using OS X and have received the "To many open files" warning this is an attempt to fix it. The watcher code has now been extracted into it's own interface and can use the experimental OSX events package https://github.com/go-fsnotify/fsevents. I have done heavy testing locally with no problems but your mileage may vary. This should have no affect on the current usage of GoAuto.

Workflows

Workflows run a set of tasks for files matching a regular expression pattern. Workflows only really need to know two things, what files to process and what tasks to perform. Workflow implements the Workflower interface.

Here we create a Workflow with a number of built in tasks. We then add a pattern to match using WatchPattern.

// Create a workflow with some tasks
wf := goauto.NewWorkflow(goauto.NewGoVetTask(), goauto.NewGoTestTask(), goauto.NewGoLintTask(), goauto.NewGoInstallTask())

// Add a regex pattern to match
err := wf.WatchPattern(".*\\.go$")

Tasks can also be added using Add

func (wf *Workflow) Add(tasks ...Tasker)

Workflows run tasks sequentially, passing the TaskInfo struct (See Tasks below) to each task on the way. Before a task is run the TaskInfo.Src is updated to the TaskInfo.Target of the previous task if it was set. TaskInfo.Src is set to the matching file name for the first task.

Any task that returns an error will stop the Workflow. If you want the Workflow to continue even if an error occurs make sure to handle the error and not return it.

Advanced Options
// A Workflow represents a set of tasks for files matching one or more regex patterns
type Workflow struct {
	Name       string
	Concurrent bool
	Op         Op
	Regexs     []*regexp.Regexp
	Tasks      []Tasker
}

Setting Concurrent to true will run a Workflow concurrently. This should be used with caution. If multiple Workflows work with the same set of files there is a potential for confusion and even data loss.

Op = goauto.Create | goauto.Write | goauto.Remove | goauto.Rename | goauto.Chmod

By default a Workflow will check file match for Create, Write, Remove, and Rename. This can be controlled by setting the Op value.

The Workflow struct implements the Workflower interface. Most use cases will have no need for anything more than a Workflow, however, Pipelines will accept anything that implements the Workflower interface. An example might be a new Workflower that implemented the WatchPattern using glob syntax rather than a regex.

Tasks

Tasks are generally small, atomic pieces of work. Run tests, compile, copy a file, etc. They are what makeup a Workflow. A task implements the Tasker interface

Task Builtins

GoAuto includes a number of pre built tasks that can be used directly.

goauto/gotask
  • NewGoPrjTask will run a go command with arguments
  • NewGoTestTask will run tests for a project
  • NewGoVetTask will run vet for a project
  • NewGoBuildTask will run build for a project
  • NewGoInstallTask will run install for a project
  • NewGoLintTask will run golint for a project
  • NewGoMetaLinter task for github.com/alecthomas/gometalinter
goauto/shelltask
  • NewShellTask task that runs a shell command with arguments
  • NewCatTask task that cats a file
  • NewRemoveTask task that deleted a file
  • NewMoveTask task that moves a file
  • NewMkdirTask task that makes a new directory
  • NewCopyTask task that copies a file
  • NewRestartTask task that will restart an executable file such as a Go server or Web server
goauto/webtask
  • NewSassTask task that runs sass command line utility with options
Task Generators

The built in tasks are a great way to get started with GoAuto. They do many useful things and serve as guides for building your own tasks. GoAuto also includes generator functions that will help you build your own simple tasks. NewTask, NewShellTask and NewGoPrjTask are examples of generic task generators.

NewTask is the most generic of the generators and can be used for building your own task. NewTask usage will be covered in Task Building

NewShellTask will return a new Tasker that will call a shell function with arguments on the target file.

func NewShellTask(cmd string, args ...string) Tasker

st := shelltask.NewShellTask("echo", "-n") // a Task that will echo the source file name

NewGoPrjTask will produce a new Tasker that will call the go command in the directory of target file

func NewGoPrjTask(gocmd string, args ...string) Tasker 

gt := gotask.NewGoPrjTask('build') // A fancy new task that will build a project
TaskInfo

Before diving into task building we need to introduce the TaskInfo struct. TaskInfo is passed between tasks as they run.

// A TaskInfo contains the results of running a Task
type TaskInfo struct {
	Src        string       // Incoming file name for a task to process
	Target     string       // Output file name after running a task
	Buf        bytes.Buffer // Output of running a task
	Tout, Terr io.Writer    // Writers to write output and errors
	Collect    []string     // List of file names processed by a Workflow
	Verbose    bool         // output debug info
}

Your tasks are expected to update Target and Buf and to use Tout and Terr for output. For example a task that renames a file would set Target equal to the new file name. If your task has output useful to another task then reset the Buf and write it. User messages or error text can be written to Tout and Terr.

As the Workflow executes each task the Src will be set to the Target of the last run task. For the first task in a Workflow Src is set to the filename matched by the Workflow.

By using Buf and Target a Workflow creates a flow similar to a using a Unix pipe

Collect keeps a running list of file targets over the course of one run of a Workflow. This gives tasks access to run functions on all the files processed by the Workflow.

Task Building

The real power comes from building custom tasks. This can be done using the NewTask generator or by writing a Tasker compliant interface. Here are examples of both for calling the cat shell command.

func NewTask(t Transformer, r Runner) Tasker 

A Transformer is the function used to convert the incoming file name to something new. A number of Transformers are built in.

func(string)string
Built In Transformers
  • Identity function that returns the string passed in
  • GoRelBase function that returns the file path relative to GOPATH
  • GoRelDir function that returns the directory path relative to GOPATH
  • GoRelSrcDir function that returns the directory path relative to GOPATH/src
  • ExtTransformer function that returns a Transformer function that returns file path with a new extension

A runner is the function called to run your task and is in the form

func foo(i *TaskInfo)error

So lets write a task that cats a file. A cat task is already included but it is a simple example. We are using the Identity Transformer which just returns the file name passed to it.

func myCat(i *goauto.TaskInfo) (err error) {
	cmd := exec.Command("cat", i.Target)
	i.Buf.Reset()
	cmd.Stdout = &i.Buf // Write the output to the buffer
	cmd.Stderr = i.Terr // use Terr as stderr
	defer func() {
		i.Tout.Write(i.Buf.Bytes()) // Write the buffer to Tout
	}()
	return cmd.Run()
}

t := goauto.NewTask(goauto.Identity, myCat)
// Identity is a built in Transformer that returns what was passed to it
// We could have written goauto.NewTask(func(f string)string {return f}, myCat)

Here it is written as a Tasker. In this case we don't need a Transformer because we are controlling the entire task from start to finish. In this simple example it is actually shorter to make our own Tasker

type myCatTask struct{}
func (t *myCatTask)Run(i *goauto.TaskInfo) (err error) {
	i.Target = i.Src // Not changing the file name so not technically required but a good habit
	cmd := exec.Command("cat", i.Target)
	i.Buf.Reset()
	cmd.Stdout = &i.Buf // Write the output to the buffer
	cmd.Stderr = i.Terr // use Terr as stderr
	defer func() {
		i.Tout.Write(i.Buf.Bytes()) // Write the buffer to Tout
	}()
	return cmd.Run()
}

To Do

  • More built ins for Web development LESS, Reload (Certainly can be done now but it would be nice to have built ins)
  • Test large, concurrent, multi Pipeline, multi Workflow systems

Alternatives

License

Copyright 2015 Davin Hills. All rights reserved. MIT license. License details can be found in the LICENSE file.

Documentation

Overview

Package goauto implements a set of tools for building workflow automation tools. These tools can be as simple as running a compiler when a source file changes to complex chains of tasks doing almost any action required within a development environment See README.md for more details on usage

Index

Constants

View Source
const (
	IgnoreHidden  = true
	IncludeHidden = false
)

Flags to WatchRecursive to include or ignore hidden directories

View Source
const (
	Verbose = true
	Silent  = false
)

Flags for verbose output

Variables

This section is empty.

Functions

func AbsPath

func AbsPath(path string) (ap string, err error)

AbsPath is a utility function to get the absolute path from a path It will first check for an absolute path then GOPATH relative then a pwd relative will return an error for a path that does not exist

func GoPaths

func GoPaths() []string

GoPaths retreives the GOPATH env and splits it []string

func GoRelBase

func GoRelBase(f string) string

GoRelBase returns the file path relative to $GOPATH

func GoRelDir

func GoRelDir(f string) string

GoRelDir returns the path relative to $GOPATH

func GoRelSrcDir

func GoRelSrcDir(f string) string

GoRelSrcDir returns the path relative to $GOPATH/src

func Identity

func Identity(f string) string

Identity returns itself

func IsHidden

func IsHidden(d string) bool

IsHidden is a HACKY check for hidden directory name

Types

type ESlice

type ESlice []*Event

ESlice is an Event buffer

type Event

type Event struct {
	Path string
	Op   Op
}

Event represents a file system notification

type Op

type Op uint32

Op describes a set of file operations. Mimics fsnotify

const (
	Create Op = 1 << iota
	Write
	Remove
	Rename
	Chmod
)

These are the generalized file operations that can trigger a notification. Mimics fsnotify

type Pipeline

type Pipeline struct {
	Name       string
	Watches    []string
	Wout, Werr io.Writer
	Workflows  []Workflower
	Verbose    bool
	OSX        bool
	// contains filtered or unexported fields
}

A Pipeline watches one or more directories for changes

func NewPipeline

func NewPipeline(name string, verbose bool) *Pipeline

NewPipeline returns a basic Pipeline with a dir to watch, output and error writers and a workflow

func (*Pipeline) Add

func (p *Pipeline) Add(ws ...Workflower)

Add adds one or more Workflows to the pipeline

func (*Pipeline) Start

func (p *Pipeline) Start()

Start begins watching for changes to files in the Watches directories Detected file changes will be compared with workflow regexp and if match will run the workflow tasks

func (*Pipeline) Stop

func (p *Pipeline) Stop() (err error)

Stop will discontinue watching for file changes

func (*Pipeline) Watch

func (p *Pipeline) Watch(watchDir string) (d string, err error)

Watch adds a GOPATH relative or absolute path to watch rejects invalid paths and ignores duplicates

func (*Pipeline) WatchRecursive

func (p *Pipeline) WatchRecursive(watchDir string, ignoreHidden bool) error

WatchRecursive adds a GOPATH relative or absolute path to watch recursivly

type Runner

type Runner func(*TaskInfo) error

A Runner represents the function needed to satisfy a Tasker interface

type TaskInfo

type TaskInfo struct {
	Src        string       // Incoming file name for a task to process
	Target     string       // Output file name after running a task
	Buf        bytes.Buffer // Output of running a task
	Tout, Terr io.Writer    // Writers to write output and errors
	Collect    []string     // List of file names processed by a Workflow
	Verbose    bool         // output debug info
}

A TaskInfo contains the results of running a Task

type Tasker

type Tasker interface {
	Run(info *TaskInfo) (err error)
}

A Tasker represents an implementation of a task It provides the capability to Run the task given information from the previous task including the Target (file targeted) and Buf (output) in TaskInfo The run function should update the TaskInfo.Target to reflect what the Task worked on It should also clear and fill the Buf if the task had output If run returns an error the workflow will stop If the workflow should continue, handle the error internally including logging to Terr and return nil

func NewEmptyTask

func NewEmptyTask() Tasker

NewEmptyTask returns a Task that does nothing. Can be useful for testing

func NewTask

func NewTask(t Transformer, r Runner) Tasker

NewTask returns a Task that will, when executed from a Workflow, transform Target with Transform(TaskInfo.Target) and execute RunFunc

type Transformer

type Transformer func(string) string

A Transformer is a function that changes a string to a string Transforms are used to build Targets in Tasks A common example would be to change myfile.scss to myfile.css

func ExtTransformer

func ExtTransformer(newExt string) Transformer

ExtTransformer returns a Transformer for chaning file extensions

type Watcher

type Watcher interface {
	SetVerbose(out io.Writer)
	Start(latency time.Duration, paths []string) (<-chan ESlice, error)
	Stop() error
	Add(path string) error
	Remove(path string) error
}

A Watcher represents a gneric type of file system monitor

func NewWatchFS

func NewWatchFS() Watcher

NewWatchFS creates a new filesystem watcher

func NewWatchOSX

func NewWatchOSX() Watcher

NewWatchOSX returns a standard WatchFS, if OS is not OSX

type Workflow

type Workflow struct {
	Name       string
	Concurrent bool
	Op         Op
	Regexs     []*regexp.Regexp
	Tasks      []Tasker
}

A Workflow represents a set of tasks for files matching one or more regex patterns

func NewWorkflow

func NewWorkflow(tasks ...Tasker) *Workflow

NewWorkflow returns a Workflow with tasks

func (*Workflow) Add

func (wf *Workflow) Add(tasks ...Tasker)

Add adds a task to the workflow

func (*Workflow) Match

func (wf *Workflow) Match(fpath string, op Op) bool

Match checks a file name against the regexp of the Workflow and the file operation

func (*Workflow) Run

func (wf *Workflow) Run(info *TaskInfo)

Run will start the execution of tasks

func (*Workflow) WatchOp

func (wf *Workflow) WatchOp(op Op)

WatchOp sets the file operations to match The default is Create | Write | Remove | Rename

func (*Workflow) WatchPattern

func (wf *Workflow) WatchPattern(patterns ...string) error

WatchPattern adds one or more regex for matching files for this workflow An invalid regexp pattern will return an error Sets file operations to Create|Write|Remove|Rename if not set

type Workflower

type Workflower interface {
	WatchPattern(patterns ...string) error
	WatchOp(op Op)
	Add(tasks ...Tasker)
	Match(fpath string, op Op) bool
	Run(*TaskInfo)
}

A Workflower represents a workflow that executes a list of Taskers

Directories

Path Synopsis
Package gotask implements tasks for running Go specific tools
Package gotask implements tasks for running Go specific tools
Package shelltask implements a set of tasks to interact with the shell.
Package shelltask implements a set of tasks to interact with the shell.
Package webtask implements tasks specific to web development
Package webtask implements tasks specific to web development

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL