Skip to content

Commit

Permalink
Add console writer for match results (#27)
Browse files Browse the repository at this point in the history
* Add constants for ANSI colour & escape codes

* Add end column & full line fields for found matches

* Add basic console writer for matches

* Fix incorrect match fields used in console writer

* Handle colour output toggle in console writer

* Update exact matcher tests to remove unit-valued offsets for line & column numbers

It is simpler for other parts of the application to treat these values as offsets,
rather than correcting for human/end-user expectations.
Instead, it will be the role of any presentation layer implementation to make such
adjustments as it sees fit.

* Add expected end columns & matching lines to exact matcher test cases

* Fix line & column values to remove unit offsets

* Add end column for matches to results

* Add matching lines to exact matcher results

* Add flags & parsing logic for colour for match results

* Make ANSI escape/colour codes private to package

* Make ANSI escape/colour code type private to package

* Move third-party imports to separate group from in-app imports

* Update go.mod

* Use line number not path as match prefix in console writer

* Add func to create a logger for console-writing purposes

* Add matching & console logging for first fetched result instead of simply printing

* Replace zerolog with simple STDOUT interactions for console writer

Zerolog's console logging is expensive, due to the following:
* The full zerolog JSON log creation is run
* The marshalled JSON is unmarshalled back into Go objects
* These objects are treated as being of type 'any', thus use reflection to be written out

In all, this is significantly more expensive than using STDOUT directly.
Furthermore, the zerolog logger creation adds more complexity than interacting with an
io.Writer (or io.StringWriter), especially considering the existing ANSI colour handling.

* Handle colour toggle for file paths in console logger

* Propagate colour toggle to zerolog logger
  • Loading branch information
agrski authored Jun 9, 2022
1 parent 3651b1c commit b5cbc60
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 28 deletions.
25 changes: 23 additions & 2 deletions cmd/cli/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"errors"
"flag"
"fmt"
"os"
"strings"

"github.com/agrski/greg/pkg/auth"
fetchTypes "github.com/agrski/greg/pkg/fetch/types"
"github.com/agrski/greg/pkg/match"
"github.com/agrski/greg/pkg/types"
"github.com/mattn/go-isatty"
"golang.org/x/oauth2"
)

Expand Down Expand Up @@ -39,8 +41,10 @@ type rawArgs struct {
accessToken string
accessTokenFile string
// Presentation/display behaviour
quiet bool
verbose bool
quiet bool
verbose bool
colour bool
noColour bool
}

type Args struct {
Expand All @@ -49,6 +53,7 @@ type Args struct {
filetypes []types.FileExtension
tokenSource oauth2.TokenSource
verbosity VerbosityLevel
enableColour bool
}

func GetArgs() (*Args, error) {
Expand Down Expand Up @@ -81,12 +86,15 @@ func GetArgs() (*Args, error) {

verbosity := getVerbosity(raw.quiet, raw.verbose)

enableColour := getColourEnabled(raw.colour, raw.noColour)

return &Args{
location: location,
searchPattern: pattern,
filetypes: filetypes,
tokenSource: tokenSource,
verbosity: verbosity,
enableColour: enableColour,
}, nil
}

Expand All @@ -112,6 +120,8 @@ func parseArguments() (*rawArgs, error) {
)
flag.BoolVar(&args.quiet, "quiet", false, "disable logging; overrides verbose mode")
flag.BoolVar(&args.verbose, "verbose", false, "increase logging; overridden by quiet mode")
flag.BoolVar(&args.colour, "colour", false, "force coloured outputs; overridden by no-colour")
flag.BoolVar(&args.noColour, "no-colour", false, "force uncoloured outputs; overrides colour")
flag.Parse()

if 1 != flag.NArg() {
Expand Down Expand Up @@ -247,3 +257,14 @@ func getVerbosity(quiet bool, verbose bool) VerbosityLevel {
return VerbosityNormal
}
}

func getColourEnabled(forceColour bool, forceNoColour bool) bool {
if forceNoColour {
return false
} else if forceColour {
return true
} else {
fd := os.Stdout.Fd()
return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
}
}
21 changes: 16 additions & 5 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,23 @@ import (
"strings"
"time"

"github.com/rs/zerolog"

"github.com/agrski/greg/pkg/fetch"
fetchTypes "github.com/agrski/greg/pkg/fetch/types"
"github.com/rs/zerolog"
"github.com/agrski/greg/pkg/match"
"github.com/agrski/greg/pkg/present/console"
)

func main() {
logger := makeLogger(zerolog.InfoLevel)

args, err := GetArgs()
if err != nil {
logger := makeLogger(zerolog.InfoLevel, false)
logger.Fatal().Err(err).Send()
}

logger := makeLogger(zerolog.InfoLevel, args.enableColour)

switch args.verbosity {
case VerbosityQuiet:
logger = logger.Level(zerolog.Disabled)
Expand All @@ -29,6 +33,10 @@ func main() {
// Already at normal verbosity
}

console := console.New(os.Stdout, args.enableColour)

matcher := match.New(logger, args.filetypes)

fetcher := fetch.New(logger, args.location, args.tokenSource)
uri := makeURI(args.location)

Expand All @@ -41,19 +49,22 @@ func main() {
fetcher.Start()
next, ok := fetcher.Next()
if ok {
fmt.Println(next)
if m, ok := matcher.Match(args.searchPattern, next); ok {
console.Write(next, m)
}
}
fetcher.Stop()
}

func makeLogger(level zerolog.Level) zerolog.Logger {
func makeLogger(level zerolog.Level, enableColour bool) zerolog.Logger {
fieldKeyFormatter := func(v interface{}) string {
return strings.ToUpper(
fmt.Sprintf("%s=", v),
)
}
logWriter := zerolog.ConsoleWriter{
Out: os.Stderr,
NoColor: !enableColour,
TimeFormat: time.RFC3339,
FormatLevel: func(v interface{}) string {
l, ok := v.(string)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.16

require (
github.com/hasura/go-graphql-client v0.6.3
github.com/mattn/go-isatty v0.0.12
github.com/rs/zerolog v1.26.1
github.com/stretchr/testify v1.4.0
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b
Expand Down
17 changes: 10 additions & 7 deletions pkg/match/exact.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,19 @@ func (em *exactMatcher) Match(pattern string, next *types.FileInfo) (*Match, boo
lineReader := bufio.NewScanner(
strings.NewReader(next.Text),
)
row := uint(0)

for lineReader.Scan() {
row++

matchColumns := em.matchLine(pattern, lineReader.Text())
for row := 0; lineReader.Scan(); row++ {
line := lineReader.Text()
matchColumns := em.matchLine(pattern, line)
for _, column := range matchColumns {
match.Positions = append(
match.Positions,
&FilePosition{Line: row, Column: column},
&FilePosition{
Line: uint(row),
ColumnStart: column,
ColumnEnd: column + uint(len(pattern)),
Text: line,
},
)
}
}
Expand All @@ -68,7 +71,7 @@ func (em *exactMatcher) matchLine(pattern string, line string) []uint {
break
} else {
column += offset
matchColumns = append(matchColumns, uint(1+column))
matchColumns = append(matchColumns, uint(column))

column += len(pattern)
line = line[offset+len(pattern):]
Expand Down
36 changes: 24 additions & 12 deletions pkg/match/exact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ func TestMatch(t *testing.T) {
expected: &Match{
Positions: []*FilePosition{
{
Line: 1,
Column: 5,
Line: 0,
ColumnStart: 4,
ColumnEnd: 7,
Text: "foo bar baz",
},
},
},
Expand All @@ -79,8 +81,10 @@ foo
expected: &Match{
Positions: []*FilePosition{
{
Line: 5,
Column: 1,
Line: 4,
ColumnStart: 0,
ColumnEnd: 3,
Text: "foo",
},
},
},
Expand All @@ -99,12 +103,16 @@ foo fifth
expected: &Match{
Positions: []*FilePosition{
{
Line: 2,
Column: 8,
Line: 1,
ColumnStart: 7,
ColumnEnd: 10,
Text: "second foo",
},
{
Line: 5,
Column: 1,
Line: 4,
ColumnStart: 0,
ColumnEnd: 3,
Text: "foo fifth",
},
},
},
Expand All @@ -118,12 +126,16 @@ foo fifth
expected: &Match{
Positions: []*FilePosition{
{
Line: 1,
Column: 1,
Line: 0,
ColumnStart: 0,
ColumnEnd: 3,
Text: "foo bar foo",
},
{
Line: 1,
Column: 9,
Line: 0,
ColumnStart: 8,
ColumnEnd: 11,
Text: "foo bar foo",
},
},
},
Expand Down
6 changes: 4 additions & 2 deletions pkg/match/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ type Match struct {
}

type FilePosition struct {
Line uint
Column uint
Line uint
ColumnStart uint
ColumnEnd uint
Text string
}

type filteringMatcher struct {
Expand Down
61 changes: 61 additions & 0 deletions pkg/present/console/colour.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package console

// ANSI colour codes

type ansiCode string

const (
escape ansiCode = "\u001b"
csi ansiCode = "["
codePrefix = escape + csi
codeSuffix = "m"
)

const (
reset ansiCode = codePrefix + "0" + codeSuffix

bold ansiCode = codePrefix + "1" + codeSuffix
faint ansiCode = codePrefix + "2" + codeSuffix
italic ansiCode = codePrefix + "3" + codeSuffix
underline ansiCode = codePrefix + "4" + codeSuffix
invert ansiCode = codePrefix + "7" + codeSuffix
conceal ansiCode = codePrefix + "8" + codeSuffix
strikethrough ansiCode = codePrefix + "9" + codeSuffix

fgBlack ansiCode = codePrefix + "30" + codeSuffix
fgRed ansiCode = codePrefix + "31" + codeSuffix
fgGreen ansiCode = codePrefix + "32" + codeSuffix
fgYellow ansiCode = codePrefix + "33" + codeSuffix
fgBlue ansiCode = codePrefix + "34" + codeSuffix
fgMagenta ansiCode = codePrefix + "35" + codeSuffix
fgCyan ansiCode = codePrefix + "36" + codeSuffix
fgWhite ansiCode = codePrefix + "37" + codeSuffix
fgDefault ansiCode = codePrefix + "39" + codeSuffix

bgBlack ansiCode = codePrefix + "40" + codeSuffix
bgRed ansiCode = codePrefix + "41" + codeSuffix
bgGreen ansiCode = codePrefix + "42" + codeSuffix
bgYellow ansiCode = codePrefix + "43" + codeSuffix
bgBlue ansiCode = codePrefix + "44" + codeSuffix
bgMagenta ansiCode = codePrefix + "45" + codeSuffix
bgCyan ansiCode = codePrefix + "46" + codeSuffix
bgWhite ansiCode = codePrefix + "47" + codeSuffix

fgIntenseBlack ansiCode = codePrefix + "90" + codeSuffix
fgIntenseRed ansiCode = codePrefix + "91" + codeSuffix
fgIntenseGreen ansiCode = codePrefix + "92" + codeSuffix
fgIntenseYellow ansiCode = codePrefix + "93" + codeSuffix
fgIntenseBlue ansiCode = codePrefix + "94" + codeSuffix
fgIntenseMagenta ansiCode = codePrefix + "95" + codeSuffix
fgIntenseCyan ansiCode = codePrefix + "96" + codeSuffix
fgIntenseWhite ansiCode = codePrefix + "97" + codeSuffix

bgIntenseBlack ansiCode = codePrefix + "100" + codeSuffix
bgIntenseRed ansiCode = codePrefix + "101" + codeSuffix
bgIntenseGreen ansiCode = codePrefix + "102" + codeSuffix
bgIntenseYellow ansiCode = codePrefix + "103" + codeSuffix
bgIntenseBlue ansiCode = codePrefix + "104" + codeSuffix
bgIntenseMagenta ansiCode = codePrefix + "105" + codeSuffix
bgIntenseCyan ansiCode = codePrefix + "106" + codeSuffix
bgIntenseWhite ansiCode = codePrefix + "107" + codeSuffix
)
74 changes: 74 additions & 0 deletions pkg/present/console/console.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package console

import (
"io"
"strconv"
"strings"

"github.com/agrski/greg/pkg/match"
"github.com/agrski/greg/pkg/types"
)

type Console struct {
enableColour bool
out io.StringWriter
}

func New(out io.StringWriter, enableColour bool) *Console {
return &Console{
enableColour: enableColour,
out: out,
}
}

func (c *Console) Write(fileInfo *types.FileInfo, match *match.Match) {
sb := strings.Builder{}

if c.enableColour {
sb.WriteString(string(fgBlue))
sb.WriteString(fileInfo.Path)
sb.WriteString(string(reset))
} else {
sb.WriteString(fileInfo.Path)
}
sb.WriteString("\n")
_, err := c.out.WriteString(sb.String())
if err != nil {
return
}

for _, p := range match.Positions {
sb := strings.Builder{}

line := strconv.Itoa(int(p.Line + 1))

if c.enableColour {
// Line number
sb.WriteString(string(fgMagenta))
sb.WriteString(line)
sb.WriteString(string(reset))
sb.WriteByte(':')
// Text
sb.WriteString(p.Text[:p.ColumnStart])
sb.WriteString(string(fgRed))
sb.WriteString(p.Text[p.ColumnStart:p.ColumnEnd])
sb.WriteString(string(reset))
sb.WriteString(p.Text[p.ColumnEnd:])
} else {
// Line number
sb.WriteString(line)
sb.WriteByte(':')
// Text
sb.WriteString(p.Text)
}

sb.WriteString("\n")

_, err := c.out.WriteString(sb.String())
if err != nil {
return
}
}

_, _ = c.out.WriteString("\n")
}

0 comments on commit b5cbc60

Please sign in to comment.