Skip to content

Commit

Permalink
implement capturing groups
Browse files Browse the repository at this point in the history
  • Loading branch information
EugenDueck committed Mar 28, 2024
1 parent b82aad7 commit d77a35a
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 18 deletions.
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,37 @@ something that is not easy to achieve with tools like `grep` and `less`.
# Usage
```
Usage: colorexp [options] patterns...
-F, --fixed-strings Don't interpret regular expression metacharacters.
-H, --highlight Color by changing the background color.
-i, --ignore-case Perform case insensitive matching.
-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.
-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.
```
## Example
![Example](example.png)
## Examples

### Basic Usage
- use the `-H` option to colorize the background, instead of the text

![Example](example-basic.png)

### Overlapping matches - last match wins
- all matches are colorized, and the color of the last match will be used

![Example](example-overlaps.png)

### Capturing groups
- when using capturing groups, only the matched group contents will be colorized
#### Vary colors of groups in patterns
- when exactly one pattern is given, the default is to use different colors for each capturing group
- in case of multiple patterns, the `-G` option can be used to enforce varying of the colors for each group

![Example](example-group-varying-colors.png)

#### Use the same color for all groups of a pattern
- 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)

# Installation

Expand Down
64 changes: 51 additions & 13 deletions colorexp.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"regexp"
)

const version = "1.0.4"
const version = "1.0.5"

var foregroundColors = []string{
//"\033[30m", // Black
Expand Down Expand Up @@ -91,13 +91,31 @@ func addRange(ranges []rangeWithID, newRange rangeWithID) []rangeWithID {
return result
}

func match(line string, regexps []*regexp.Regexp) []rangeWithID {
func match(line string, regexps []*regexp.Regexp, varyGroupColors bool) []rangeWithID {
var ranges []rangeWithID
for colorIdx, re := range regexps {
matchRanges := re.FindAllStringIndex(line, -1)
colorIdx := 0
for _, re := range regexps {
numGroups := re.NumSubexp()
matchRanges := re.FindAllStringSubmatchIndex(line, -1)
firstGroupToColorize := min(1, numGroups)
groupsToColorize := numGroups + 1 - firstGroupToColorize
for _, matchRange := range matchRanges {
ranges = addRange(ranges, rangeWithID{matchRange[0], matchRange[1], colorIdx})
// if there is no capturing group, the full match will be colorized (group 0)
// if there are capturing groups, all groups but group 0 (the full match) will be colorized
for i := 0; i < groupsToColorize; i++ {
curColorIdx := colorIdx
if varyGroupColors {
curColorIdx += i
}
gIdx := (i + firstGroupToColorize) * 2
matchRangeStart := matchRange[gIdx]
matchRangeEnd := matchRange[gIdx+1]
if matchRangeEnd > matchRangeStart {
ranges = addRange(ranges, rangeWithID{matchRangeStart, matchRangeEnd, curColorIdx})
}
}
}
colorIdx += groupsToColorize
}
return ranges
}
Expand Down Expand Up @@ -127,11 +145,14 @@ func printUsage() {

func main() {
var (
fixedStrings bool
showHelp bool
highlight bool
ignoreCase bool
showVersion bool
fixedStrings bool
showHelp bool
highlight bool
ignoreCase bool
showVersion bool
varyGroupColorsOn bool
varyGroupColorsOff bool
varyGroupColors bool
)

pflag.BoolVarP(&fixedStrings, "fixed-strings", "F", false, "Do not interpret regular expression metacharacters.")
Expand All @@ -140,6 +161,9 @@ func main() {
pflag.BoolVarP(&ignoreCase, "ignore-case", "i", false, "Perform case insensitive matching.")
pflag.BoolVarP(&showVersion, "version", "V", false, "Display version information and exit.")

pflag.BoolVarP(&varyGroupColorsOn, "vary-group-colors-on", "G", false, "Turn on changing of colors for every capturing group. Defaults to on if exactly one pattern is given.")
pflag.BoolVarP(&varyGroupColorsOff, "vary-group-colors-off", "g", false, "Turn off changing of colors for every capturing group. Defaults to on if exactly one pattern is given.")

pflag.Parse()

if showVersion {
Expand All @@ -155,11 +179,24 @@ func main() {
regexStrings := pflag.Args()

if len(regexStrings) == 0 {
_, _ = fmt.Println("Error: At least one pattern argument is required.")
_, _ = fmt.Println("Error: At least one pattern argument is required.\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")
printUsage()
os.Exit(1)
}
varyGroupColors = true
} else if pflag.Lookup("vary-group-colors-off").Changed {
varyGroupColors = false
} else {
varyGroupColors = len(regexStrings) == 1
}

// Note that the order in regexps is the reverse of the original order, to implement the "last regexp wins" logic
var regexps []*regexp.Regexp
for _, regexString := range regexStrings {
Expand All @@ -174,7 +211,8 @@ func main() {
_, _ = fmt.Printf("Invalid regular expression: %v\n", err)
os.Exit(1)
}
// insert at the beginning of the slice, to revert the order
// insert at the beginning of the slice, to invert the order,
// so that the last regex takes precedence
regexps = append([]*regexp.Regexp{re}, regexps...)
}

Expand All @@ -191,7 +229,7 @@ func main() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()
ranges := match(line, regexps)
ranges := match(line, regexps, varyGroupColors)
colorizedLine := colorize(line, colors, resetColor, ranges)
fmt.Println(colorizedLine)
}
Expand Down
Binary file added example-basic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example-group-same-colors.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example-group-varying-colors.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example-overlaps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed example.png
Binary file not shown.

0 comments on commit d77a35a

Please sign in to comment.