Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions acceptance.tests
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ STDERR
Matched: 5 / 5
END

NAME search for lines
RUN search -n 5 -e "{1}" "(\d{3}) (\d+)" cmd/testdata/access.txt
cmd/testdata/access.txt 1: 200
cmd/testdata/access.txt 2: 200
cmd/testdata/access.txt 3: 404
cmd/testdata/access.txt 4: 301
cmd/testdata/access.txt 5: 200
STDERR
Matched: 5 / 5
END

NAME filter error
RUN filter notafile
ERR Read errors
Expand Down
1 change: 1 addition & 0 deletions cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var (

var commands []*cli.Command = []*cli.Command{
filterCommand(),
searchCommand(),
histogramCommand(),
heatmapCommand(),
sparkCommand(),
Expand Down
112 changes: 87 additions & 25 deletions cmd/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"bufio"
"os"
"unicode/utf8"

"rare/cmd/helpers"
"rare/pkg/color"
Expand All @@ -11,15 +12,16 @@ import (
"github.com/urfave/cli/v2"
)

func filterFunction(c *cli.Context) error {
func filterFunction(c *cli.Context, fileGlobs ...string) error {
var (
writeLines = c.Bool("line")
customExtractor = c.IsSet("extract")
onlyText = c.Bool("text")
numLineLimit = uint64(c.Int64("num"))
readLines = uint64(0)
)

batcher := helpers.BuildBatcherFromArguments(c)
batcher := helpers.BuildBatcherFromArgumentsEx(c, fileGlobs...)
extractor := helpers.BuildExtractorFromArgumentsEx(c, batcher, "\t")

stdout := bufio.NewWriter(os.Stdout)
Expand All @@ -33,16 +35,18 @@ OUTER_LOOP:
color.WriteUint64(stdout, color.BrightYellow, match.LineNumber)
stdout.WriteString(": ")
}
if !customExtractor {
if len(match.Indices) == 2 {
// Single match, highlight entire phrase
color.WrapIndices(stdout, match.Line, match.Indices)
} else {
// Multi-match groups, highlight individual groups
color.WrapIndices(stdout, match.Line, match.Indices[2:])
}
} else {

switch {
case customExtractor:
stdout.WriteString(match.Extracted)
case onlyText && !utf8.ValidString(match.Line):
color.WriteString(stdout, color.BrightBlue, "Binary Match")
case len(match.Indices) == 2:
// Single match, highlight entire phrase
color.WrapIndices(stdout, match.Line, match.Indices)
default:
// Multi-match groups, highlight individual groups
color.WrapIndices(stdout, match.Line, match.Indices[2:])
}
stdout.WriteByte('\n')

Expand Down Expand Up @@ -74,27 +78,85 @@ OUTER_LOOP:
return helpers.DetermineErrorState(batcher, extractor, nil)
}

func getFilterArgs(isSearch bool) []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
Name: "line",
Aliases: []string{"l"},
Usage: "Output source file and line number",
Value: isSearch,
},
&cli.Int64Flag{
Name: "num",
Aliases: []string{"n"},
Usage: "Print the first NUM of lines seen (Not necessarily in-order)",
},
&cli.BoolFlag{
Name: "text",
Aliases: []string{"a"},
Usage: "Only output lines that contain valid text",
Value: isSearch,
},
}
}

// FilterCommand Exported command
func filterCommand() *cli.Command {
return helpers.AdaptCommandForExtractor(cli.Command{
Name: "filter",
Usage: "Filter incoming results with search criteria, and output raw matches",
Description: `Filters incoming results by a regex, and output the match of a single line
or an extracted expression.`,
Aliases: []string{"f"},
Action: filterFunction,
Category: cmdCatAnalyze,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "line",
Aliases: []string{"l"},
Usage: "Output source file and line number",
},
&cli.Int64Flag{
Name: "num",
Aliases: []string{"n"},
Usage: "Print the first NUM of lines seen (Not necessarily in-order)",
},
Aliases: []string{"f"},
Action: func(ctx *cli.Context) error {
return filterFunction(ctx, ctx.Args().Slice()...)
},
Category: cmdCatAnalyze,
Flags: getFilterArgs(false),
})
}

// Remap some arguments, and pass on to normal filter
func searchFunction(c *cli.Context) error {
fileGlobs := c.Args().Slice()

if len(fileGlobs) == 0 {
logger.Fatal(helpers.ExitCodeInvalidUsage, "Missing match argument")
}

if !c.IsSet("match") && !c.IsSet("dissect") {
c.Set("match", fileGlobs[0])
fileGlobs = fileGlobs[1:]
}

if len(fileGlobs) == 0 {
fileGlobs = append(fileGlobs, ".")
}

return filterFunction(c, fileGlobs...)
}

// Search command is very similar to filter, but with syntactic sugar to make
// it easier to discover things in a directory
func searchCommand() *cli.Command {
command := helpers.AdaptCommandForExtractor(cli.Command{
Name: "search",
Usage: "Searches current directory recursively for a regex match",
Description: `Same as filter, with defaults to easily search with a regex. Alias for: filter -IRla -m`,
Action: searchFunction,
Category: cmdCatAnalyze,
Flags: getFilterArgs(true),
})

command.ArgsUsage = "<regex> " + command.ArgsUsage

// modify some defaults
helpers.ModifyArgOrPanic(command, "recursive", func(flag *cli.BoolFlag) {
flag.Value = true
})
helpers.ModifyArgOrPanic(command, "ignore-case", func(flag *cli.BoolFlag) {
flag.Value = true
})

return command
}
7 changes: 7 additions & 0 deletions cmd/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ func TestFilter(t *testing.T) {
)
}

func TestSarch(t *testing.T) {
testCommandSet(t, searchCommand(),
`the testdata/log.txt`,
`testtest`,
)
}

func TestFilterExtract(t *testing.T) {
out, eout, err := testCommandCapture(filterCommand(), `-m (\d+) -e "{1}" testdata/log.txt`)
assert.NoError(t, err)
Expand Down
17 changes: 15 additions & 2 deletions cmd/helpers/extractorBuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"rare/pkg/matchers/dissect"
"rare/pkg/matchers/fastregex"
"runtime"
"slices"
"strings"

"github.com/urfave/cli/v2"
Expand All @@ -28,6 +29,10 @@ const (
)

func BuildBatcherFromArguments(c *cli.Context) *batchers.Batcher {
return BuildBatcherFromArgumentsEx(c, c.Args().Slice()...)
}

func BuildBatcherFromArgumentsEx(c *cli.Context, fileglobs ...string) *batchers.Batcher {
var (
follow = c.Bool("follow") || c.Bool("reopen")
followTail = c.Bool("tail")
Expand All @@ -52,8 +57,6 @@ func BuildBatcherFromArguments(c *cli.Context) *batchers.Batcher {
logger.Fatalf(ExitCodeInvalidUsage, "Follow (-f) must be enabled for --tail")
}

fileglobs := c.Args().Slice()

if len(fileglobs) == 0 || fileglobs[0] == "-" { // Read from stdin
if gunzip {
logger.Fatalln(ExitCodeInvalidUsage, "Cannot decompress (-z) with stdin")
Expand Down Expand Up @@ -332,3 +335,13 @@ func AdaptCommandForExtractor(command cli.Command) *cli.Command {

return &command
}

func ModifyArgOrPanic[T cli.Flag](cmd *cli.Command, name string, modifier func(T)) {
for _, flag := range cmd.Flags {
if slices.Contains(flag.Names(), name) {
modifier(flag.(T))
return
}
}
panic("no flag change")
}
21 changes: 21 additions & 0 deletions cmd/helpers/extractorBuilder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,27 @@ func TestAdaptingCommandForExtractor(t *testing.T) {
assert.True(t, called)
}

func TestModifyArgument(t *testing.T) {
testFlag := &cli.BoolFlag{
Name: "cat",
}
cmd := AdaptCommandForExtractor(cli.Command{
Flags: []cli.Flag{
testFlag,
},
})

assert.False(t, testFlag.Value)
ModifyArgOrPanic(cmd, "cat", func(arg *cli.BoolFlag) {
arg.Value = true
})
assert.True(t, testFlag.Value)

assert.Panics(t, func() {
ModifyArgOrPanic(cmd, "dog", func(arg *cli.BoolFlag) {})
})
}

func TestBuildingExtractorFromContext(t *testing.T) {
actionCalled := 0
cmdAction := func(c *cli.Context) error {
Expand Down
18 changes: 3 additions & 15 deletions cmd/reduce.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,21 +240,9 @@ func reduceCommand() *cli.Command {
},
})

// Rewrite the default extraction to output array rather than {0} match
{
didInject := false
for _, flag := range cmd.Flags {
if slice, ok := flag.(*cli.StringSliceFlag); ok && slice.Name == "extract" {
slice.Value = cli.NewStringSlice("{@}")
didInject = true
break
}
}

if !didInject { // To catch issues in tests
panic("Unable to inject extract change")
}
}
helpers.ModifyArgOrPanic(cmd, "extract", func(arg *cli.StringSliceFlag) {
arg.Value = cli.NewStringSlice("{@}")
})

return cmd
}
4 changes: 4 additions & 0 deletions docs/images/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ rare filter -n 5 --match "(\d{3}) (\d+)" -e "{1} {2}" access.log

head -n 5 access.log | rare filter -m "(\d{3}) (\d+)"

### Search

rare search --include "*.go" test

### Output json for further analysis
rare filter -n 5 --match "(?P<code>\d{3}) (?P<bytes>\d+)" -e '{.}' access.log

Expand Down
Binary file added docs/images/rare-search.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 33 additions & 1 deletion docs/usage/aggregators.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ and/or `sed`.

Extract out two numbers from `access.log`

```
```sh
$ rare filter -n 4 -m "(\d{3}) (\d+)" -e "{1} {2}" access.log
404 169
404 169
Expand All @@ -36,6 +36,38 @@ Matched: 4 / 4

![Gif of Filter with JSON](../images/rare-json.gif)

## Search

```sh
rare help search
```

### Summary

Search is an alias to `filter` which changes default settings to make it easier
to find text. It is equivalent to `-IRla -m` (ignore-case, recursive, line-numbers, text-only).

Unless a matcher is specified explicitly, the first argument will be assumed to be a regex.

If a file path is not provided, it will search the current working directory.

### Example

```sh
rare search test

# Is equivalent to
rare filter -IRla -m test .
```

And its output will look like:
```
access.log 181: x.x.x.x - - [20/Aug/2019:12:09:43 +0000] "GET /test.php HTTP/1.1" 404 571 "-" "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"
access.log 454: x.x.x.x - - [20/Aug/2019:12:11:23 +0000] "POST /test.php HTTP/1.1" 404 571 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36"
```

![Gif of Search](../images/rare-search.gif)

## Histogram

```sh
Expand Down
Loading