2025-06-28 17:14:54 -06:00
package main
import (
2025-06-29 10:33:58 -06:00
"crypto/sha256"
2025-06-28 17:14:54 -06:00
"fmt"
2025-06-29 10:33:58 -06:00
"io"
2025-06-28 17:14:54 -06:00
"io/fs"
"log"
"os"
"path/filepath"
2025-06-29 11:25:24 -06:00
"time"
2025-06-28 17:14:54 -06:00
flags "github.com/jessevdk/go-flags"
)
2025-07-11 00:44:19 -06:00
const VERSION string = "v0.4.0"
2025-06-28 17:14:54 -06:00
2025-06-29 12:58:37 -06:00
type Options struct {
2025-07-11 00:44:19 -06:00
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" `
2025-06-28 17:14:54 -06:00
}
2025-06-29 12:58:37 -06:00
func validateArgs ( opts Options , commandLen int ) error {
2025-06-29 11:25:24 -06:00
// file checks
2025-06-29 12:58:37 -06:00
if len ( opts . Files ) <= 0 {
2025-06-28 17:14:54 -06:00
fmt . Println ( "listen: at least one file (-f) is required" )
return flags . ErrCommandRequired
}
2025-06-29 12:58:37 -06:00
for _ , file := range opts . Files {
2025-06-28 17:14:54 -06:00
info , err := os . Stat ( file )
if err != nil {
fmt . Printf ( "listen: %s\n" , err )
return err
}
if info . IsDir ( ) {
fmt . Printf ( "listen: %s: not a file\n" , file )
return fs . ErrInvalid
}
_ , err = os . Open ( file )
if err != nil {
fmt . Printf ( "listen: %s\n" , err )
return err
}
}
2025-06-29 11:25:24 -06:00
// interval checks
2025-06-29 12:58:37 -06:00
if _ , err := time . ParseDuration ( opts . Interval ) ; err != nil {
2025-06-29 11:25:24 -06:00
fmt . Printf ( "listen: %s\n" , err )
return err
}
2025-06-29 12:58:37 -06:00
// run first check
if commandLen < 1 && opts . RunFirst {
fmt . Println ( "listen: -r cannot be specified without a command" )
return flags . ErrInvalidChoice
}
2025-06-28 17:14:54 -06:00
return nil
}
2025-07-11 12:32:31 -06:00
func setup ( opts Options , command [ ] string ) int {
2025-06-29 12:58:37 -06:00
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 )
intervalDuration , _ := time . ParseDuration ( opts . Interval )
success := true
for _ , file := range opts . Files {
hasher := sha256 . New ( )
f , err := os . Open ( file )
if err != nil {
success = false
break
}
defer f . Close ( )
s , err := os . Stat ( file )
if err != nil {
success = false
break
}
if _ , err := io . Copy ( hasher , f ) ; err != nil {
log . Fatal ( err )
}
abs , err := filepath . Abs ( file )
if err == nil {
filesMap [ abs ] = false
cksumMap [ abs ] = hasher . Sum ( nil )
intervalMap [ abs ] = s . ModTime ( )
} else {
success = false
break
}
}
if success {
2025-07-11 00:44:19 -06:00
l := Listen { filesMap , cksumMap , intervalMap , opts . Condition , opts . NoChecksum , intervalDuration , command , opts . Quiet , opts . RunFirst }
2025-07-11 12:32:31 -06:00
return l . Run ( )
2025-06-29 12:58:37 -06:00
}
2025-07-11 12:32:31 -06:00
return - 1
2025-06-29 12:58:37 -06:00
}
2025-06-28 17:14:54 -06:00
func main ( ) {
2025-07-11 12:32:31 -06:00
ret := 0
2025-06-29 12:58:37 -06:00
opts := Options { }
2025-06-28 17:14:54 -06:00
parser := flags . NewParser ( & opts , flags . Default )
parser . Usage = "[OPTIONS] -- [COMMAND]"
remaining , err := parser . Parse ( )
2025-07-11 00:44:19 -06:00
defer func ( ) {
if err := recover ( ) ; err != nil {
2025-07-11 12:32:31 -06:00
switch ret {
case - 1 :
os . Exit ( 5 )
default :
os . Exit ( ret )
}
2025-07-11 00:44:19 -06:00
}
} ( )
2025-06-28 17:14:54 -06:00
if err == nil {
if opts . Version {
fmt . Printf ( "listen %s\n" , VERSION )
2025-06-29 12:58:37 -06:00
} else if err := validateArgs ( opts , len ( remaining ) ) ; err == nil {
2025-07-11 12:32:31 -06:00
ret = setup ( opts , remaining )
if ret != 0 {
panic ( ret )
}
2025-06-28 17:14:54 -06:00
}
}
2025-07-11 00:44:19 -06:00
2025-06-28 17:14:54 -06:00
}