From 0cadafaac964fd602060658fda5724156f6c64f7 Mon Sep 17 00:00:00 2001
From: Patrick Georgi <pgeorgi@google.com>
Date: Mon, 12 Nov 2018 17:31:48 +0100
Subject: util/scripts/maintainers.go: Use a full glob parser

Instead of checking only for three cases, just use a glob parser (that
translates the glob to regex).

After that, maintainers src/arch/x86/memlayout.h emits:

    src/arch/x86/memlayout.h is in subsystem X86 ARCHITECTURE
    Maintainers:  []
    src/arch/x86/memlayout.h is in subsystem MEMLAYOUT
    Maintainers:  [Julius Werner <jwerner@chromium.org>]

The latter entry was invisible to the maintainers tool because its path
description wasn't in one of the supported formats.

Change-Id: I7e5cf4269415269552e35f2c73952ce3dff487e1
Signed-off-by: Patrick Georgi <pgeorgi@google.com>
Reviewed-on: https://review.coreboot.org/29603
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
---
 util/scripts/maintainers.go | 177 +++++++++++++++++++++++++++-----------------
 1 file changed, 108 insertions(+), 69 deletions(-)

diff --git a/util/scripts/maintainers.go b/util/scripts/maintainers.go
index 0474b77f90..623562bd64 100644
--- a/util/scripts/maintainers.go
+++ b/util/scripts/maintainers.go
@@ -19,13 +19,14 @@ import (
 	"log"
 	"os"
 	"os/exec"
-	"path/filepath"
+	"regexp"
 )
 
 type subsystem struct {
 	name       string
 	maintainer []string
-	file       []string
+	paths      []string
+	globs      []*regexp.Regexp
 }
 
 var subsystems []subsystem
@@ -84,6 +85,19 @@ func get_maintainers() ([]string, error) {
 	return maintainers, nil
 }
 
+func path_to_regexstr(path string) string {
+	// if prefix, allow all subdirectories
+	if path[len(path)-1] == '/' {
+		path += "*"
+	}
+	return glob_to_regex(path)
+}
+
+func path_to_regex(path string) *regexp.Regexp {
+	regexstr := path_to_regexstr(path)
+	return regexp.MustCompile(regexstr)
+}
+
 func build_maintainers(maintainers []string) {
 	var current *subsystem
 	for _, line := range maintainers {
@@ -100,7 +114,9 @@ func build_maintainers(maintainers []string) {
 				current.maintainer = append(current.maintainer, line[3:len(line)])
 			case 'F':
 				// add files
-				current.file = append(current.file, line[3:len(line)])
+				current.paths = append(current.paths, line[3:len(line)])
+				current.globs = append(current.globs, path_to_regex(line[3:len(line)]))
+				break
 			case 'L', 'S', 'T', 'W': // ignore
 			default:
 				fmt.Println("No such specifier: ", line)
@@ -113,78 +129,24 @@ func print_maintainers() {
 	for _, subsystem := range subsystems {
 		fmt.Println(subsystem.name)
 		fmt.Println("  ", subsystem.maintainer)
-		fmt.Println("  ", subsystem.file)
+		fmt.Println("  ", subsystem.paths)
 	}
 }
 
-func match_file(fname string, files []string) (bool, error) {
-	var matched bool
-	var err error
-
-	for _, file := range files {
-		/* Direct match */
-		matched, err = filepath.Match(file, fname)
-		if err != nil {
-			return false, err
-		}
-		if matched {
-			return true, nil
-		}
-
-		/* There are three cases that match_file can handle:
-		 *
-		 *  dirname/filename
-		 *  dirname/*
-		 *  dirname/
-		 *
-		 * The first case is an exact match, the second case is a
-		 * direct match of everything in that directory, and the third
-		 * is a direct match of everything in that directory and its
-		 * subdirectories.
-		 *
-		 * The first two cases are handled above, the code below is
-		 * only for that latter case, so if file doesn't end in /,
-		 * skip to the next file.
-		 */
-		if file[len(file)-1] != '/' {
-			continue
-		}
-
-		/* Remove / because we add it again below */
-		file = file[:len(file)-1]
-
-		/* Maximum tree depth, as calculated by
-		 * $(( `git ls-files | tr -d "[a-z][A-Z][0-9]\-\_\." | \
-		 *     sort -u | tail -1 | wc -c` - 1 ))
-		 * 11
-		 */
-		max_depth := 11
-
-		for i := 0; i < max_depth; i++ {
-			/* Subdirectory match */
-			file += "/*"
-
-			if matched, err = filepath.Match(file, fname); err != nil {
-				return false, err
-			}
-			if matched {
-				return true, nil
-			}
-
+func match_file(fname string, component subsystem) bool {
+	for _, glob := range component.globs {
+		if glob.Match([]byte(fname)) {
+			return true
 		}
 	}
-	return false, nil
+	return false
 }
 
 func find_maintainer(fname string) {
 	var success bool
 
 	for _, subsystem := range subsystems {
-		matched, err := match_file(fname, subsystem.file)
-		if err != nil {
-			log.Fatalf("match_file failed: %v", err)
-			return
-		}
+		matched := match_file(fname, subsystem)
 		if matched && subsystem.name != "THE REST" {
 			success = true
 			fmt.Println(fname, "is in subsystem",
@@ -201,11 +163,7 @@ func find_unmaintained(fname string) {
 	var success bool
 
 	for _, subsystem := range subsystems {
-		matched, err := match_file(fname, subsystem.file)
-		if err != nil {
-			log.Fatalf("match_file failed: %v", err)
-			return
-		}
+		matched := match_file(fname, subsystem)
 		if matched && subsystem.name != "THE REST" {
 			success = true
 			fmt.Println(fname, "is in subsystem",
@@ -217,6 +175,87 @@ func find_unmaintained(fname string) {
 	}
 }
 
+// taken from https://github.com/zyedidia/glob/blob/master/glob.go which is
+// Copyright (c) 2016: Zachary Yedidia.
+// and was published under the MIT "Expat" license.
+//
+// only change: return the string, instead of a compiled golang regex
+func glob_to_regex(glob string) string {
+	regex := ""
+	inGroup := 0
+	inClass := 0
+	firstIndexInClass := -1
+	arr := []byte(glob)
+
+	for i := 0; i < len(arr); i++ {
+		ch := arr[i]
+
+		switch ch {
+		case '\\':
+			i++
+			if i >= len(arr) {
+				regex += "\\"
+			} else {
+				next := arr[i]
+				switch next {
+				case ',':
+					// Nothing
+				case 'Q', 'E':
+					regex += "\\\\"
+				default:
+					regex += "\\"
+				}
+				regex += string(next)
+			}
+		case '*':
+			if inClass == 0 {
+				regex += ".*"
+			} else {
+				regex += "*"
+			}
+		case '?':
+			if inClass == 0 {
+				regex += "."
+			} else {
+				regex += "?"
+			}
+		case '[':
+			inClass++
+			firstIndexInClass = i + 1
+			regex += "["
+		case ']':
+			inClass--
+			regex += "]"
+		case '.', '(', ')', '+', '|', '^', '$', '@', '%':
+			if inClass == 0 || (firstIndexInClass == i && ch == '^') {
+				regex += "\\"
+			}
+			regex += string(ch)
+		case '!':
+			if firstIndexInClass == i {
+				regex += "^"
+			} else {
+				regex += "!"
+			}
+		case '{':
+			inGroup++
+			regex += "("
+		case '}':
+			inGroup--
+			regex += ")"
+		case ',':
+			if inGroup > 0 {
+				regex += "|"
+			} else {
+				regex += ","
+			}
+		default:
+			regex += string(ch)
+		}
+	}
+	return "^" + regex + "$"
+}
+
 func main() {
 	var files []string
 	var maint bool
-- 
cgit v1.2.3