diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index a2f4d1f..8a18dcc 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -25,6 +25,10 @@ jobs: - name: Download dependencies run: go mod tidy + # Run tests + - name: Run tests + run: go test ./... + # Build Linux - name: Build Linux run: | diff --git a/README.md b/README.md index 3873075..fc684f9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ something that is not easy to achieve with tools like `grep` and `less`. ``` Usage: colorexp [options] patterns... -F, --fixed-strings Do not interpret regular expression metacharacters. - -H, --highlight Color by changing the background color. The default is to change the foreground color. + -h, --no-highlight Do not color by changing the background color. + -H, --only-highlight Only color by changing the background color. -i, --ignore-case Perform case insensitive matching. -g, --vary-group-colors-off Turn off changing of colors for every capturing group. Defaults to on if exactly one pattern is given. -G, --vary-group-colors-on Turn on changing of colors for every capturing group. Defaults to on if exactly one pattern is given. @@ -18,7 +19,7 @@ Usage: colorexp [options] patterns... ## Examples ### Basic Usage -- use the `-H` option to colorize the background, instead of the text +- use the `-h`/`-H` options to only colorize the text, or only the background ![Example](example-basic.png) @@ -39,7 +40,7 @@ Usage: colorexp [options] patterns... - when multiple patterns are given, the default is to use the same colors for all capturing groups of a pattern - in case of a single pattern, the `-g` option can be used to enforce use of a single color -![Example](example-group-same-colors.png) +![Example](example-group-same-color.png) # Installation diff --git a/colorexp.go b/colorexp.go index e66a8d0..0edfb38 100644 --- a/colorexp.go +++ b/colorexp.go @@ -8,7 +8,7 @@ import ( "regexp" ) -const version = "1.0.5" +const version = "1.0.6" var foregroundColors = []string{ //"\033[30m", // Black @@ -105,7 +105,7 @@ func match(line string, regexps []*regexp.Regexp, varyGroupColors bool) []rangeW for i := 0; i < groupsToColorize; i++ { curColorIdx := colorIdx if varyGroupColors { - curColorIdx += i + curColorIdx += groupsToColorize - 1 - i } gIdx := (i + firstGroupToColorize) * 2 matchRangeStart := matchRange[gIdx] @@ -115,19 +115,27 @@ func match(line string, regexps []*regexp.Regexp, varyGroupColors bool) []rangeW } } } - colorIdx += groupsToColorize + if varyGroupColors { + colorIdx += groupsToColorize + } else { + colorIdx++ + } } return ranges } -func colorize(s string, colors []string, resetColor string, ranges []rangeWithID) string { +func colorize(s string, colors [][]string, ranges []rangeWithID, patternColorCount int) string { for i, r := range ranges { - color := colors[r.id%len(colors)] - s = insertString(s, color, r.startIndex) - incRanges(ranges, len(color)) + colorIdx := patternColorCount - r.id - 1 + for colorIdx < 0 { + colorIdx += len(colors) + } + color := colors[colorIdx%len(colors)] + s = insertString(s, color[0], r.startIndex) + incRanges(ranges, len(color[0])) // ranges[i] was modified by incRanges, so we need to use that, not the stale r - s = insertString(s, resetColor, ranges[i].endIndex) - incRanges(ranges, len(resetColor)) + s = insertString(s, color[1], ranges[i].endIndex) + incRanges(ranges, len(color[1])) } return s } @@ -147,7 +155,8 @@ func main() { var ( fixedStrings bool showHelp bool - highlight bool + noHighlight bool + onlyHighlight bool ignoreCase bool showVersion bool varyGroupColorsOn bool @@ -156,8 +165,9 @@ func main() { ) pflag.BoolVarP(&fixedStrings, "fixed-strings", "F", false, "Do not interpret regular expression metacharacters.") - pflag.BoolVarP(&showHelp, "help", "h", false, "Display this help and exit.") - pflag.BoolVarP(&highlight, "highlight", "H", false, "Color by changing the background color. The default is to change the foreground color.") + pflag.BoolVarP(&showHelp, "help", "", false, "Display this help and exit.") + pflag.BoolVarP(&noHighlight, "no-highlight", "h", false, "Do not color by changing the background color.") + pflag.BoolVarP(&onlyHighlight, "only-highlight", "H", false, "Only color by changing the background color.") pflag.BoolVarP(&ignoreCase, "ignore-case", "i", false, "Perform case insensitive matching.") pflag.BoolVarP(&showVersion, "version", "V", false, "Display version information and exit.") @@ -179,14 +189,14 @@ func main() { regexStrings := pflag.Args() if len(regexStrings) == 0 { - _, _ = fmt.Println("Error: At least one pattern argument is required.\n") + _, _ = fmt.Printf("Error: At least one pattern argument is required.\n\n") printUsage() os.Exit(1) } if pflag.Lookup("vary-group-colors-on").Changed { if pflag.Lookup("vary-group-colors-off").Changed { - _, _ = fmt.Println("Error: -G/-g arguments cannot both be used at the same time.\n") + _, _ = fmt.Printf("Error: -g/-G arguments cannot both be used at the same time.\n\n") printUsage() os.Exit(1) } @@ -211,26 +221,40 @@ func main() { _, _ = fmt.Printf("Invalid regular expression: %v\n", err) os.Exit(1) } - // insert at the beginning of the slice, to invert the order, + // insert at the beginning of the slice, to reverse the order, // so that the last regex takes precedence regexps = append([]*regexp.Regexp{re}, regexps...) + //regexps = append(regexps, re) } - var colors []string - var resetColor string - if highlight { - colors = backgroundColors - resetColor = resetBackgroundColor - } else { - colors = foregroundColors - resetColor = resetForegroundColor + var colors [][]string + if !pflag.Lookup("only-highlight").Changed { + for _, foreColor := range foregroundColors { + colors = append(colors, []string{foreColor, resetForegroundColor}) + } + } else if pflag.Lookup("no-highlight").Changed { + _, _ = fmt.Printf("Error: -h/-H arguments cannot both be used at the same time.\n\n") + printUsage() + os.Exit(1) + } + if !pflag.Lookup("no-highlight").Changed { + for _, backColor := range backgroundColors { + colors = append(colors, []string{backColor, resetBackgroundColor}) + } + } + + patternColorCount := len(regexps) + if varyGroupColors { + for _, re := range regexps { + patternColorCount += max(0, re.NumSubexp()-1) + } } scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { line := scanner.Text() ranges := match(line, regexps, varyGroupColors) - colorizedLine := colorize(line, colors, resetColor, ranges) + colorizedLine := colorize(line, colors, ranges, patternColorCount) fmt.Println(colorizedLine) } diff --git a/colorexp_test.go b/colorexp_test.go index 1684e85..0dc6d06 100644 --- a/colorexp_test.go +++ b/colorexp_test.go @@ -86,42 +86,42 @@ func TestAddRange(t *testing.T) { func TestColorize(t *testing.T) { tests := []struct { - name string - s string - colors []string - resetColor string - ranges []rangeWithID - want string + name string + s string + reversedColors [][]string + patternColorCount int + ranges []rangeWithID + want string }{ { - name: "single colorization", - s: "Hello, world!", - colors: []string{"\033[31m"}, - resetColor: "\033[0m", - ranges: []rangeWithID{{7, 12, 0}}, - want: "Hello, \033[31mworld\033[0m!", + name: "single colorization", + s: "Hello, world!", + reversedColors: [][]string{{"\033[31m", "\033[0m"}}, + patternColorCount: 1, + ranges: []rangeWithID{{7, 12, 0}}, + want: "Hello, \033[31mworld\033[0m!", }, { - name: "multiple colorization", - s: "Hello, beautiful world!", - colors: []string{"\033[31m", "\033[32m"}, - resetColor: "\033[0m", - ranges: []rangeWithID{{7, 16, 0}, {17, 22, 1}}, - want: "Hello, \033[31mbeautiful\033[0m \033[32mworld\033[0m!", + name: "multiple colorization", + s: "Hello, beautiful world!", + reversedColors: [][]string{{"\033[32m", "\033[0m"}, {"\033[31m", "\033[0m"}}, + patternColorCount: 2, + ranges: []rangeWithID{{7, 16, 0}, {17, 22, 1}}, + want: "Hello, \033[31mbeautiful\033[0m \033[32mworld\033[0m!", }, { - name: "cycle colors", - s: "Hello, beautiful world!", - colors: []string{"\033[31m", "\033[32m"}, - resetColor: "\033[0m", - ranges: []rangeWithID{{7, 16, 0}, {17, 22, 2}}, - want: "Hello, \033[31mbeautiful\033[0m \033[31mworld\033[0m!", + name: "cycle colors", + s: "Hello, beautiful world!", + reversedColors: [][]string{{"\033[32m", "\033[0m"}, {"\033[31m", "\033[0m"}}, + patternColorCount: 2, + ranges: []rangeWithID{{7, 16, 0}, {17, 22, 2}}, + want: "Hello, \033[31mbeautiful\033[0m \033[31mworld\033[0m!", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := colorize(tt.s, tt.colors, tt.resetColor, tt.ranges) + got := colorize(tt.s, tt.reversedColors, tt.ranges, tt.patternColorCount) if got != tt.want { t.Errorf("colorize() = %v, want %v", got, tt.want) } diff --git a/example-basic.png b/example-basic.png index be13500..8c96233 100644 Binary files a/example-basic.png and b/example-basic.png differ diff --git a/example-group-same-color.png b/example-group-same-color.png new file mode 100644 index 0000000..35eb9ee Binary files /dev/null and b/example-group-same-color.png differ diff --git a/example-group-same-colors.png b/example-group-same-colors.png deleted file mode 100644 index 19a5010..0000000 Binary files a/example-group-same-colors.png and /dev/null differ diff --git a/example-group-varying-colors.png b/example-group-varying-colors.png index 013bcfa..b230074 100644 Binary files a/example-group-varying-colors.png and b/example-group-varying-colors.png differ diff --git a/example-overlaps.png b/example-overlaps.png index aca0635..7f57ffb 100644 Binary files a/example-overlaps.png and b/example-overlaps.png differ