git » summer » commit 8f1304d

Implement -exclude and -excludere

author Alberto Bertogli
2023-08-04 23:17:16 UTC
committer Alberto Bertogli
2023-08-05 00:09:08 UTC
parent dbf6a0f1228d61fcfade43248678024818bd6bae

Implement -exclude and -excludere

summer.go +45 -1
test/exclude.t +27 -0
test/help.t +12 -0
test/test.sh +3 -1
ui.go +11 -0

diff --git a/summer.go b/summer.go
index a55696f..e7860a3 100644
--- a/summer.go
+++ b/summer.go
@@ -8,6 +8,7 @@ import (
 	"io/fs"
 	"os"
 	"path/filepath"
+	"regexp"
 	"syscall"
 
 	"golang.org/x/term"
@@ -39,10 +40,13 @@ Usage:
 Flags:
 `
 
+// Flags.
 var (
 	dbPath        = flag.String("db", "", "database to read from/write to")
 	oneFilesystem = flag.Bool("x", false, "don't cross filesystem boundaries")
 	forceTTY      = flag.Bool("forcetty", false, "force TTY output")
+	exclude       = &RepeatedStringFlag{}
+	excludeRe     = &RepeatedStringFlag{}
 )
 
 var options = struct {
@@ -54,6 +58,12 @@ var options = struct {
 
 	// Whether output is a TTY.
 	isTTY bool
+
+	// Paths to exclude.
+	exclude map[string]bool
+
+	// Regexp patterns to exclude.
+	excludeRe []*regexp.Regexp
 }{}
 
 func Usage() {
@@ -64,12 +74,26 @@ func Usage() {
 func main() {
 	var err error
 
+	flag.Var(exclude, "exclude",
+		"exclude these paths (can be repeated)")
+	flag.Var(excludeRe, "excludere",
+		"exclude paths matching this regexp (can be repeated)")
+
 	flag.Usage = Usage
 	flag.Parse()
 
 	options.oneFilesystem = *oneFilesystem
 	options.isTTY = *forceTTY || term.IsTerminal(int(os.Stdout.Fd()))
 
+	options.exclude = map[string]bool{}
+	for _, s := range *exclude {
+		options.exclude[filepath.Clean(s)] = true
+	}
+
+	for _, s := range *excludeRe {
+		options.excludeRe = append(options.excludeRe, regexp.MustCompile(s))
+	}
+
 	op := flag.Arg(0)
 	root := flag.Arg(1)
 
@@ -105,6 +129,18 @@ func main() {
 	}
 }
 
+func isExcluded(path string) bool {
+	if options.exclude[path] {
+		return true
+	}
+	for _, re := range options.excludeRe {
+		if re.MatchString(path) {
+			return true
+		}
+	}
+	return false
+}
+
 var crc32c = crc32.MakeTable(crc32.Castagnoli)
 
 type ChecksumV1 struct {
@@ -117,13 +153,21 @@ type ChecksumV1 struct {
 }
 
 func openAndInfo(path string, d fs.DirEntry, err error, rootDev uint64) (bool, *os.File, fs.FileInfo, error) {
+	// Excluded check must come first, because it can be use to skip
+	// directories that would otherwise cause errors.
+	if isExcluded(path) {
+		if d.IsDir() {
+			return false, nil, nil, fs.SkipDir
+		}
+		return false, nil, nil, nil
+	}
+
 	if err != nil {
 		return false, nil, nil, err
 	}
 	if d.IsDir() || !d.Type().IsRegular() {
 		return false, nil, nil, nil
 	}
-
 	info, err := d.Info()
 	if err != nil {
 		return true, nil, nil, err
diff --git a/test/exclude.t b/test/exclude.t
new file mode 100644
index 0000000..f67a76b
--- /dev/null
+++ b/test/exclude.t
@@ -0,0 +1,27 @@
+Tests for excluding files.
+
+  $ alias summer="$TESTDIR/../summer"
+
+Simple test data.
+
+  $ touch empty1 empty2 empty3
+  $ echo marola > hola
+
+Generate.
+
+  $ summer -n --exclude empty2 --excludere emp..3 generate .
+  0s: 0 matched, 0 modified, 2 new, 0 corrupted
+
+Use a bit more complex test data.
+
+  $ mkdir dir1 dir2
+  $ touch dir1/file1 dir1/file2 dir1/file3
+  $ touch dir2/file1 dir2/file2 dir2/file3
+
+  $ summer -n \
+  >   --exclude empty2 \
+  >   --excludere emp..3 \
+  >   -exclude dir2 \
+  >   -excludere d..1/f...2 \
+  >   generate .
+  0s: 0 matched, 0 modified, 4 new, 0 corrupted
diff --git a/test/help.t b/test/help.t
index d3b4b5a..5f6d217 100644
--- a/test/help.t
+++ b/test/help.t
@@ -31,6 +31,10 @@ No arguments.
   Flags:
     -db string
       \tdatabase to read from/write to (esc)
+    -exclude value
+      \texclude these paths (can be repeated) (esc)
+    -excludere value
+      \texclude paths matching this regexp (can be repeated) (esc)
     -forcetty
       \tforce TTY output (esc)
     -n\tdry-run mode (do not write anything) (esc)
@@ -69,6 +73,10 @@ Too few arguments.
   Flags:
     -db string
       \tdatabase to read from/write to (esc)
+    -exclude value
+      \texclude these paths (can be repeated) (esc)
+    -excludere value
+      \texclude paths matching this regexp (can be repeated) (esc)
     -forcetty
       \tforce TTY output (esc)
     -n\tdry-run mode (do not write anything) (esc)
@@ -107,6 +115,10 @@ No valid path (the argument is given, but it is empty).
   Flags:
     -db string
       \tdatabase to read from/write to (esc)
+    -exclude value
+      \texclude these paths (can be repeated) (esc)
+    -excludere value
+      \texclude paths matching this regexp (can be repeated) (esc)
     -forcetty
       \tforce TTY output (esc)
     -n\tdry-run mode (do not write anything) (esc)
diff --git a/test/test.sh b/test/test.sh
index c52bf39..3360ac8 100755
--- a/test/test.sh
+++ b/test/test.sh
@@ -6,4 +6,6 @@ cd $(realpath "$(dirname "$0")" )
 # shellcheck disable=SC2086
 ( cd ..; go build $BUILDARGS -o summer . )
 
-cram3 ./*.t
+TARGETS="${@:-./*.t}"
+
+cram3 $TARGETS
diff --git a/ui.go b/ui.go
index 1b37192..bc8b3a6 100644
--- a/ui.go
+++ b/ui.go
@@ -167,3 +167,14 @@ func (p *Progress) PrintMatched(path string, cs ChecksumV1) {
 	Verbosef("%q: match (checksum:%x, mtime:%d)",
 		path, cs.CRC32C, cs.ModTimeUsec)
 }
+
+type RepeatedStringFlag []string
+
+func (f *RepeatedStringFlag) String() string {
+	return fmt.Sprintf("%v", *f)
+}
+
+func (f *RepeatedStringFlag) Set(value string) error {
+	*f = append(*f, value)
+	return nil
+}