<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"github.com/howeyc/fsnotify"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

const (
	Version         = "0.1.2"
	PidFile         = "watchf.pid"
	Program         = "watchf"
	ContinueOnError = false
)

func main() {
	// command line parsing
	var commands StringSet
	var sensitive time.Duration

	flag.Var(&commands, "c", "Add arbitrary command(repeatable)")
	flag.DurationVar(&sensitive, "t", time.Duration(100)*time.Millisecond, "The time sensitive for avoid execute command frequently(time unit: ns/us/ms/s/m/h)")
	stop := flag.Bool("s", false, "To stop the "+Program+" Daemon")
	showVersion := flag.Bool("v", false, "show version")

	flag.Usage = func() {
		fmt.Println("Usage:\n  " + Program + " options 'pattern'")
		fmt.Println("Options:")
		flag.PrintDefaults()
		fmt.Println("Variables:")
		fmt.Println("  $f: The filename of changed file")

		fmt.Println("Example 1:")
		fmt.Println("  " + Program + " -c 'go vet' -c 'go test' -c 'go install' '*.go'")
		fmt.Println("Example 2(Daemon):")
		fmt.Println("  " + Program + " -c 'chmod 644 $f' '*.exe' &")
		fmt.Println("  " + Program + " -s")
	}
	flag.Parse()

	if *showVersion {
		fmt.Println("version " + Version)
	}

	if len(commands) == 0 && !*stop {
		flag.Usage()
		os.Exit(-1)
	}

	pattern := "*"
	if len(flag.Args()) > 0 {
		pattern = strings.Trim(strings.Join(flag.Args(), " "), " ")
	}

	// stop daemon via signal
	if *stop {
		daemon := &Daemon{}
		daemon.Stop()
		return
	}

	// start daemon
	daemon := &Daemon{
		service: &WatchService{pattern, sensitive, commands, nil},
	}

	err := daemon.Start()
	checkError(err)

	// stop daemon
	waitForStop(daemon)
}

type Service interface {
	start() error
	stop() error
}

type Daemon struct {
	local   bool
	process *os.Process
	service Service
}

func (d *Daemon) Start() (err error) {
	if d.IsRunning() {
		log.Fatalln(Program + " is already running")
		return
	} else {
		err := ioutil.WriteFile(PidFile, []byte(strconv.Itoa(os.Getpid())), 0644)
		checkError(err)
		d.local = true
		return d.service.start()
	}
}

func (d *Daemon) Stop() (err error) {
	if d.IsRunning() {
		if d.local {
			os.Remove(PidFile)
			return d.service.stop()
		} else {
			err = d.process.Signal(os.Interrupt)
		}
	}
	return
}

func (d *Daemon) IsRunning() bool {
	if d.local {
		return true
	}

	pid, err := getDaemonPid()
	if err == nil {
		d.process, err = os.FindProcess(pid)
		return err == nil
	}
	return false
}

func checkError(err error) {
	if err != nil {
		log.Fatalln(err)
	}
}

func getDaemonPid() (pid int, err error) {
	data, err := ioutil.ReadFile(PidFile)
	if err == nil {
		pid, err = strconv.Atoi(string(data))
	}
	return
}

func waitForStop(daemon *Daemon) {
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Kill, os.Interrupt)

	<-quit
	if err := daemon.Stop(); err != nil {
		fmt.Printf(Program+" stop failed: %s\n", err)
	} else {
		fmt.Println(Program + " stopped")
	}
}

type WatchService struct {
	pattern   string
	sensitive time.Duration
	commands  []string
	watcher   *fsnotify.Watcher
}

const (
	TRUE  int32 = 1
	FALSE int32 = 0
)

func (w *WatchService) start() (err error) {
	w.watcher, err = fsnotify.NewWatcher()
	if err != nil {
		return
	}

	go func() {
		var running = FALSE
		var lastExec time.Time

		for {
			select {
			case evt, ok := <-w.watcher.Event:
				if ok {
					now := time.Now()
					// TODO: verify file change by size and checksum
					// TODO: accept specific event
					if atomic.LoadInt32(&running) != TRUE && acceptedFile(w.pattern, evt) && lastExec.Add(w.sensitive).Before(now) {
						lastExec = now
						// using another goroutine to run command in order to non-blocking watcher.Event channel
						go execute(w.commands, evt, &running)
					}
				} else {
					break
				}
			case err, ok := <-w.watcher.Error:
				if ok {
					log.Fatalf("watcher error: %s\n", err)
				} else {
					break
				}
			}
		}
	}()

	// TODO: watching subdirectory
	err = w.watcher.Watch(".")<commit_msg>graceful quit on err<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"github.com/howeyc/fsnotify"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

const (
	Version         = "0.1.2"
	PidFile         = "watchf.pid"
	Program         = "watchf"
	ContinueOnError = false
)

var quit = make(chan os.Signal, 1)

func main() {
	// command line parsing
	var commands StringSet
	var sensitive time.Duration

	flag.Var(&commands, "c", "Add arbitrary command(repeatable)")
	flag.DurationVar(&sensitive, "t", time.Duration(100)*time.Millisecond, "The time sensitive for avoid execute command frequently(time unit: ns/us/ms/s/m/h)")
	stop := flag.Bool("s", false, "To stop the "+Program+" Daemon")
	showVersion := flag.Bool("v", false, "show version")

	flag.Usage = func() {
		fmt.Println("Usage:\n  " + Program + " options 'pattern'")
		fmt.Println("Options:")
		flag.PrintDefaults()
		fmt.Println("Variables:")
		fmt.Println("  $f: The filename of changed file")

		fmt.Println("Example 1:")
		fmt.Println("  " + Program + " -c 'go vet' -c 'go test' -c 'go install' '*.go'")
		fmt.Println("Example 2(Daemon):")
		fmt.Println("  " + Program + " -c 'chmod 644 $f' '*.exe' &")
		fmt.Println("  " + Program + " -s")
	}
	flag.Parse()

	if *showVersion {
		fmt.Println("version " + Version)
	}

	if len(commands) == 0 && !*stop {
		flag.Usage()
		os.Exit(-1)
	}

	pattern := "*"
	if len(flag.Args()) > 0 {
		pattern = strings.Trim(strings.Join(flag.Args(), " "), " ")
	}

	// stop daemon via signal
	if *stop {
		daemon := &Daemon{}
		daemon.Stop()
		return
	}

	// start daemon
	daemon := &Daemon{
		service: &WatchService{pattern, sensitive, commands, nil},
	}

	err := daemon.Start()
	checkError(err)

	// stop daemon
	waitForStop(daemon)
}

type Service interface {
	start() error
	stop() error
}

type Daemon struct {
	local   bool
	process *os.Process
	service Service
}

func (d *Daemon) Start() (err error) {
	if d.IsRunning() {
		log.Fatalln(Program + " is already running")
		return
	} else {
		if err = ioutil.WriteFile(PidFile, []byte(strconv.Itoa(os.Getpid())), 0644); err != nil {
			return
		}
		d.local = true
		return d.service.start()
	}
}

func (d *Daemon) Stop() (err error) {
	if d.IsRunning() {
		if d.local {
			os.Remove(PidFile)
			return d.service.stop()
		} else {
			err = d.process.Signal(os.Interrupt)
		}
	}
	return
}

func (d *Daemon) IsRunning() bool {
	if d.local {
		return true
	}

	pid, err := getDaemonPid()
	if err == nil {
		d.process, err = os.FindProcess(pid)
		return err == nil
	}
	return false
}

func checkError(err error) {
	if err != nil {
		log.Println(err)
		close(quit)
	}
}

func getDaemonPid() (pid int, err error) {
	data, err := ioutil.ReadFile(PidFile)
	if err == nil {
		pid, err = strconv.Atoi(string(data))
	}
	return
}

func waitForStop(daemon *Daemon) {

	signal.Notify(quit, os.Kill, os.Interrupt)

	<-quit
	if err := daemon.Stop(); err != nil {
		fmt.Printf(Program+" stop failed: %s\n", err)
	} else {
		fmt.Println(Program + " stopped")
	}
}

type WatchService struct {
	pattern   string
	sensitive time.Duration
	commands  []string
	watcher   *fsnotify.Watcher
}

const (
	TRUE  int32 = 1
	FALSE int32 = 0
)

func (w *WatchService) start() (err error) {
	w.watcher, err = fsnotify.NewWatcher()
	if err != nil {
		return
	}

	go func() {
		var running = FALSE
		var lastExec time.Time

		for {
			select {
			case evt, ok := <-w.watcher.Event:
				if ok {
					now := time.Now()
					// TODO: verify file change by size and checksum
					// TODO: accept specific event
					if atomic.LoadInt32(&running) != TRUE && acceptedFile(w.pattern, evt) && lastExec.Add(w.sensitive).Before(now) {
						lastExec = now
						// using another goroutine to run command in order to non-blocking watcher.Event channel
						go execute(w.commands, evt, &running)
					}
				} else {
					break
				}
			case err, ok := <-w.watcher.Error:
				if ok {
					checkError(err)
				} else {
					break
				}
			}
		}
	}()

	// TODO: watching subdirectory
	err = w.watcher.Watch(".")
{'fraction_non_alphanumeric': 0.16029411764705884, 'fraction_numerical': 0.006617647058823529, 'mean_word_length': 3.0662680617837568, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2785411', 'n_tokens_mistral': 3422, 'n_tokens_neox': 3088, 'n_words': 768}