-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Complete tool with working config example.
Signed-off-by: Vaibhav <[email protected]>
- Loading branch information
Showing
183 changed files
with
45,738 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Leaf configuration file. | ||
|
||
# Root directory to watch. | ||
# Defaults to current working directory. | ||
root: "." | ||
|
||
# Exclude directories while watching. | ||
# If certain directories are not excluded, it might reach a limitation where watcher doesn't start. | ||
exclude: | ||
- ".git/" | ||
- "vendor/" | ||
- "build/" | ||
|
||
# Filters to apply on the watch. | ||
# Filters starting with '+' are includent and then with '-' are excluded. | ||
# This is not like exclude, these are still being watched yet can be excluded from the execution. | ||
# These can include any filepath regex supported by "filepath".Match method or even a directory. | ||
filters: | ||
- "- .git*" | ||
- "- .go*" | ||
- "- .golangci.yml" | ||
- "- go.*" | ||
- "- Makefile" | ||
- "- LICENSE" | ||
- "- README.md" | ||
|
||
# Commands to be executed. | ||
# These are run in the provided order. | ||
exec: | ||
- ["make", "format"] | ||
- ["make", "build"] | ||
|
||
# Delay after which commands are executed. | ||
delay: '1s' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,57 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/sirupsen/logrus" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/vrongmeal/leaf/pkg/engine" | ||
"github.com/vrongmeal/leaf/pkg/utils" | ||
|
||
prefixed "github.com/x-cray/logrus-prefixed-formatter" | ||
) | ||
|
||
var ( | ||
confPath string | ||
|
||
rootCmd = &cobra.Command{ | ||
Use: "leaf", | ||
Short: "General purpose hot-reloader for all projects", | ||
Long: `Given a set of commands, leaf watches the filtered paths in the project directory for any changes and runs the commands in | ||
order so you don't have to yourself`, | ||
|
||
Run: func(*cobra.Command, []string) { | ||
conf, err := utils.GetConfig(confPath) | ||
if err != nil { | ||
logrus.Fatalf("Error getting config: %s", err.Error()) | ||
} | ||
|
||
isdir, err := utils.IsDir(conf.Root) | ||
if err != nil || !isdir { | ||
conf.Root = utils.CWD | ||
} | ||
|
||
logrus.Infof("Starting to watch: %s", conf.Root) | ||
logrus.Infoln("Excluded paths:") | ||
for i, e := range conf.Exclude { | ||
logrus.Infof("%d. %s", i, e) | ||
} | ||
|
||
if err := engine.Start(&conf); err != nil { | ||
logrus.Fatalf("Cannot start leaf: %s", err.Error()) | ||
} | ||
}, | ||
} | ||
) | ||
|
||
func init() { | ||
logrus.SetLevel(logrus.TraceLevel) | ||
logrus.SetFormatter(new(prefixed.TextFormatter)) | ||
|
||
rootCmd.PersistentFlags().StringVarP(&confPath, "config", "c", utils.DefaultConfPath, "Config path for leaf configuration file") | ||
} | ||
|
||
func main() { | ||
println("Hello, World!") | ||
if err := rootCmd.Execute(); err != nil { | ||
logrus.Fatalf("Cannot start leaf: %s", err.Error()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package commander | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"sync" | ||
"syscall" | ||
|
||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
type pipeLogger struct{} | ||
|
||
func (pl pipeLogger) Write(p []byte) (int, error) { | ||
fmt.Print(string(p)) | ||
return len(p), nil | ||
} | ||
|
||
// Commander is a type with multiple commands and runs them in order. | ||
type Commander struct { | ||
index int | ||
cmds [][]string | ||
cmd *exec.Cmd | ||
kill chan bool | ||
wg *sync.WaitGroup | ||
} | ||
|
||
// NewCommander returns a Commander with given commands. | ||
func NewCommander(cmds [][]string) *Commander { | ||
return &Commander{ | ||
cmds: cmds, | ||
index: 0, | ||
kill: make(chan bool), | ||
wg: &sync.WaitGroup{}, | ||
} | ||
} | ||
|
||
func newCmd(cmd []string) (*exec.Cmd, error) { | ||
if len(cmd) == 0 { | ||
return nil, fmt.Errorf("command cannot be empty") | ||
} | ||
|
||
var c *exec.Cmd | ||
|
||
if len(cmd) == 1 { | ||
c = exec.Command(cmd[0]) // nolint:gosec | ||
} else { | ||
name := cmd[0] | ||
args := cmd[1:] | ||
c = exec.Command(name, args...) // nolint:gosec | ||
} | ||
|
||
c.Stdout = os.Stdout | ||
c.Stderr = os.Stderr | ||
c.Stdin = os.Stdin | ||
c.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} | ||
|
||
return c, nil | ||
} | ||
|
||
// Run executes the commands in order. | ||
func (c *Commander) Run() error { | ||
c.wg.Add(1) | ||
defer c.wg.Done() | ||
|
||
var err error | ||
|
||
for _, command := range c.cmds { | ||
c.cmd, err = newCmd(command) | ||
if err != nil { | ||
c.reset() | ||
continue | ||
} | ||
|
||
logrus.Debugln("Running:", c.cmd.String()) | ||
if err := c.cmd.Start(); err != nil { | ||
c.reset() | ||
return err | ||
} | ||
|
||
c.cmd.Wait() // nolint:errcheck,gosec | ||
select { | ||
case <-c.kill: | ||
goto killRun | ||
default: | ||
continue | ||
} | ||
} | ||
|
||
killRun: | ||
c.reset() | ||
return nil | ||
} | ||
|
||
func (c *Commander) reset() { | ||
c.cmd = nil | ||
} | ||
|
||
// Kill stops the execution of current command and terminates the Run. | ||
func (c *Commander) Kill() error { | ||
if c.cmd == nil { | ||
return nil | ||
} | ||
|
||
if err := syscall.Kill(-c.cmd.Process.Pid, syscall.SIGKILL); err != nil { | ||
return err | ||
} | ||
c.kill <- true | ||
c.wg.Wait() | ||
c.reset() | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package engine | ||
|
||
import ( | ||
"os" | ||
"os/signal" | ||
"time" | ||
|
||
"github.com/sirupsen/logrus" | ||
|
||
"github.com/vrongmeal/leaf/pkg/commander" | ||
"github.com/vrongmeal/leaf/pkg/utils" | ||
"github.com/vrongmeal/leaf/pkg/watcher" | ||
) | ||
|
||
// Start runs the watcher and executes the commands from the config on file change. | ||
func Start(conf *utils.Config) error { | ||
cmdr := commander.NewCommander(conf.Exec) | ||
|
||
opts := watcher.WatchOpts{ | ||
Root: conf.Root, | ||
Exclude: conf.Exclude, | ||
Filters: conf.Filters, | ||
} | ||
|
||
wchr, err := watcher.NewWatcher(&opts) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
go func() { | ||
if err := wchr.Watch(); err != nil { | ||
logrus.Errorf("Failed to setup watcher: %s", err.Error()) | ||
} | ||
}() | ||
|
||
interrupt := make(chan os.Signal) | ||
signal.Notify(interrupt, os.Interrupt) | ||
|
||
exit := make(chan bool, 1) | ||
|
||
runCmd(cmdr) | ||
|
||
go func() { | ||
<-interrupt | ||
logrus.Infoln("Terminating after cleanup") | ||
if err := cmdr.Kill(); err != nil { | ||
logrus.Errorf("Error while stopping command: %s", err.Error()) | ||
} | ||
wchr.Close() | ||
exit <- true | ||
}() | ||
|
||
for { | ||
select { | ||
case file := <-wchr.File: | ||
if err := cmdr.Kill(); err != nil { | ||
logrus.Errorf("Error while stopping command: %s", err.Error()) | ||
wchr.Close() | ||
return err | ||
} | ||
logrus.Infof("File modified! Reloading... (%s)", file) | ||
|
||
// Sleep for conf.Delay duration amount of time. | ||
time.Sleep(conf.Delay) | ||
|
||
runCmd(cmdr) | ||
|
||
case err := <-wchr.Err: | ||
logrus.Errorf("Error while watching: %s", err.Error()) | ||
|
||
case <-exit: | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
func runCmd(cmdr *commander.Commander) { | ||
go func() { | ||
if err := cmdr.Run(); err != nil { | ||
logrus.Warnf("Error while running command: %s", err.Error()) | ||
} | ||
}() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package utils | ||
|
||
import ( | ||
"encoding/json" | ||
"io/ioutil" | ||
"path/filepath" | ||
"time" | ||
|
||
"github.com/BurntSushi/toml" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
// Config represents the conf file for the runner. | ||
type Config struct { | ||
Root string `json:"root" yaml:"root" toml:"root"` | ||
Exclude []string `json:"exclude" yaml:"exclude" toml:"exclude"` | ||
Filters []string `json:"filters" yaml:"filters" toml:"filters"` | ||
Exec [][]string `json:"exec" yaml:"exec" toml:"exec"` | ||
Delay time.Duration `json:"delay" yaml:"delay" toml:"delay"` | ||
} | ||
|
||
// GetConfig returns config from the filepath. | ||
func GetConfig(path string) (Config, error) { | ||
config := Config{} | ||
|
||
isdir, err := IsDir(path) | ||
if err != nil || isdir { | ||
return config, err | ||
} | ||
|
||
content, err := ioutil.ReadFile(filepath.Clean(path)) | ||
if err != nil { | ||
return config, err | ||
} | ||
|
||
switch filepath.Ext(path) { | ||
case ".json": | ||
if err := json.Unmarshal(content, &config); err != nil { | ||
return config, err | ||
} | ||
case ".toml": | ||
if _, err := toml.Decode(string(content), &config); err != nil { | ||
return config, err | ||
} | ||
default: | ||
if err := yaml.Unmarshal(content, &config); err != nil { | ||
return config, err | ||
} | ||
} | ||
|
||
return config, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package utils | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
var ( | ||
// CWD is the current working directory or "." | ||
CWD string | ||
|
||
// DefaultConfPath is the default path for app config. | ||
DefaultConfPath string | ||
) | ||
|
||
func init() { | ||
var err error | ||
CWD, err = os.Getwd() | ||
if err != nil { | ||
logrus.Fatalln(err) | ||
} | ||
|
||
DefaultConfPath = filepath.Join(CWD, ".leaf.yml") | ||
} |
Oops, something went wrong.