leaf

package module
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 12, 2020 License: MIT Imports: 13 Imported by: 0

README

leaf

General purpose hot-reloader for all projects.

Continuous Integration

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

  1. Installation
    1. Using go get
    2. Manual
  2. Usage
    1. Command line help
    2. Configuration file
  3. Custom hot reloader

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
  1. Clone the repository and cd into it.
  2. Run make build to build the leaf as build/leaf.
  3. 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

Constants

This section is empty.

Variables

View Source
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

func GoModuleInfo() (*debug.Module, error)

GoModuleInfo returns the go module information which includes the build info (version etc.).

func NewCmdContext added in v1.2.2

func NewCmdContext(onInterrupt func(os.Signal)) context.Context

NewCmdContext returns a context which cancels on an OS interrupt, i.e., cancels when process is killed.

func StandardFilterHandler added in v1.2.1

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 added in v1.2.1

func StandardFilterMatcher(pattern, path string) bool

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

type Command struct {
	Name string
	Args []string
	// contains filtered or unexported fields
}

Command is an external command that can be executed.

func NewCommand

func NewCommand(cmd string) (*Command, error)

NewCommand creates a new command from the string.

func (*Command) Execute

func (c *Command) Execute(ctx context.Context) error

Execute runs the commands and exits elegantly when the context is canceled.

This doesn't use the exec.CommandContext because we just don't want to kill the parent process but all the child processes too.

func (*Command) String

func (c *Command) String() string

String returns the command in a human-readable format.

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

func NewCommander(commander Commander) *Commander

NewCommander creates a new commander.

func (*Commander) Done

func (c *Commander) Done() <-chan bool

Done signals that the commander is done running the commands.

func (*Commander) Run

func (c *Commander) Run(ctx context.Context)

Run executes the commands in order. It stops when the context is canceled.

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 Filter

type Filter struct {
	Include bool // whether to include pattern
	Pattern string
}

Filter can be used to Filter out watch results.

func NewFilter

func NewFilter(pattern string) (Filter, error)

NewFilter creates a filter from the pattern string. The pattern either starts with '+' or '-' to include or exclude the directory from results.

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 added in v1.2.1

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 added in v1.2.1

func (fc *FilterCollection) ShouldHandlePath(path string) bool

ShouldHandlePath returns the result of the path handler for the filter collection.

type FilterHandleFunc added in v1.2.1

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 added in v1.2.1

type FilterMatchFunc func(pattern, path string) bool

FilterMatchFunc compares the pattern with the path of the file changed and returns true if the path resembles the given pattern.

type WatchResult

type WatchResult struct {
	File string
	Err  error
}

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.

func (*Watcher) Watch

func (w *Watcher) Watch(ctx context.Context) <-chan WatchResult

Watch executes the watching of files. Exits on cancellation of the context.

Directories

Path Synopsis
cmd
Package cmd implements the command-line interface for the leaf command.
Package cmd implements the command-line interface for the leaf command.
leaf
Command leaf watches for changes in the working directory and runs the specified set of commands whenever a file updates.
Command leaf watches for changes in the working directory and runs the specified set of commands whenever a file updates.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL