Compare commits
No commits in common. "master" and "v0.3.1" have entirely different histories.
8 changed files with 114 additions and 170 deletions
56
Taskfile.yml
56
Taskfile.yml
|
@ -1,56 +0,0 @@
|
|||
version: '3'
|
||||
|
||||
vars:
|
||||
UNAME:
|
||||
sh: uname
|
||||
DOCKER_EXE: /usr/src/listen/out/{{OS}}/listen
|
||||
|
||||
tasks:
|
||||
default:
|
||||
deps:
|
||||
- build:go
|
||||
|
||||
build:go:
|
||||
aliases:
|
||||
- build
|
||||
cmds:
|
||||
- "go build -o out/{{OS}}/ ."
|
||||
preconditions:
|
||||
- sh: "which go"
|
||||
msg: Go is not installed. Install Go or build with Docker using the "docker" task
|
||||
|
||||
build:docker:
|
||||
aliases:
|
||||
- docker
|
||||
cmds:
|
||||
- task: build:docker:dockercli
|
||||
- "docker build -t listen-build -f docker/build.Dockerfile --build-arg GOOS='{{OS}}' --build-arg GOARCH='{{ARCH}}' ."
|
||||
- "mkdir -p out/{{OS}}"
|
||||
- "docker create --name listen-build-tmp listen-build"
|
||||
- cmd: "docker cp listen-build-tmp:{{.DOCKER_EXE}}.exe out/{{OS}}/"
|
||||
platforms:
|
||||
- windows
|
||||
- cmd: "docker cp listen-build-tmp:{{.DOCKER_EXE}} out/{{OS}}/"
|
||||
platforms:
|
||||
- darwin
|
||||
- linux
|
||||
- "docker rm listen-build-tmp"
|
||||
preconditions:
|
||||
- sh: "uname | grep -qe Linux -e MINGW -e Darwin"
|
||||
msg: "This task cannot be ran on OS: {{OS}}"
|
||||
- sh: "which docker"
|
||||
msg: Docker is not installed. Install Docker or build with Go using the "build" task
|
||||
|
||||
build:docker:dockercli:
|
||||
cmds:
|
||||
- cmd: '"C:\Program Files\Docker\Docker\DockerCli.exe" -SwitchLinuxEngine'
|
||||
platforms:
|
||||
- windows
|
||||
|
||||
clean:
|
||||
cmds:
|
||||
- rm -rf out/
|
||||
- cmd: docker rm listen-build-tmp
|
||||
ignore_error: true
|
||||
- cmd: docker image rm listen-build
|
||||
ignore_error: true
|
49
build
Executable file
49
build
Executable file
|
@ -0,0 +1,49 @@
|
|||
#!/bin/sh
|
||||
# Create all the different builds for listen
|
||||
|
||||
# verify we are at root of repository
|
||||
if ! [ -d .git ]; then
|
||||
echo build: this script must be run at the root of the repo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if uname | grep -qe Linux; then
|
||||
os=linux
|
||||
elif uname | grep -qe MINGW; then
|
||||
os=windows
|
||||
else
|
||||
os=other
|
||||
fi
|
||||
|
||||
build_go() {
|
||||
go build -o out/$(go env GOOS)/listen .
|
||||
}
|
||||
|
||||
build_docker() {
|
||||
if [ $os = "windows" ]; then
|
||||
/c/Program\ Files/Docker/Docker/DockerCli.exe -SwitchWindowsEngine
|
||||
exe=C:/build/listen/listen.exe
|
||||
else
|
||||
exe=/usr/src/listen/listen
|
||||
fi
|
||||
|
||||
if docker build -t listen-build -f docker/build.$os.Dockerfile .; then
|
||||
mkdir -p out/$os
|
||||
docker create --name listen-build-tmp listen-build
|
||||
docker cp listen-build-tmp:$exe out/$os
|
||||
docker rm listen-build-tmp
|
||||
fi
|
||||
}
|
||||
|
||||
# if an arg is specified, force building with the specified method
|
||||
[ $1 ] && (build_$1; exit)
|
||||
|
||||
# prefer building with local go install if it exists on path
|
||||
if which go &> /dev/null; then
|
||||
build_go; exit
|
||||
# if windows or linux, try building with docker
|
||||
elif echo $os | grep -qe linux -e windows; then
|
||||
which docker &> /dev/null && (build_docker; exit)
|
||||
fi
|
||||
|
||||
echo could not find valid build method for OS && exit 2
|
|
@ -1,16 +0,0 @@
|
|||
# This Dockerfile is meant for building listen ONLY
|
||||
# listen is currently not intended to run in a Docker container
|
||||
|
||||
FROM golang:1-alpine AS build
|
||||
|
||||
WORKDIR /usr/src/listen
|
||||
COPY . .
|
||||
|
||||
RUN apk upgrade --no-cache
|
||||
ARG GOOS="linux"
|
||||
RUN go env -w GOOS=${GOOS}
|
||||
ARG GOARCH="amd64"
|
||||
RUN go env -w GOARCH=${GOARCH}
|
||||
RUN go build -o out/$(go env GOOS)/ .
|
||||
|
||||
CMD ["tail", "-f", "/dev/null"]
|
14
docker/build.linux.Dockerfile
Normal file
14
docker/build.linux.Dockerfile
Normal file
|
@ -0,0 +1,14 @@
|
|||
# This Dockerfile is meant for building listen for Linux ONLY
|
||||
# listen is currently not intended to run in a Docker container
|
||||
|
||||
ARG IMAGE="1-alpine"
|
||||
|
||||
FROM golang:${IMAGE} AS build
|
||||
|
||||
WORKDIR /usr/src/listen
|
||||
COPY . .
|
||||
|
||||
RUN apk upgrade --no-cache
|
||||
RUN go build .
|
||||
|
||||
CMD ["tail", "-f", "/dev/null"]
|
13
docker/build.windows.Dockerfile
Normal file
13
docker/build.windows.Dockerfile
Normal file
|
@ -0,0 +1,13 @@
|
|||
# This Dockerfile is meant for building listen for Windows ONLY
|
||||
# listen is currently not intended to run in a Docker container
|
||||
|
||||
ARG IMAGE="1-nanoserver"
|
||||
|
||||
FROM golang:${IMAGE} AS build
|
||||
|
||||
WORKDIR C:/build/listen
|
||||
COPY . .
|
||||
|
||||
RUN go build .
|
||||
|
||||
CMD ["ping.exe", "-t", "localhost"]
|
85
listen.go
85
listen.go
|
@ -17,14 +17,12 @@ import (
|
|||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
var triggered bool = false
|
||||
|
||||
type Listen struct {
|
||||
FileMap map[string]bool
|
||||
CksumMap map[string][]byte
|
||||
IntervalMap map[string]time.Time
|
||||
Condition string
|
||||
NoCksum bool
|
||||
Cksum bool
|
||||
Interval time.Duration
|
||||
Command []string
|
||||
Quiet bool
|
||||
|
@ -76,7 +74,6 @@ func allCheck(l Listen) bool {
|
|||
}
|
||||
|
||||
func runTrigger(l Listen, f string, r bool, quit chan bool) bool {
|
||||
triggered = true
|
||||
if !l.Quiet && !r {
|
||||
if l.Condition == "any" {
|
||||
printStatus(fmt.Sprintf("& File %s modified. ", f))
|
||||
|
@ -97,32 +94,12 @@ func runTrigger(l Listen, f string, r bool, quit chan bool) bool {
|
|||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
exit := make(chan bool)
|
||||
s := make(chan os.Signal, 1)
|
||||
signal.Notify(s, os.Interrupt)
|
||||
|
||||
go func() {
|
||||
if err := cmd.Wait(); err != nil {
|
||||
if rc, ok := err.(*exec.ExitError); ok {
|
||||
if !l.Quiet {
|
||||
switch rc.ExitCode() {
|
||||
case -1:
|
||||
color.New(color.FgYellow).Fprintf(os.Stderr, "\n& WARNING: Command interrupted with ^C.\n")
|
||||
default:
|
||||
color.New(color.FgYellow).Fprintf(os.Stderr, "& WARNING: Command exited with code %d.\n", rc.ExitCode())
|
||||
}
|
||||
}
|
||||
if err := cmd.Wait(); err != nil {
|
||||
if rc, ok := err.(*exec.ExitError); ok {
|
||||
if !l.Quiet {
|
||||
color.New(color.FgYellow).Fprintf(os.Stderr, "& WARNING: Command exited with code %d.\n", rc.ExitCode())
|
||||
}
|
||||
}
|
||||
|
||||
exit <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-s:
|
||||
cmd.Process.Signal(os.Interrupt)
|
||||
<-exit
|
||||
case <-exit:
|
||||
}
|
||||
|
||||
if !l.Quiet {
|
||||
|
@ -134,11 +111,9 @@ func runTrigger(l Listen, f string, r bool, quit chan bool) bool {
|
|||
}
|
||||
|
||||
quit <- true
|
||||
triggered = false
|
||||
return true
|
||||
}
|
||||
|
||||
triggered = false
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -165,7 +140,13 @@ func loopFsnotify(l Listen, quit chan bool) {
|
|||
|
||||
for key := range l.FileMap {
|
||||
if event.Name == key {
|
||||
if l.NoCksum {
|
||||
if l.Cksum {
|
||||
// return value indicates a break is needed
|
||||
if cksumCheck(l, key, &trigger) {
|
||||
fileMod = key
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if event.Has(fsnotify.Write) {
|
||||
if l.Condition == "any" {
|
||||
fileMod = key
|
||||
|
@ -188,12 +169,6 @@ func loopFsnotify(l Listen, quit chan bool) {
|
|||
|
||||
l.FileMap[key] = true
|
||||
}
|
||||
} else {
|
||||
// return value indicates a break is needed
|
||||
if cksumCheck(l, key, &trigger) {
|
||||
fileMod = key
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} // end for
|
||||
|
@ -231,7 +206,13 @@ func loopInterval(l Listen, quit chan bool) {
|
|||
trigger := false
|
||||
|
||||
for key := range l.FileMap {
|
||||
if l.NoCksum {
|
||||
if l.Cksum {
|
||||
// return value indicates a break is needed
|
||||
if cksumCheck(l, key, &trigger) {
|
||||
fileMod = key
|
||||
break
|
||||
}
|
||||
} else {
|
||||
s, err := os.Stat(key)
|
||||
if err != nil {
|
||||
log.Fatal()
|
||||
|
@ -247,12 +228,7 @@ func loopInterval(l Listen, quit chan bool) {
|
|||
|
||||
l.FileMap[key] = true
|
||||
}
|
||||
} else {
|
||||
// return value indicates a break is needed
|
||||
if cksumCheck(l, key, &trigger) {
|
||||
fileMod = key
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
} // end for key
|
||||
|
||||
|
@ -286,7 +262,7 @@ func startMessage(l Listen) {
|
|||
printStatus("& listen will exit when ")
|
||||
}
|
||||
|
||||
if !l.NoCksum {
|
||||
if l.Cksum {
|
||||
printStatus("the checksum of ")
|
||||
}
|
||||
|
||||
|
@ -302,10 +278,10 @@ func startMessage(l Listen) {
|
|||
printStatus("this file has ")
|
||||
}
|
||||
|
||||
if l.NoCksum {
|
||||
printStatus("been modified:\n")
|
||||
} else {
|
||||
if l.Cksum {
|
||||
printStatus("changed:\n")
|
||||
} else {
|
||||
printStatus("been modified:\n")
|
||||
}
|
||||
|
||||
for key := range l.FileMap {
|
||||
|
@ -313,9 +289,8 @@ func startMessage(l Listen) {
|
|||
}
|
||||
}
|
||||
|
||||
func (l Listen) Run() int {
|
||||
func (l Listen) Run() {
|
||||
// catch ^C
|
||||
ret := 0
|
||||
quit := make(chan bool)
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
|
@ -342,14 +317,7 @@ func (l Listen) Run() int {
|
|||
|
||||
select {
|
||||
case <-c:
|
||||
for {
|
||||
if !triggered {
|
||||
fmt.Println()
|
||||
ret = 130
|
||||
break
|
||||
}
|
||||
<-c
|
||||
}
|
||||
fmt.Println()
|
||||
case <-quit:
|
||||
}
|
||||
|
||||
|
@ -357,5 +325,4 @@ func (l Listen) Run() int {
|
|||
printStatus("& Exiting...\n")
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
|
42
main.go
42
main.go
|
@ -13,16 +13,16 @@ import (
|
|||
flags "github.com/jessevdk/go-flags"
|
||||
)
|
||||
|
||||
const VERSION string = "v0.4.1"
|
||||
const VERSION string = "v0.3.1"
|
||||
|
||||
type Options struct {
|
||||
Version bool `short:"v" long:"version" description:"Displays version info and exits"`
|
||||
Quiet bool `short:"q" long:"quiet" description:"Suppresses status messages (stderr lines beginning with '&')"`
|
||||
Files []string `short:"f" long:"file" description:"File(s) to listen to (watch)" value-name:"FILE"`
|
||||
Condition string `short:"w" long:"when" description:"If multiple files are specified, choose if any file or all files specified are needed to trigger COMMAND" default:"any" choice:"any" choice:"all"`
|
||||
NoChecksum bool `short:"x" long:"no-checksum" description:"Do not calculate checksum as an additional check for file changes (see MODES section in man page for more info)"`
|
||||
Interval string `short:"i" long:"interval" description:"Use this time interval (ex. 5m30s, 1s) between filesystem checks instead of watching kernel events. If the interval is effectively 0 (the default), kernel events are used" default:"0s"`
|
||||
RunFirst bool `short:"r" long:"run" description:"Runs COMMAND (if specified) before starting listen"`
|
||||
Version bool `short:"v" long:"version" description:"Displays version info and exits"`
|
||||
Quiet bool `short:"q" long:"quiet" description:"Suppresses status messages (stderr lines beginning with '&')"`
|
||||
Files []string `short:"f" long:"file" description:"File(s) to listen to (watch)" value-name:"FILE"`
|
||||
Condition string `short:"w" long:"when" description:"If multiple files are specified, choose if any file or all files specified are needed to trigger COMMAND" default:"any" choice:"any" choice:"all"`
|
||||
Checksum bool `short:"c" long:"checksum" description:"Use checksum to determine when file(s) are changed instead of writes/modification time"`
|
||||
Interval string `short:"i" long:"interval" description:"Use this time interval (ex. 5m30s, 1s) between filesystem checks instead of watching kernel events. If the interval is effectively 0 (the default), kernel events are used" default:"0s"`
|
||||
RunFirst bool `short:"r" long:"run" description:"Runs COMMAND (if specified) before starting listen"`
|
||||
}
|
||||
|
||||
func validateArgs(opts Options, commandLen int) error {
|
||||
|
@ -66,7 +66,7 @@ func validateArgs(opts Options, commandLen int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func setup(opts Options, command []string) int {
|
||||
func setup(opts Options, command []string) {
|
||||
var filesMap map[string]bool = make(map[string]bool)
|
||||
var cksumMap map[string][]byte = make(map[string][]byte)
|
||||
var intervalMap map[string]time.Time = make(map[string]time.Time)
|
||||
|
@ -103,40 +103,22 @@ func setup(opts Options, command []string) int {
|
|||
}
|
||||
|
||||
if success {
|
||||
l := Listen{filesMap, cksumMap, intervalMap, opts.Condition, opts.NoChecksum, intervalDuration, command, opts.Quiet, opts.RunFirst}
|
||||
return l.Run()
|
||||
l := Listen{filesMap, cksumMap, intervalMap, opts.Condition, opts.Checksum, intervalDuration, command, opts.Quiet, opts.RunFirst}
|
||||
l.Run()
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func main() {
|
||||
ret := 0
|
||||
opts := Options{}
|
||||
parser := flags.NewParser(&opts, flags.Default)
|
||||
parser.Usage = "[OPTIONS] -- [COMMAND]"
|
||||
remaining, err := parser.Parse()
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
switch ret {
|
||||
case -1:
|
||||
os.Exit(5)
|
||||
default:
|
||||
os.Exit(ret)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err == nil {
|
||||
if opts.Version {
|
||||
fmt.Printf("listen %s\n", VERSION)
|
||||
} else if err := validateArgs(opts, len(remaining)); err == nil {
|
||||
ret = setup(opts, remaining)
|
||||
if ret != 0 {
|
||||
panic(ret)
|
||||
}
|
||||
setup(opts, remaining)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
% listen | General Commands Manual
|
||||
|
||||
# NAME
|
||||
|
||||
listen - A simple watcher program that runs commands when specified files are modified
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
listen [OPTIONS] -- [COMMAND]
|
Loading…
Add table
Reference in a new issue