README
leaf
General purpose hot-reloader for all projects.
Command leaf watches for changes in the working directory and runs the specified set of commands whenever a file updates. A set of filters can be applied to the watch and directories can be excluded.
Contents
Installation
Using go get
The following command will download and build Leaf in your
$GOPATH/bin
.
❯ go get -u github.com/vrongmeal/leaf/cmd/leaf
Manual
- Clone the repository and
cd
into it. - Run
make build
to build the leaf asbuild/leaf
. - Move the binary somewhere in your
$PATH
.
Usage
❯ leaf -x 'make build' -x 'make run'
The above command runs make build
and make run
commands
(in order).
Command line help
The CLI can be used as described by the help message:
❯ leaf help
Command leaf watches for changes in the working directory and
runs the specified set of commands whenever a file updates.
A set of filters can be applied to the watch and directories
can be excluded.
Usage:
leaf [flags]
leaf [command]
Available Commands:
help Help about any command
version prints leaf version
Flags:
-c, --config string config path for the configuration file (default "<CWD>/.leaf.yml")
--debug run in development (debug) environment
-d, --delay duration delay after which commands are run on file change (default 500ms)
-e, --exclude strings paths to exclude from watching (default [.git/,node_modules/,vendor/,venv/])
-x, --exec strings exec commands on file change
-z, --exit-on-err exit chain of commands on error
-f, --filters strings filters to apply to watch
-h, --help help for leaf
-o, --once run once and exit (no reload)
-r, --root string root directory to watch (default "<CWD>")
Use "leaf [command] --help" for more information about a command.
Configuration file
In order to configure using a configuration file, create a
YAML or TOML or even a JSON file with the following structure
and pass it using the -c
or --config
flag. By default
a file named .leaf.yml
in your working directory is taken
if no configuration file is found.
# 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:
- DEFAULTS # This includes the default ignored directories
- build/
- scripts/
# 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 regex supported by filepath.Match
# method or even a directory.
filters:
- '+ go.mod'
- '+ go.sum'
- '+ *.go'
- '+ cmd/'
# Commands to be executed. These are run in the provided order.
exec:
- make format
- make build
# Stop the command chain when an error occurs
exit_on_err: true
# Delay after which commands are executed.
delay: 1s
The above config file is suitable to use with the current project itself. It can also be translated into a command as such:
❯ leaf -z -x 'make format' -x 'make build' -d '1s' \
-e 'DEFAULTS' -e 'build' -e 'scripts' \
-f '+ go.*' -f '+ *.go' -f '+ cmd/'
Custom hot reloader
The package github.com/vrongmeal/leaf comes with utilities that can aid in creating a hot-reloader with a simple go program.
Let's look at an example where the watcher watches the src/
directory for changes and for any changes builds the project.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/vrongmeal/leaf"
)
func main() {
// Use a context that cancels when program is interrupted.
ctx := leaf.NewCmdContext(func(os.Signal) {
log.Println("Shutting down.")
})
cwd, err := os.Getwd()
if err != nil {
log.Fatalln(err)
}
// Root is <cwd>/src
root := filepath.Join(cwd, "src")
// Exclude "src/index.html" from results.
filters := []leaf.Filter{
{Include: false, Pattern: "src/index.html"},
}
filterCollection := leaf.NewFilterCollection(
filters,
// Matches directory or filepath.Match expressions
leaf.StandardFilterMatcher,
// Definitely excludes and shows only includes (if any)
leaf.StandardFilterHandler)
watcher, err := leaf.NewWatcher(
root,
// Standard paths to exclude, like vendor, .git,
// node_modules, venv etc.
leaf.DefaultExcludePaths,
filterCollection)
if err != nil {
log.Fatalln(err)
}
cmd, err := leaf.NewCommand("npm run build")
if err != nil {
log.Fatalln(err)
}
log.Printf("Watching: %s\n", root)
for change := range watcher.Watch(ctx) {
if change.Err != nil {
log.Printf("ERROR: %v", change.Err)
continue
}
// If no error run the command
fmt.Printf("Running: %s\n", cmd.String())
cmd.Execute(ctx)
}
}
Made with khoon, paseena and love :-)
by
Vaibhav (vrongmeal)
Documentation
Overview ¶
Package leaf provides with utilities to create the leaf CLI tool. It includes watcher, filters and commander which watch files for changes, filter out required results and execute external commands respectively.
The package comes with utilities that can aid in creating a hot-reloader with a simple go program.
Let's look at an example where the watcher watches the `src/` directory for changes and for any changes builds the project.
package main import ( "log" "os" "path/filepath" "github.com/vrongmeal/leaf" ) func main() { // Use a context that cancels when program is interrupted. ctx := leaf.NewCmdContext(func(os.Signal) { log.Println("Shutting down.") }) cwd, err := os.Getwd() if err != nil { log.Fatalln(err) } // Root is <cwd>/src root := filepath.Join(cwd, "src") // Exclude "src/index.html" from results. filters := []leaf.Filter{ {Include: false, Pattern: "src/index.html"}, } filterCollection := leaf.NewFilterCollection( filters, // Matches directory or filepath.Match expressions leaf.StandardFilterMatcher, // Definitely excludes and shows only includes (if any) leaf.StandardFilterHandler) watcher, err := leaf.NewWatcher( root, // Standard paths to exclude, like vendor, .git, // node_modules, venv etc. leaf.DefaultExcludePaths, filterCollection) if err != nil { log.Fatalln(err) } cmd, err := leaf.NewCommand("npm run build") if err != nil { log.Fatalln(err) } log.Printf("Watching: %s\n", root) for change := range watcher.Watch(ctx) { if change.Err != nil { log.Printf("ERROR: %v", change.Err) continue } // If no error run the command log.Printf("Running: %s\n", cmd.String()) cmd.Execute(ctx) } }
Index ¶
- Variables
- func GoModuleInfo() (*debug.Module, error)
- func NewCmdContext(onInterrupt func(os.Signal)) context.Context
- func StandardFilterHandler(fc *FilterCollection, path string) bool
- func StandardFilterMatcher(pattern, path string) bool
- type Command
- type Commander
- type Config
- type Filter
- type FilterCollection
- type FilterHandleFunc
- type FilterMatchFunc
- type WatchResult
- type Watcher
Constants ¶
Variables ¶
var ( // DefaultExcludePathsKeyword is used to include all // default excludes. DefaultExcludePathsKeyword = "DEFAULTS" // CWD is the current working directory or ".". CWD string // DefaultConfPath is the default path for app config. DefaultConfPath string // DefaultExcludePaths are the paths that should be // generally excluded while watching a project. DefaultExcludePaths = []string{ ".git/", "node_modules/", "vendor/", "venv/", } // ImportPath is the import path for leaf package. ImportPath = "github.com/vrongmeal/leaf" )
Functions ¶
func GoModuleInfo ¶
GoModuleInfo returns the go module information which includes the build info (version etc.).
func NewCmdContext ¶
NewCmdContext returns a context which cancels on an OS interrupt, i.e., cancels when process is killed.
func StandardFilterHandler ¶
func StandardFilterHandler(fc *FilterCollection, path string) bool
StandardFilterHandler returns true if the path should be included and returns false if path should not be included in result.
func StandardFilterMatcher ¶
StandardFilterMatcher matches the pattern with the path and returns true if the path either starts with (in absolute terms) or matches like the path regex.
Types ¶
type Command ¶
Command is an external command that can be executed.
func NewCommand ¶
NewCommand creates a new command from the string.
type Commander ¶
type Commander struct { Commands []string OnStart func(*Command) OnError func(error) OnExit func() ExitOnError bool // contains filtered or unexported fields }
Commander has a set of commands that run in order and exit when the context is canceled.
func NewCommander ¶
NewCommander creates a new commander.
type Config ¶
type Config struct { // Root directory to watch. Root string `mapstructure:"root"` // Exclude these directories from watch. Exclude []string `mapstructure:"exclude"` // Filters to apply to the watch. Filters []string `mapstructure:"filters"` // Exec these commads after changes detected. Exec []string `mapstructure:"exec"` // ExitOnErr breaks the chain of command if any command returnns an error. ExitOnErr bool `mapstructure:"exit_on_err"` // Delay after which commands should be executed. Delay time.Duration `mapstructure:"delay"` }
Config represents the conf file for the runner.
type FilterCollection ¶
type FilterCollection struct { Includes []string Excludes []string // contains filtered or unexported fields }
A FilterCollection contains a bunch of includes and excludes.
func NewFCFromPatterns ¶
func NewFCFromPatterns(patterns []string, mf FilterMatchFunc, hf FilterHandleFunc) (*FilterCollection, error)
NewFCFromPatterns creates a filter collection from a list of string format filters, like `+ /path/to/some/dir`.
func NewFilterCollection ¶
func NewFilterCollection(filters []Filter, mf FilterMatchFunc, hf FilterHandleFunc) *FilterCollection
NewFilterCollection creates a filter collection from a bunch of filter patterns.
func (*FilterCollection) HasExclude ¶
func (fc *FilterCollection) HasExclude(path string) bool
HasExclude tells if the collection matches the path with one of its excludes.
func (*FilterCollection) HasInclude ¶
func (fc *FilterCollection) HasInclude(path string) bool
HasInclude tells if the collection matches the path with one of its includes.
func (*FilterCollection) ShouldHandlePath ¶
func (fc *FilterCollection) ShouldHandlePath(path string) bool
ShouldHandlePath returns the result of the path handler for the filter collection.
type FilterHandleFunc ¶
type FilterHandleFunc func(fc *FilterCollection, path string) bool
FilterHandleFunc is a function that checks if for the filter collection, should the path be handled or not, i.e., should the notifier tick for change in path or not.
type FilterMatchFunc ¶
FilterMatchFunc compares the pattern with the path of the file changed and returns true if the path resembles the given pattern.
type WatchResult ¶
WatchResult has the file changed or the error that occurred during watching.
type Watcher ¶
type Watcher struct {
// contains filtered or unexported fields
}
Watcher watches a directory for changes and updates the stream when a file change (valid by filters) is updated.
func NewWatcher ¶
func NewWatcher(root string, exclude []string, fc *FilterCollection) (*Watcher, error)
NewWatcher returns a watcher from the given options.