diff --git a/README.md b/README.md index b30499b..ebd4cd4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # alfred + Because even Batman needs a little help. [![Build Status](https://travis-ci.org/kcmerrill/alfred.svg?branch=master)](https://travis-ci.org/kcmerrill/alfred) [![Join the chat at https://gitter.im/kcmerrill/alfred](https://badges.gitter.im/kcmerrill/alfred.svg)](https://gitter.im/kcmerrill/alfred?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) @@ -6,9 +7,11 @@ Because even Batman needs a little help. ![Alfred](https://raw.githubusercontent.com/kcmerrill/alfred/master/assets/alfred.jpg "Alfred") ## What is it + A simple go/yaml powered make file/task runner with a bit of a twist. ## Binaries || Installation + [![MacOSX](https://raw.githubusercontent.com/kcmerrill/go-dist/master/assets/apple_logo.png "Mac OSX")] (http://go-dist.kcmerrill.com/kcmerrill/alfred/mac/amd64) [![Linux](https://raw.githubusercontent.com/kcmerrill/go-dist/master/assets/linux_logo.png "Linux")] (http://go-dist.kcmerrill.com/kcmerrill/alfred/linux/amd64) via go: @@ -20,19 +23,22 @@ via docker: `$ docker run -v $PWD:$PWD -w $PWD kcmerrill/alfred` ## Features + - Extendable. Common tasks(Private too) - Watch files for modifications -- Retry/Rerun tasks based on failures before giving up +- Retry/Rerun tasks based on failures before giving up - Logging - Success/Failure decision tree -- Run tasks asynchronously or synchronously +- Run tasks asynchronously or synchronously - Autocomplete task names - Static webserver -- Many more! +- Many more! ## Usage + Create a file named: `alfred.yml` -``` + +```yaml say.hello: summary: I will say hello! usage: alfred say.hello @@ -65,24 +71,30 @@ Then, anywhere in the top-level or child directories to the `alfred.yml` file: `alfred blurt` will perform both tasks at the same time -# Quick Walkthrough -[![asciicast](https://asciinema.org/a/103711.png)](https://asciinema.org/a/103711) +## Quick Walkthrough + +To see a comprehensive list of features and functionality please [RTFM](RTFM.md "additional documentation") -[For additional documentation, please refer to the GUIDE](GUIDE.md "additional documentation") +[![asciicast](https://asciinema.org/a/103711.png)](https://asciinema.org/a/103711) ## Example uses -* Monitor websites -* Setup/Update/Deploy projects in your dev env -* Simple Nagios, Jenkins, pingdom replacement -* Monitor crons(alert on failures, update endpoints etc ... ) -* Watch for file modifications to run tests->builds +- Monitor websites +- Setup/Update/Deploy projects in your dev env +- Simple Nagios, Jenkins, pingdom replacement +- Monitor crons(alert on failures, update endpoints etc ... ) +- Watch for file modifications to run tests->builds ## Tab completion -Copy the included `alfred.completion.sh` to `/etc/bash_completion.d/`, or source it in your `~/.profile` file. +Copy the included `alfred.completion.sh` to `/etc/bash_completion.d/`. + +Or source it in your `~/.profile` file. + +Or `alfred /self tab.completion` ## Testing + You might say I've cheated the testing route by only scraping the output. You'd be right. -"I live with a wizard. I cheat" ~ Mouse +"I live with a wizard. I cheat" ~ Mouse \ No newline at end of file diff --git a/GUIDE.md b/RTFM.md similarity index 100% rename from GUIDE.md rename to RTFM.md diff --git a/alfred.yml b/alfred.yml index 65986f7..a284200 100644 --- a/alfred.yml +++ b/alfred.yml @@ -26,6 +26,7 @@ tdd: test: summary: Testing ... command: | + go install go test -v commit: diff --git a/alfred/alfred.go b/alfred/alfred.go index a970d63..4bd44c9 100644 --- a/alfred/alfred.go +++ b/alfred/alfred.go @@ -17,7 +17,6 @@ import ( "github.com/fatih/color" "github.com/kcmerrill/alfred/remote" - "github.com/kcmerrill/alfred/task" "gopkg.in/yaml.v2" ) @@ -32,7 +31,7 @@ type Alfred struct { // Variables Vars map[string]string `yaml:"alfred.vars"` // All of the tasks parsed from the yaml file - Tasks map[string]*task.Task `yaml:",inline"` + Tasks map[string]*Task `yaml:",inline"` // Alfred remotes(private/public repos) remote *remote.Remote // Originating directory @@ -56,6 +55,8 @@ func New(args []string) { // Grab the current directory and save if off a.dir, _ = os.Getwd() + fmt.Println(args) + // Set our Arguments a.args = args @@ -69,7 +70,7 @@ func New(args []string) { if !a.findTask() { a.args = append(a.args[:1], append([]string{"default"}, a.args[1:]...)...) if !a.findTask() { - say("ERROR", "Invalid task.") + say("ERROR", "Invalid taskkkkkkk.") os.Exit(1) } } @@ -117,7 +118,7 @@ func (a *Alfred) findTask() bool { return false } } else { - say(a.args[2], "invalid task.") + say(a.args[2], "Invalid taskk.") return false } break @@ -129,15 +130,15 @@ func (a *Alfred) findTask() bool { func (a *Alfred) runTask(task string, args []string, formatted bool) bool { // Verify again it's a valid task if !a.isValidTask(task) { - say(task, "Invalid task.") + say(task, "Invalid task."+task+"--") return false } // Infinite loop Used for the every command for { // Run our setup tasks - for _, s := range a.Tasks[task].SetupTasks() { - if !a.runTask(s, args, formatted) { + for _, taskDefinition := range a.Tasks[task].TaskGroup(a.Tasks[task].Setup, args) { + if !a.runTask(taskDefinition.Name, taskDefinition.Params, formatted) { break } } @@ -177,7 +178,7 @@ func (a *Alfred) runTask(task string, args []string, formatted bool) bool { m, _ := regexp.Match(a.Tasks[task].Watch, []byte(path)) if m { // If not a match ... - return errors.New("") + return errors.New("no matches") } } return nil @@ -248,8 +249,8 @@ func (a *Alfred) runTask(task string, args []string, formatted bool) bool { fmt.Println(red("✘"), task) // Failed? Lets run the failed tasks - for _, failed := range a.Tasks[task].FailedTasks() { - if !a.runTask(failed, args, formatted) { + for _, taskDefinition := range a.Tasks[task].TaskGroup(a.Tasks[task].Fail, args) { + if !a.runTask(taskDefinition.Name, taskDefinition.Params, formatted) { break } } @@ -274,26 +275,26 @@ func (a *Alfred) runTask(task string, args []string, formatted bool) bool { var wg sync.WaitGroup // Do we have any tasks we need to run in parallel? - for _, t := range a.Tasks[task].MultiTask() { + for _, taskDefinition := range a.Tasks[task].TaskGroup(a.Tasks[task].Multitask, args) { wg.Add(1) go func(t string, args []string) { defer wg.Done() a.runTask(t, args, true) - }(t, args) + }(taskDefinition.Name, taskDefinition.Params) } wg.Wait() // Ok, we made it here ... Is this task a task group? - for _, t := range a.Tasks[task].TaskGroup() { - if !a.runTask(t, args, formatted) { + for _, taskDefinition := range a.Tasks[task].TaskGroup(a.Tasks[task].Tasks, args) { + if !a.runTask(taskDefinition.Name, taskDefinition.Params, formatted) { break } } // Woot! Lets run the ok tasks if taskok { - for _, okTasks := range a.Tasks[task].OkTasks() { - if !a.runTask(okTasks, args, formatted) { + for _, taskDefinition := range a.Tasks[task].TaskGroup(a.Tasks[task].Ok, args) { + if !a.runTask(taskDefinition.Name, taskDefinition.Params, formatted) { break } } diff --git a/task/task.go b/alfred/task.go similarity index 66% rename from task/task.go rename to alfred/task.go index b6af390..8d6e435 100644 --- a/task/task.go +++ b/alfred/task.go @@ -1,4 +1,4 @@ -package task +package alfred import ( "bufio" @@ -44,6 +44,8 @@ A brief explination: - Setup: Similiar to tasks but these get run _before_ the command/task group gets called - Serve: A string, denoting port number to serve a static webserver */ + +// Task contains a task definition and all of it's components type Task struct { Summary string Test string @@ -76,6 +78,12 @@ type Task struct { Serve string } +// TaskDefinition defines the name and params of a task when originally in string format +type TaskDefinition struct { + Name string + Params []string +} + // IsPrivate returns true/false if the task is private(not executable individually) func (t *Task) IsPrivate() bool { return t.Private @@ -96,24 +104,49 @@ func (t *Task) Aliases() []string { return strings.Fields(t.Alias) } -func (t *Task) FailedTasks() []string { - return strings.Fields(t.Fail) -} - -func (t *Task) OkTasks() []string { - return strings.Fields(t.Ok) -} - -func (t *Task) TaskGroup() []string { - return strings.Fields(t.Tasks) -} - -func (t *Task) MultiTask() []string { - return strings.Fields(t.Multitask) -} +//TaskGroup takes in a string(bleh(1234) whatever(bleh, woot)) and returns the values and args +func (t *Task) TaskGroup(tasks string, args []string) []TaskDefinition { + results := make([]TaskDefinition, 0) + if tasks == "" { + // If there is nothing, then there is nothing to report + return results + } + if strings.Index(tasks, "(") == -1 { + // This means we have a regular space delimited list + tasks := strings.Split(tasks, " ") + for _, task := range tasks { + results = append(results, TaskDefinition{Name: task, Params: args}) + } + } else { + // This means we have a group of tasks() + // Not going to do a ton of error checking. + // Don't be a sad panda and forget to add () to _EVERY_ task! + definitions := strings.Split(tasks, ")") + for _, task := range definitions { + // Clean up the task in case + task = strings.TrimSpace(task) + if task == "" { + // Empty task? Continue ... + continue + } + // Now, lets separate the task from the params + if len(strings.Split(task, "(")) == 1 { + // No args + results = append(results, TaskDefinition{Name: strings.TrimSpace(strings.Split(task, "(")[0]), Params: args}) + } else { + taskName := strings.TrimSpace(strings.Split(task, "(")[0]) + p := strings.TrimSpace(strings.Split(task, "(")[1]) + params := strings.Split(p, ",") + for idx, param := range params { + // lets clean up our params + params[idx] = strings.TrimSpace(param) + } + results = append(results, TaskDefinition{Name: taskName, Params: params}) + } + } + } -func (t *Task) SetupTasks() []string { - return strings.Fields(t.Setup) + return results } // RunCommand runs a command, also determining if it needs to be formated(multitasks for example) @@ -134,18 +167,18 @@ func (t *Task) RunCommand(cmd, name string, formatted bool) bool { return true } -//CommandComplex takes in a command and will write it to a file, or special formatting(multitask for example) +// CommandComplex takes in a command and will write it to a file, or special formatting(multitask for example) func (t *Task) CommandComplex(cmd, name string) bool { if cmd != "" { var l *os.File var err error - /* If log is set ... lets use it */ + // If log is set ... lets use it if t.Log != "" { l, err = os.OpenFile(t.Log, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) if err != nil { - /* Don't quit ... but don't log either */ + // Don't quit ... but don't log either t.Log = "" } defer l.Close() @@ -198,11 +231,11 @@ func (t *Task) CommandComplex(cmd, name string) bool { return false } } - /* If there was no command to run, then don't fail the task */ + // If there was no command to run, then don't fail the task return true } -/* Execute a task ... */ +// CommandBasic runs a basic command, no frills func (t *Task) CommandBasic(cmd string) bool { if cmd != "" { cmd := exec.Command("bash", "-c", cmd) @@ -210,39 +243,30 @@ func (t *Task) CommandBasic(cmd string) bool { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if cmd.Run() == nil { - /* Was it succesful? */ + // Was it succesful? return true - } else { - return false } + return false } - /* If there was no command to run, then don't fail the task */ + // If there was no command to run, then don't fail the task return true } -/* Test - Currently lets just run a command, BUT, in the future I'd love to see: - file.exists "filename" - dir.exists "dirname" - dir.age 15m - file.age 15m - etc etc .. -*/ +// TestF runs a command with _NO_ output! func (t *Task) TestF(tst string) bool { if tst != "" { cmd := exec.Command("bash", "-c", tst) if cmd.Run() == nil { /* Was it succesful? */ return true - } else { - return false } + return false } /* If there was no command to run, then don't fail the test */ return true } -/* Evaluate */ +// Eval runs a string to see if it's a command or not(depending on it's exit code) func (t *Task) Eval(cmd string) string { out, err := exec.Command(cmd).Output() if err != nil { @@ -259,12 +283,12 @@ func (t *Task) Prepare(args []string, vars map[string]string) bool { t.Vars = make(map[string]string, 0) } - /* override variable defaults with actual vars */ + // override variable defaults with actual vars for key, value := range vars { t.Vars[key] = t.Eval(value) } - /* override defaults with the args */ + // override defaults with the args for index, value := range args { if len(t.Args) > index { t.Args[index] = value @@ -273,7 +297,7 @@ func (t *Task) Prepare(args []string, vars map[string]string) bool { } } - /* Any null values? If so, bail ... */ + // Any null values? If so, bail ... for _, value := range t.Args { if value == "" { return false @@ -292,65 +316,63 @@ func (t *Task) Prepare(args []string, vars map[string]string) bool { // Setup time t.Time = time.Now() - /* All of the modules */ + // All of the modules for key, value := range t.Modules { - if module_ok, module_translated := t.template(value); module_ok { - t.Modules[key] = module_translated + if moduleOk, moduleTranslated := t.template(value); moduleOk { + t.Modules[key] = moduleTranslated } else { return false } } - /* get to translating */ - if every_ok, every_translated := t.template(t.Every); every_ok { - t.Every = every_translated + // get to translating + if everyOk, everyTranslated := t.template(t.Every); everyOk { + t.Every = everyTranslated } else { return false } - if allargs_ok, allargs_translated := t.template(strings.Join(args, " ")); allargs_ok { - t.AllArgs = allargs_translated + if allargsOk, allargsTranslated := t.template(strings.Join(args, " ")); allargsOk { + t.AllArgs = allargsTranslated } else { return false } - if cmd_ok, cmd_translated := t.template(t.Command); cmd_ok { - t.Command = cmd_translated + if cmdOk, cmdTranslated := t.template(t.Command); cmdOk { + t.Command = cmdTranslated } else { return false } - if cmd_ok, cmd_translated := t.template(t.Commands); cmd_ok { - t.Commands = cmd_translated + if cmdOk, cmdTranslated := t.template(t.Commands); cmdOk { + t.Commands = cmdTranslated } else { return false } - if dir_ok, dir_translated := t.template(t.Dir); dir_ok { - t.Dir = dir_translated + if dirOk, dirTranslated := t.template(t.Dir); dirOk { + t.Dir = dirTranslated } else { return false } - if tst_ok, tst_translated := t.template(t.Test); tst_ok { - t.Test = tst_translated + if tstOk, tstTranslated := t.template(t.Test); tstOk { + t.Test = tstTranslated } else { return false } - /* if we made it here, then we are good to go */ + // if we made it here, then we are good to go return true } -/* Translate a string to a template */ +// template a helper function to translate a string to a template func (t *Task) template(translate string) (bool, string) { template := template.Must(template.New("translate").Parse(translate)) b := new(bytes.Buffer) err := template.Execute(b, t) if err == nil { return true, b.String() - } else { - return false, translate } - return true, translate + return false, translate } diff --git a/alfred_test.go b/alfred_test.go index df2d539..1b51751 100644 --- a/alfred_test.go +++ b/alfred_test.go @@ -32,6 +32,7 @@ func run(cmd string, t *testing.T) (string, error) { } else { return strings.Trim(string(out), "\n"), err } + } func TestCurrentDirectory(t *testing.T) { @@ -254,7 +255,7 @@ func TestArgumentsOkAndDefaults(t *testing.T) { t.FailNow() } /* Verfify our command had run succesfully */ - if !strings.Contains(sut, "alfred.yml") { + if !strings.Contains(sut, ".alfred") { t.Logf("Listing of the current directory should show an alfred.yml file") t.FailNow() } diff --git a/examples/demo-everything/alfred.yml b/examples/demo-everything/.alfred/part-one.alfred.yml similarity index 100% rename from examples/demo-everything/alfred.yml rename to examples/demo-everything/.alfred/part-one.alfred.yml diff --git a/examples/demo-everything/.alfred/part-two.alfred.yml b/examples/demo-everything/.alfred/part-two.alfred.yml new file mode 100644 index 0000000..c086dcd --- /dev/null +++ b/examples/demo-everything/.alfred/part-two.alfred.yml @@ -0,0 +1,7 @@ +thirty.eight: + tasks: fourty(kc, merrill) fourty(clark, kent ) fourty( bruce , wayne ) + +fourty: + summary: Takes two args, tests the () functionality + command: | + echo -{{ index .Args 0 }}-{{ index .Args 1 }}- diff --git a/modules/self/alfred.yml b/modules/self/alfred.yml index aebbf1f..fae484e 100644 --- a/modules/self/alfred.yml +++ b/modules/self/alfred.yml @@ -42,7 +42,9 @@ tab.completion: summary: Install tab completion test: which wget commands: | - mkdir -p /etc/bash_completion.d/ + echo "Need to request sudo access to run:" + echo "mkdir -p /etc/bash_completion.d" + sudo mkdir -p /etc/bash_completion.d/ cd /etc/bash_completion.d/ rm -rf alfred.completion.sh wget https://raw.githubusercontent.com/kcmerrill/alfred/master/alfred.completion.sh