Skip to content

Commit

Permalink
Fixes error message and formats command line output (#139)
Browse files Browse the repository at this point in the history
  • Loading branch information
akhilbojedla authored and gopinath-langote committed Sep 17, 2019
1 parent 5544687 commit c9de5a7
Show file tree
Hide file tree
Showing 9 changed files with 533 additions and 94 deletions.
108 changes: 51 additions & 57 deletions cmd/exec/exec.go
Original file line number Diff line number Diff line change
@@ -1,69 +1,44 @@
package exec

import (
"errors"
"fmt"

"github.com/codeskyblue/go-sh"
sh "github.com/codeskyblue/go-sh"
"github.com/gopinath-langote/1build/cmd/config"
"github.com/gopinath-langote/1build/cmd/models"
"github.com/gopinath-langote/1build/cmd/utils"
"github.com/logrusorgru/aurora"
)

// ExecuteCommands executes given command with before and after is present
func ExecuteCommands(commands ...string) {
// ExecutePlan executes the Execution plan
func ExecutePlan(commands ...string) {

configuration, err := config.LoadOneBuildConfiguration()
if err != nil {
utils.PrintErr(err)
return
}
beforeCmd := configuration.Before
afterCmd := configuration.After

cmdMap, err := createMapOfCommandsToExecute(configuration, commands...)
if err != nil {
return
}

printMessage(cmdMap, beforeCmd, afterCmd)
executionPlan := buildExecutionPlan(configuration, commands...)
utils.PrintExecutionPlan(executionPlan)

if beforeCmd != "" {
err := executeAndStopIfFailed(beforeCmd)
if err != nil {
utils.Println("\nFailed to execute '" + beforeCmd + "'\n")
return
}
}
for _, v := range cmdMap {
err = executeAndStopIfFailed(v)
if err != nil {
utils.Println("\nFailed to execute '" + v + "'\n")
return
}
if executionPlan.HasBefore() {
executeAndStopIfFailed(executionPlan.Before)
}

if afterCmd != "" {
err = executeAndStopIfFailed(afterCmd)
if err != nil {
utils.Println("\nFailed to execute '" + afterCmd + "'\n")
return
if executionPlan.HasCommands() {
for _, commandContext := range executionPlan.Commands {
executeAndStopIfFailed(commandContext)
}
}
}

func printMessage(m map[string]string, beforeCmd string, afterCmd string) {
var message = utils.DASH() + "\n"

if beforeCmd != "" {
message = message + "Before: " + beforeCmd + "\n\n"
}
for k, v := range m {
message = message + k + " : " + v + "\n"
if executionPlan.HasAfter() {
executeAndStopIfFailed(executionPlan.After)
}

if afterCmd != "" {
message = message + "\nAfter: " + afterCmd + "\n"
}
message = message + utils.DASH()
utils.Println(message)
fmt.Println()
fmt.Println(aurora.BrightGreen("SUCCESS").Bold())

}

func getCommandFromName(config config.OneBuildConfiguration, cmd string) string {
Expand All @@ -77,22 +52,41 @@ func getCommandFromName(config config.OneBuildConfiguration, cmd string) string
return ""
}

func createMapOfCommandsToExecute(oneBuildConfiguration config.OneBuildConfiguration, commands ...string) (map[string]string, error) {
var cmdMap map[string]string
cmdMap = make(map[string]string)
func bashCommand(s *sh.Session, command string) *sh.Session {
return s.Command("bash", "-c", command)
}

func executeAndStopIfFailed(command *models.CommandContext) {
err := command.CommandSession.Run()
if err != nil {
exitCode := (err.Error())[12:]
utils.PrintlnDashedErr("Execution failed during phase \"" + command.Name + "\" - Execution of the script \"" + command.Command + "\" returned non-zero exit code : " + exitCode)
utils.ExitWithCode(exitCode)
}
}

func buildExecutionPlan(onebuildConfig config.OneBuildConfiguration, commands ...string) models.OneBuildExecutionPlan {

before := onebuildConfig.Before
var executionPlan models.OneBuildExecutionPlan
if before != "" {
executionPlan.Before = &models.CommandContext{"before", before, bashCommand(sh.NewSession(), before)}
}

for _, name := range commands {
value := getCommandFromName(oneBuildConfiguration, name)
if value == "" {
utils.Println("No command '" + name + "' found in config file\n")
config.PrintConfiguration(oneBuildConfiguration)
return nil, errors.New("")
executionCommand := getCommandFromName(onebuildConfig, name)
if executionCommand == "" {
utils.PrintlnErr("Error building exectuion plan. Command \"" + name + "\" not found.")
config.PrintConfiguration(onebuildConfig)
utils.Exit(127)
}
cmdMap[name] = value
executionPlan.Commands = append(executionPlan.Commands, &models.CommandContext{name, executionCommand, bashCommand(sh.NewSession(), executionCommand)})
}

after := onebuildConfig.After
if after != "" {
executionPlan.After = &models.CommandContext{"after", after, bashCommand(sh.NewSession(), after)}
}
return cmdMap, nil
}

func executeAndStopIfFailed(command string) error {
return sh.NewSession().Command("bash", "-c", command).Run()
return executionPlan
}
29 changes: 29 additions & 0 deletions cmd/models/onebuild-execution-plan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package models

import sh "github.com/codeskyblue/go-sh"

// OneBuildExecutionPlan holds all information for the execution strategy
type OneBuildExecutionPlan struct {
Before *CommandContext
Commands []*CommandContext
After *CommandContext
}

// CommandContext holds all meta-data and required information for execution of a command
type CommandContext struct {
Name string
Command string
CommandSession *sh.Session
}

func (executionPlan *OneBuildExecutionPlan) HasBefore() bool {
return executionPlan.Before != nil
}

func (executionPlan *OneBuildExecutionPlan) HasAfter() bool {
return executionPlan.After != nil
}

func (executionPlan *OneBuildExecutionPlan) HasCommands() bool {
return len(executionPlan.Commands) > 0
}
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var rootCmd = &cobra.Command{
if len(args) < 1 {
listCmd.Run(cmd, args)
} else {
exec.ExecuteCommands(args...)
exec.ExecutePlan(args...)
}
},
}
Expand Down
104 changes: 103 additions & 1 deletion cmd/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,29 @@ package utils
import (
"fmt"
"os"
"strconv"
"text/tabwriter"

"github.com/gopinath-langote/1build/cmd/models"
"github.com/logrusorgru/aurora"
rune "github.com/mattn/go-runewidth"
)

// DASH return dashes with fixed lenght
func DASH() string {
return "--------------------------------------------------"
}

// DashesMatchingTextLength returns a string of '-' matching the length of provided stirng
func DashesMatchingTextLength(text string) string {
width := rune.StringWidth(text)
dashString := ""
for i := 1; i <= width; i++ {
dashString = dashString + "-"
}
return dashString
}

// Println prints text on the console
func Println(text string) {
fmt.Println(text)
Expand All @@ -20,9 +36,34 @@ func PrintErr(err error) {
fmt.Println(err)
}

// PrintlnErr prints error line to console in bold Red
func PrintlnErr(text string) {
fmt.Println(aurora.Red("\n" + text).Bold())
}

// PrintlnDashedErr prints error line to console in bold Red with dashes above and below
func PrintlnDashedErr(text string) {
errDash := DashesMatchingTextLength(text)
fmt.Println()
fmt.Println(errDash)
fmt.Println(aurora.Red(text).Bold())
fmt.Println(errDash)
}

// Exit exits the current process with specified exit code
func Exit(code int) {
os.Exit(code)
}

// ExitWithCode exits the current process with specified exit code provided as stirng
func ExitWithCode(code string) {
exitCode, _ := strconv.Atoi(code)
os.Exit(exitCode)
}

// ExitError exit the program with non success code
func ExitError() {
os.Exit(1)
Exit(1)
}

// SliceIndex find the index of element matching given predicate
Expand All @@ -34,3 +75,64 @@ func SliceIndex(limit int, predicate func(i int) bool) int {
}
return -1
}

// PrintExecutionPlan prints a formatted executoin plan to console
func PrintExecutionPlan(executionPlan models.OneBuildExecutionPlan) {
fmt.Println()
fmt.Println(aurora.BrightGreen("Execution plan (executed in ordered sequence)").Bold().Underline())
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', tabwriter.TabIndent)

maxPhaseName := "Phase"
maxCommand := "Command"

if executionPlan.HasBefore() {
maxPhaseName = executionPlan.Before.Name
maxCommand = executionPlan.Before.Command
}

if executionPlan.HasCommands() {
for _, command := range executionPlan.Commands {
if len(command.Name) > len(maxPhaseName) {
maxPhaseName = command.Name
}
if len(command.Command) > len(maxCommand) {
maxCommand = command.Command
}
}
}

if executionPlan.HasAfter() {
command := executionPlan.After
if len(command.Name) > len(maxPhaseName) {
maxPhaseName = command.Name
}
if len(command.Command) > len(maxCommand) {
maxCommand = command.Command
}
}

phaseDashes := DashesMatchingTextLength(maxPhaseName)
commandDashes := DashesMatchingTextLength(maxCommand)

fmt.Fprintf(w, "%s\t%s\n", phaseDashes, commandDashes)
fmt.Fprintln(w, "Phase\tCommand")
fmt.Fprintf(w, "%s\t%s\n", phaseDashes, commandDashes)

if executionPlan.HasBefore() {
fmt.Fprintln(w, fmt.Sprintf("%s\t%s", executionPlan.Before.Name, executionPlan.Before.Command))
}

if executionPlan.HasCommands() {
for _, command := range executionPlan.Commands {
fmt.Fprintln(w, fmt.Sprintf("%s\t%s", command.Name, command.Command))
}
}

if executionPlan.HasAfter() {
fmt.Fprintln(w, fmt.Sprintf("%s\t%s", executionPlan.After.Name, executionPlan.After.Command))
}

w.Flush()
fmt.Println()
fmt.Println()
}
2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Join Slack Chat` button to the Readme file.
- Github action - workflow for CI/CD
### Changed
- Prints well formatted execution plan at start of the execution

### Deprecated

### Removed

### Fixed
- Fixes the failure message to be more relavant and includes exit-code

### Security

Expand Down
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@ require (
github.com/codeskyblue/go-sh v0.0.0-20170112005953-b097669b1569
github.com/davecgh/go-spew v1.1.1
github.com/inconshreveable/mousetrap v1.0.0
github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.9 // indirect
github.com/mattn/go-runewidth v0.0.4
github.com/pmezard/go-difflib v1.0.0
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.3
github.com/stretchr/objx v0.2.0 // indirect
github.com/stretchr/testify v1.4.0
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 // indirect
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 // indirect
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/tools v0.0.0-20190821162956-65e3620a7ae7 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22
)
Loading

0 comments on commit c9de5a7

Please sign in to comment.