From c787f3f35fd43b0f21339c77d0ea7178bf78efab Mon Sep 17 00:00:00 2001 From: Joseph Matthias Goh Date: Mon, 1 Apr 2019 23:41:58 +0800 Subject: [PATCH] [#17] Added `--args` for specifying arguments to be passed to the application being live-reloaded (#38) * completes [#17] * added tests --- Makefile | 6 +- README.md | 6 ++ cli_default_handler.go | 6 ++ cli_default_handler_test.go | 1 + config.go | 4 ++ data.go | 6 +- flags.go | 9 +++ flags_test.go | 4 ++ main.go | 111 +++++++++++++++++++----------------- main_test.go | 81 ++++++++++++++++++++------ utils_test.go | 10 ++++ 11 files changed, 166 insertions(+), 78 deletions(-) diff --git a/Makefile b/Makefile index 9293e39..5a97edf 100644 --- a/Makefile +++ b/Makefile @@ -81,13 +81,13 @@ start: fi start.linux: compile.linux @$(MAKE) log.debug MSG="running godev for development..." - @$(CURDIR)/bin/godev--linux-amd64 -vv --watch $(CURDIR) --dir $(CURDIR)/dev ${ARGS} + @$(CURDIR)/bin/godev--linux-amd64 -vv --args 'hi,there' --watch $(CURDIR) --dir $(CURDIR)/dev ${ARGS} start.macos: compile.macos @$(MAKE) log.debug MSG="running godev for development..." - @$(CURDIR)/bin/godev--darwin-amd64 -vv --watch $(CURDIR) --dir $(CURDIR)/dev ${ARGS} + @$(CURDIR)/bin/godev--darwin-amd64 -vv --args 'hi,there' --watch $(CURDIR) --dir $(CURDIR)/dev ${ARGS} start.windows: compile.windows @$(MAKE) log.debug MSG="running godev for development..." - @$(CURDIR)/bin/godev--windows-386.exe -vv --watch $(CURDIR) --dir $(CURDIR)/dev ${ARGS} + @$(CURDIR)/bin/godev--windows-386.exe -vv --args 'hi,there' --watch $(CURDIR) --dir $(CURDIR)/dev ${ARGS} ## installs the dependencies deps: diff --git a/README.md b/README.md index c5c4b04..48b3298 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ By default, GoDev will run for live-reload in development. This results in the d | Flag | Description | | --- | --- | +| [`--args`](#--args) | Specifies arguments to pass into commands of the final execution group (the application being live-reloaded) | | [`--dir`](#--dir) | Specifies the working directory | | [`--env`](#--env) | Specifies an environment variable | | [`--exec`](#--exec) | Specifies comma-delimited commands | @@ -212,6 +213,11 @@ Tells GoDev to keep completely quiet. Only panic level logs are printed before G #### Configuration +##### `--args` +Specifies the arguments to be passed into the last execution group which should contain the path to your binary. + +Default: None + ##### `--dir` Specifies the directory for commands from GoDev to run from. diff --git a/cli_default_handler.go b/cli_default_handler.go index bf99d36..bed054a 100644 --- a/cli_default_handler.go +++ b/cli_default_handler.go @@ -3,12 +3,14 @@ package main import ( "strings" + shellquote "github.com/kballard/go-shellquote" "github.com/urfave/cli" ) func getDefaultFlags() []cli.Flag { return []cli.Flag{ getFlagBuildOutput(), + getFlagCommandArguments(), getFlagCommandsDelimiter(), getFlagEnvVars(), getFlagExecGroups(), @@ -25,8 +27,12 @@ func getDefaultFlags() []cli.Flag { func getDefaultAction(config *Config) cli.ActionFunc { return func(c *cli.Context) error { + var err error config.RunDefault = true config.BuildOutput = c.String("output") + if config.CommandArguments, err = shellquote.Split(c.String("args")); err != nil { + panic(err) + } config.CommandsDelimiter = c.String("exec-delim") config.EnvVars = c.StringSlice("env") config.ExecGroups = c.StringSlice("exec") diff --git a/cli_default_handler_test.go b/cli_default_handler_test.go index cc9a113..62929a9 100644 --- a/cli_default_handler_test.go +++ b/cli_default_handler_test.go @@ -29,6 +29,7 @@ func (s *CLIDefaultHandlerTestSuite) SetupTest() { func (s *CLIDefaultHandlerTestSuite) Test_getDefaultFlags() { ensureCLIFlags(s.T(), []string{ + "args", "dir", "env", "exec-delim", diff --git a/config.go b/config.go index 2c208b5..f4f0bce 100644 --- a/config.go +++ b/config.go @@ -10,6 +10,9 @@ import ( // DefaultBuildOutput - default relative path to watch directory to place built binaries in const DefaultBuildOutput = "bin/app" +// DefaultCommandArguments - default arguments to pass to commands in the last execution group +const DefaultCommandArguments = "" + // DefaultCommandsDelimiter - default string to split --execs into commands with const DefaultCommandsDelimiter = "," @@ -31,6 +34,7 @@ const DefaultRefreshRate = 2 * time.Second // Config configures the main application entrypoint type Config struct { BuildOutput string + CommandArguments ConfigCommaDelimitedString CommandsDelimiter string EnvVars ConfigMultiflagString ExecGroups ConfigMultiflagString diff --git a/data.go b/data.go index 9065b5b..cf46c4d 100644 --- a/data.go +++ b/data.go @@ -4,7 +4,7 @@ // MAKE CHANGES AT ~/app/data/generate AND RUN make generate TO REGENERATE // THE FOLLOWING FILE // -// GENERATED BY GO:GENERATE AT 2019-03-23 17:36:49.028176925 +0000 UTC m=+0.005074006 +// GENERATED BY GO:GENERATE AT 2019-04-01 23:13:50.175213187 +0800 HKT m=+0.029246505 // // FILE GENERATED USING ~/app/data/generate.go @@ -14,7 +14,7 @@ package main const Version = "0.5.40" // Commit is used by godev for reporting the version when installed via 'go get' -const Commit = "48be2b5" +const Commit = "6444d53" // DataDockerfile defines the 'Dockerfile' contents when --init is used // hash:fc3c6491cb0d101ae17e2e68aec4714f @@ -249,7 +249,7 @@ const DataGoDotMod = `module app // MAKE CHANGES AT ~/app/data/generate AND RUN make generate TO REGENERATE // THE FOLLOWING FILE // -// GENERATED BY GO:GENERATE AT 2019-03-23 17:36:49.028176925 +0000 UTC m=+0.005074006 +// GENERATED BY GO:GENERATE AT 2019-04-01 23:13:50.175213187 +0800 HKT m=+0.029246505 // // FILE GENERATED USING ~/app/data/generate.go diff --git a/flags.go b/flags.go index e0638b0..0b861f1 100644 --- a/flags.go +++ b/flags.go @@ -13,6 +13,15 @@ func getFlagBuildOutput() cli.Flag { } } +// getFlagCommandArguments provisions --output +func getFlagCommandArguments() cli.Flag { + return cli.StringFlag{ + Name: "args", + Usage: "| where is a comma delimited string containing arguments to pass to commands in the final execution group", + Value: DefaultCommandArguments, + } +} + // getFlagCommandsDelimiter provisions --exec-delim func getFlagCommandsDelimiter() cli.Flag { return cli.StringFlag{ diff --git a/flags_test.go b/flags_test.go index d5f3e09..6590598 100644 --- a/flags_test.go +++ b/flags_test.go @@ -19,6 +19,10 @@ func (s *FlagsTestSuite) Test_getFlagBuildOutput() { ensureFlag(s.T(), getFlagBuildOutput(), cli.StringFlag{}, `^output.*`) } +func (s *FlagsTestSuite) Test_getFlagCommandArguments() { + ensureFlag(s.T(), getFlagCommandArguments(), cli.StringFlag{}, `^args`) +} + func (s *FlagsTestSuite) Test_getFlagCommandsDelimiter() { ensureFlag(s.T(), getFlagCommandsDelimiter(), cli.StringFlag{}, `^exec-delim.*`) } diff --git a/main.go b/main.go index 1ac5ed1..4c914cd 100644 --- a/main.go +++ b/main.go @@ -46,18 +46,16 @@ type GoDev struct { func (godev *GoDev) Start() { defer godev.logger.Infof("godev has ended") godev.logger.Infof("godev has started") - if godev.config.RunVersion || godev.config.RunView { - // do nothing + if godev.config.RunDefault || godev.config.RunTest { + godev.startWatching() } else if godev.config.RunInit { godev.initialiseDirectory() - } else { - godev.startWatching() } } func (godev *GoDev) createPipeline() []*ExecutionGroup { var pipeline []*ExecutionGroup - for _, execGroup := range godev.config.ExecGroups { + for execGroupIndex, execGroup := range godev.config.ExecGroups { executionGroup := &ExecutionGroup{} var executionCommands []*Command commands := strings.Split(execGroup, godev.config.CommandsDelimiter) @@ -65,11 +63,15 @@ func (godev *GoDev) createPipeline() []*ExecutionGroup { if sections, err := shellquote.Split(command); err != nil { panic(err) } else { + arguments := sections[1:] + if execGroupIndex == len(godev.config.ExecGroups)-1 { + arguments = append(arguments, godev.config.CommandArguments...) + } executionCommands = append( executionCommands, InitCommand(&CommandConfig{ Application: sections[0], - Arguments: sections[1:], + Arguments: arguments, Directory: godev.config.WorkDirectory, Environment: godev.config.EnvVars, LogLevel: godev.config.LogLevel, @@ -91,53 +93,6 @@ func (godev *GoDev) eventHandler(events *[]WatcherEvent) bool { return true } -func (godev *GoDev) startWatching() { - godev.logUniversalConfigurations() - godev.logWatchModeConfigurations() - godev.initialiseWatcher() - godev.initialiseRunner() - - var wg sync.WaitGroup - godev.watcher.BeginWatch(&wg, godev.eventHandler) - godev.logger.Infof("working dir : '%s'", godev.config.WorkDirectory) - godev.logger.Infof("watching dir: '%s'", godev.config.WatchDirectory) - godev.runner.Trigger() - wg.Wait() -} - -func (godev *GoDev) logUniversalConfigurations() { - godev.logger.Debugf("flag - init : %v", godev.config.RunInit) - godev.logger.Debugf("flag - test : %v", godev.config.RunTest) - godev.logger.Debugf("flag - view : %v", godev.config.RunView) - godev.logger.Debugf("watch directory : %s", godev.config.WatchDirectory) - godev.logger.Debugf("work directory : %s", godev.config.WorkDirectory) - godev.logger.Debugf("build output : %s", godev.config.BuildOutput) -} - -func (godev *GoDev) logWatchModeConfigurations() { - config := godev.config - logger := godev.logger - logger.Debugf("environment : %v", config.EnvVars) - logger.Debugf("file extensions : %v", config.FileExtensions) - logger.Debugf("ignored names : %v", config.IgnoredNames) - logger.Debugf("refresh interval : %v", config.Rate) - logger.Debugf("execution delim : %s", config.CommandsDelimiter) - logger.Debug("execution groups as follows...") - for egIndex, execGroup := range config.ExecGroups { - logger.Debugf(" %v) %s", egIndex+1, execGroup) - commands := strings.Split(execGroup, config.CommandsDelimiter) - for cIndex, command := range commands { - sections, err := shellquote.Split(command) - if err != nil { - panic(err) - } - app := sections[0] - args := sections[1:] - logger.Debugf(" %v > %s %v", cIndex+1, app, args) - } - } -} - func (godev *GoDev) initialiseInitialisers() []Initialiser { return []Initialiser{ InitGitInitialiser(&GitInitialiserConfig{ @@ -218,3 +173,53 @@ func (godev *GoDev) initialiseWatcher() { }) godev.watcher.RecursivelyWatch(godev.config.WatchDirectory) } + +func (godev *GoDev) logUniversalConfigurations() { + godev.logger.Debugf("flag - init : %v", godev.config.RunInit) + godev.logger.Debugf("flag - test : %v", godev.config.RunTest) + godev.logger.Debugf("flag - view : %v", godev.config.RunView) + godev.logger.Debugf("watch directory : %s", godev.config.WatchDirectory) + godev.logger.Debugf("work directory : %s", godev.config.WorkDirectory) + godev.logger.Debugf("build output : %s", godev.config.BuildOutput) +} + +func (godev *GoDev) logWatchModeConfigurations() { + config := godev.config + logger := godev.logger + logger.Debugf("environment : %v", config.EnvVars) + logger.Debugf("file extensions : %v", config.FileExtensions) + logger.Debugf("ignored names : %v", config.IgnoredNames) + logger.Debugf("refresh interval : %v", config.Rate) + logger.Debugf("execution delim : %s", config.CommandsDelimiter) + logger.Debug("execution groups as follows...") + for execGroupIndex, execGroup := range config.ExecGroups { + logger.Debugf(" %v) %s", execGroupIndex+1, execGroup) + commands := strings.Split(execGroup, config.CommandsDelimiter) + for commandIndex, command := range commands { + sections, err := shellquote.Split(command) + if err != nil { + panic(err) + } + application := sections[0] + arguments := sections[1:] + if execGroupIndex == len(config.ExecGroups)-1 { + arguments = append(arguments, config.CommandArguments...) + } + logger.Debugf(" %v > %s %v", commandIndex+1, application, arguments) + } + } +} + +func (godev *GoDev) startWatching() { + godev.logUniversalConfigurations() + godev.logWatchModeConfigurations() + godev.initialiseWatcher() + godev.initialiseRunner() + + var wg sync.WaitGroup + godev.watcher.BeginWatch(&wg, godev.eventHandler) + godev.logger.Infof("working dir : '%s'", godev.config.WorkDirectory) + godev.logger.Infof("watching dir: '%s'", godev.config.WatchDirectory) + godev.runner.Trigger() + wg.Wait() +} diff --git a/main_test.go b/main_test.go index de684fd..a695364 100644 --- a/main_test.go +++ b/main_test.go @@ -22,6 +22,7 @@ func TestMainTestSuite(t *testing.T) { func (s *MainTestSuite) SetupTest() { s.godev = InitGoDev(&Config{ + CommandArguments: []string{"test", "arg"}, CommandsDelimiter: ",", ExecGroups: []string{ "echo 'a b' c,echo 'd e',echo f", @@ -74,40 +75,65 @@ func (s *MainTestSuite) Test_createPipeline_separatesCommandArgsCorrectly() { assert.Equal(t, "2", pipeline[1].commands[1].config.Arguments[0]) assert.Equal(t, "3", pipeline[1].commands[1].config.Arguments[1]) // echo '' - assert.Len(t, pipeline[2].commands[0].config.Arguments, 1) + assert.Len(t, pipeline[2].commands[0].config.Arguments, 3) assert.Equal(t, "", pipeline[2].commands[0].config.Arguments[0]) + assert.Equal(t, "test", pipeline[2].commands[0].config.Arguments[1]) + assert.Equal(t, "arg", pipeline[2].commands[0].config.Arguments[2]) +} + +func (s *MainTestSuite) Test_eventHandler() { + t := s.T() + // set exec groups to none so that no pipeline triggers + s.godev.config.ExecGroups = []string{} + s.godev.initialiseRunner() + s.godev.eventHandler(&[]WatcherEvent{ + WatcherEvent{Op: 1}, + WatcherEvent{Op: 2}, + WatcherEvent{Op: 4}, + WatcherEvent{Op: 8}, + WatcherEvent{Op: 16}, + }) + logs := s.logs.String() + assert.Contains(t, logs, "CREATE") + assert.Contains(t, logs, "WRITE") + assert.Contains(t, logs, "REMOVE") + assert.Contains(t, logs, "RENAME") + assert.Contains(t, logs, "CHMOD") } func (s *MainTestSuite) Test_initialiseInitialisers() { + t := s.T() initialisers := s.godev.initialiseInitialisers() - assert.Len(s.T(), initialisers, 7) + assert.Len(t, initialisers, 7) var keys []string for _, initialiser := range initialisers { keys = append(keys, initialiser.GetKey()) } - assert.Contains(s.T(), keys, ".git") - assert.Contains(s.T(), keys, ".gitignore") - assert.Contains(s.T(), keys, ".dockerignore") - assert.Contains(s.T(), keys, "dockerfile") - assert.Contains(s.T(), keys, "makefile") - assert.Contains(s.T(), keys, "main.go") - assert.Contains(s.T(), keys, "go.mod") + assert.Contains(t, keys, ".git") + assert.Contains(t, keys, ".gitignore") + assert.Contains(t, keys, ".dockerignore") + assert.Contains(t, keys, "dockerfile") + assert.Contains(t, keys, "makefile") + assert.Contains(t, keys, "main.go") + assert.Contains(t, keys, "go.mod") } func (s *MainTestSuite) Test_initialiseRunner() { - assert.Nil(s.T(), s.godev.runner) + t := s.T() + assert.Nil(t, s.godev.runner) s.godev.initialiseRunner() - assert.NotNil(s.T(), s.godev.runner) + assert.NotNil(t, s.godev.runner) } func (s *MainTestSuite) Test_initialiseWatcher() { + t := s.T() s.godev.config.FileExtensions = []string{"a", "b", "c"} s.godev.config.IgnoredNames = []string{".cache", ".git", "bin", "vendor"} s.godev.config.Rate = time.Second * 2 s.godev.config.WatchDirectory = getCurrentWorkingDirectory() - assert.Nil(s.T(), s.godev.watcher) + assert.Nil(t, s.godev.watcher) s.godev.initialiseWatcher() - assert.NotNil(s.T(), s.godev.watcher) + assert.NotNil(t, s.godev.watcher) } func (s *MainTestSuite) Test_initialiseWatcher_withInvalidWatchDirectory() { @@ -132,12 +158,29 @@ func (s *MainTestSuite) Test_initialiseWatcher_withInvalidWatchDirectory() { } func (s *MainTestSuite) Test_logUniversalConfiguration() { + t := s.T() s.godev.logUniversalConfigurations() logs := s.logs.String() - assert.Contains(s.T(), logs, "flag - init") - assert.Contains(s.T(), logs, "flag - test") - assert.Contains(s.T(), logs, "flag - view") - assert.Contains(s.T(), logs, "watch directory") - assert.Contains(s.T(), logs, "work directory") - assert.Contains(s.T(), logs, "build output") + assert.Contains(t, logs, "flag - init") + assert.Contains(t, logs, "flag - test") + assert.Contains(t, logs, "flag - view") + assert.Contains(t, logs, "watch directory") + assert.Contains(t, logs, "work directory") + assert.Contains(t, logs, "build output") +} + +func (s *MainTestSuite) Test_logWatchModeConfigurations() { + t := s.T() + s.godev.logWatchModeConfigurations() + logs := s.logs.String() + assert.Contains(t, logs, "environment") + assert.Contains(t, logs, "file extensions") + assert.Contains(t, logs, "ignored names") + assert.Contains(t, logs, "refresh interval") + assert.Contains(t, logs, "execution delim") + assert.Contains(t, logs, "execution groups") + assert.Contains(t, logs, "1) echo 'a b' c,echo 'd e',echo f") + assert.Contains(t, logs, "2) echo 1,echo 2 3") + assert.Contains(t, logs, "3) echo ''") + assert.Contains(t, logs, "test arg") } diff --git a/utils_test.go b/utils_test.go index b1d50d2..78ea758 100644 --- a/utils_test.go +++ b/utils_test.go @@ -84,3 +84,13 @@ func (s *UtilsTestSuite) Test_fileExists() { func (s *UtilsTestSuite) Test_fileExists_onDirectory() { assert.False(s.T(), fileExists(getCurrentWorkingDirectory())) } + +func (s *UtilsTestSuite) Test_sliceContainsString() { + testSlice := []string{"a", "b", "c"} + // test the happy path + assert.True(s.T(), sliceContainsString(testSlice, "a")) + // test the sad path + assert.False(s.T(), sliceContainsString(testSlice, "d")) + // test if duplicates might affect searching + assert.False(s.T(), sliceContainsString(testSlice, "aa")) +}