incorporate code review feedback
This commit is contained in:
parent
43d5345b8b
commit
88887c8d55
7 changed files with 87 additions and 67 deletions
|
|
@ -29,7 +29,7 @@ func NewCmdCode(f *cmdutil.Factory, runF func(*CodeOptions) error) *cobra.Comman
|
||||||
opts := &CodeOptions{
|
opts := &CodeOptions{
|
||||||
Browser: f.Browser,
|
Browser: f.Browser,
|
||||||
IO: f.IOStreams,
|
IO: f.IOStreams,
|
||||||
Query: search.Query{Kind: search.KindCode, TextMatch: true},
|
Query: search.Query{Kind: search.KindCode},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
|
@ -59,9 +59,6 @@ func NewCmdCode(f *cmdutil.Factory, runF func(*CodeOptions) error) *cobra.Comman
|
||||||
# search code matching "cli" in repositories owned by microsoft organization
|
# search code matching "cli" in repositories owned by microsoft organization
|
||||||
$ gh search code cli --owner=microsoft
|
$ gh search code cli --owner=microsoft
|
||||||
|
|
||||||
# search code matching "html" in repositories owned by octocat
|
|
||||||
$ gh search code html --owner=octocat
|
|
||||||
|
|
||||||
# search code matching "panic" in cli repository
|
# search code matching "panic" in cli repository
|
||||||
$ gh search code panic --repo cli/cli
|
$ gh search code panic --repo cli/cli
|
||||||
|
|
||||||
|
|
@ -99,8 +96,8 @@ func NewCmdCode(f *cmdutil.Factory, runF func(*CodeOptions) error) *cobra.Comman
|
||||||
cmd.Flags().StringVar(&opts.Query.Qualifiers.Extension, "extension", "", "Filter on file extension")
|
cmd.Flags().StringVar(&opts.Query.Qualifiers.Extension, "extension", "", "Filter on file extension")
|
||||||
cmd.Flags().StringVar(&opts.Query.Qualifiers.Filename, "filename", "", "Filter on filename")
|
cmd.Flags().StringVar(&opts.Query.Qualifiers.Filename, "filename", "", "Filter on filename")
|
||||||
cmdutil.StringSliceEnumFlag(cmd, &opts.Query.Qualifiers.In, "match", "", nil, []string{"file", "path"}, "Restrict search to file contents or file path")
|
cmdutil.StringSliceEnumFlag(cmd, &opts.Query.Qualifiers.In, "match", "", nil, []string{"file", "path"}, "Restrict search to file contents or file path")
|
||||||
cmd.Flags().StringVarP(&opts.Query.Qualifiers.Language, "language", "l", "", "Filter results by language")
|
cmd.Flags().StringVarP(&opts.Query.Qualifiers.Language, "language", "", "", "Filter results by language")
|
||||||
cmd.Flags().StringSliceVar(&opts.Query.Qualifiers.Repo, "repo", nil, "Filter on repository")
|
cmd.Flags().StringSliceVarP(&opts.Query.Qualifiers.Repo, "repo", "R", nil, "Filter on repository")
|
||||||
cmd.Flags().StringVar(&opts.Query.Qualifiers.Size, "size", "", "Filter on size range, in kilobytes")
|
cmd.Flags().StringVar(&opts.Query.Qualifiers.Size, "size", "", "Filter on size range, in kilobytes")
|
||||||
cmd.Flags().StringSliceVar(&opts.Query.Qualifiers.User, "owner", nil, "Filter on owner")
|
cmd.Flags().StringSliceVar(&opts.Query.Qualifiers.User, "owner", nil, "Filter on owner")
|
||||||
|
|
||||||
|
|
@ -131,7 +128,7 @@ func codeRun(opts *CodeOptions) error {
|
||||||
fmt.Fprintf(io.ErrOut, "failed to start pager: %v\n", err)
|
fmt.Fprintf(io.ErrOut, "failed to start pager: %v\n", err)
|
||||||
}
|
}
|
||||||
if opts.Exporter != nil {
|
if opts.Exporter != nil {
|
||||||
return opts.Exporter.Write(io, results)
|
return opts.Exporter.Write(io, results.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
return displayResults(io, results, opts.Query.Limit)
|
return displayResults(io, results, opts.Query.Limit)
|
||||||
|
|
@ -141,33 +138,28 @@ func displayResults(io *iostreams.IOStreams, results search.CodeResult, limit in
|
||||||
cs := io.ColorScheme()
|
cs := io.ColorScheme()
|
||||||
tp := tableprinter.New(io)
|
tp := tableprinter.New(io)
|
||||||
displayed := 0
|
displayed := 0
|
||||||
|
outer:
|
||||||
for _, code := range results.Items {
|
for _, code := range results.Items {
|
||||||
if displayed == limit {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if io.IsStdoutTTY() {
|
if io.IsStdoutTTY() {
|
||||||
header := fmt.Sprintf("%s %s", cs.GreenBold(code.Repo.FullName), cs.GreenBold(code.Path))
|
header := fmt.Sprintf("%s %s", cs.GreenBold(code.Repo.FullName), cs.GreenBold(code.Path))
|
||||||
tp.AddField(header)
|
tp.AddField(header)
|
||||||
tp.EndRow()
|
tp.EndRow()
|
||||||
}
|
}
|
||||||
row := 0
|
i := 1
|
||||||
for _, textMatch := range code.TextMatches {
|
for _, match := range code.TextMatches {
|
||||||
out, shouldPrint := buildOutput(textMatch, cs)
|
fragments := formatMatch(cs, match)
|
||||||
for i, line := range strings.Split(out, "\n") {
|
for _, fragment := range fragments {
|
||||||
if !shouldPrint[i] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if displayed == limit {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if io.IsStdoutTTY() {
|
if io.IsStdoutTTY() {
|
||||||
tp.AddField(fmt.Sprintf("%s: %s", cs.CyanBold(strconv.Itoa(row+1)), line))
|
tp.AddField(fmt.Sprintf("%s: %s", strconv.Itoa(i), fragment))
|
||||||
} else {
|
} else {
|
||||||
tp.AddField(fmt.Sprintf("%s %s: %s", code.Repo.FullName, code.Path, line))
|
tp.AddField(fmt.Sprintf("%s %s: %s", code.Repo.FullName, code.Path, fragment))
|
||||||
}
|
}
|
||||||
tp.EndRow()
|
tp.EndRow()
|
||||||
row++
|
i++
|
||||||
displayed++
|
displayed++
|
||||||
|
if displayed == limit {
|
||||||
|
break outer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -178,27 +170,32 @@ func displayResults(io *iostreams.IOStreams, results search.CodeResult, limit in
|
||||||
return tp.Render()
|
return tp.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildOutput(tm search.TextMatch, cs *iostreams.ColorScheme) (string, map[int]bool) {
|
func formatMatch(cs *iostreams.ColorScheme, tm search.TextMatch) []string {
|
||||||
shouldHighlight := getHighlightIndices(tm.Matches)
|
shouldHighlight := matchHighlightMask(tm.Fragment, tm.Matches)
|
||||||
linesToPrint := make(map[int]bool)
|
var lines []string
|
||||||
line := 0
|
var found bool
|
||||||
var out strings.Builder
|
var b strings.Builder
|
||||||
for i, c := range tm.Fragment {
|
for i, c := range tm.Fragment {
|
||||||
if shouldHighlight[i] {
|
|
||||||
out.WriteString(cs.CyanBold(string(c)))
|
|
||||||
linesToPrint[line] = true
|
|
||||||
} else {
|
|
||||||
out.WriteRune(c)
|
|
||||||
}
|
|
||||||
if c == '\n' {
|
if c == '\n' {
|
||||||
line++
|
if found {
|
||||||
|
lines = append(lines, b.String())
|
||||||
|
}
|
||||||
|
found = false
|
||||||
|
b.Reset()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if shouldHighlight[i] {
|
||||||
|
b.WriteString(cs.CyanBold(string(c)))
|
||||||
|
found = true
|
||||||
|
} else {
|
||||||
|
b.WriteRune(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out.String(), linesToPrint
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHighlightIndices(matches []search.Match) map[int]bool {
|
func matchHighlightMask(fragment string, matches []search.Match) []bool {
|
||||||
m := make(map[int]bool)
|
m := make([]bool, len(fragment))
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
start := match.Indices[0]
|
start := match.Indices[0]
|
||||||
stop := match.Indices[1]
|
stop := match.Indices[1]
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,9 @@ func TestNewCmdCode(t *testing.T) {
|
||||||
input: "react lifecycle",
|
input: "react lifecycle",
|
||||||
output: CodeOptions{
|
output: CodeOptions{
|
||||||
Query: search.Query{
|
Query: search.Query{
|
||||||
Keywords: []string{"react", "lifecycle"},
|
Keywords: []string{"react", "lifecycle"},
|
||||||
Kind: "code",
|
Kind: "code",
|
||||||
Limit: 30,
|
Limit: 30,
|
||||||
TextMatch: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -44,10 +43,9 @@ func TestNewCmdCode(t *testing.T) {
|
||||||
input: "--web",
|
input: "--web",
|
||||||
output: CodeOptions{
|
output: CodeOptions{
|
||||||
Query: search.Query{
|
Query: search.Query{
|
||||||
Keywords: []string{},
|
Keywords: []string{},
|
||||||
Kind: "code",
|
Kind: "code",
|
||||||
Limit: 30,
|
Limit: 30,
|
||||||
TextMatch: true,
|
|
||||||
},
|
},
|
||||||
WebMode: true,
|
WebMode: true,
|
||||||
},
|
},
|
||||||
|
|
@ -57,10 +55,9 @@ func TestNewCmdCode(t *testing.T) {
|
||||||
input: "--limit 10",
|
input: "--limit 10",
|
||||||
output: CodeOptions{
|
output: CodeOptions{
|
||||||
Query: search.Query{
|
Query: search.Query{
|
||||||
Keywords: []string{},
|
Keywords: []string{},
|
||||||
Kind: "code",
|
Kind: "code",
|
||||||
Limit: 10,
|
Limit: 10,
|
||||||
TextMatch: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -83,10 +80,9 @@ func TestNewCmdCode(t *testing.T) {
|
||||||
`,
|
`,
|
||||||
output: CodeOptions{
|
output: CodeOptions{
|
||||||
Query: search.Query{
|
Query: search.Query{
|
||||||
Keywords: []string{},
|
Keywords: []string{},
|
||||||
Kind: "code",
|
Kind: "code",
|
||||||
Limit: 30,
|
Limit: 30,
|
||||||
TextMatch: true,
|
|
||||||
Qualifiers: search.Qualifiers{
|
Qualifiers: search.Qualifiers{
|
||||||
Extension: "extension",
|
Extension: "extension",
|
||||||
Filename: "filename",
|
Filename: "filename",
|
||||||
|
|
@ -141,7 +137,6 @@ func TestCodeRun(t *testing.T) {
|
||||||
Language: "go",
|
Language: "go",
|
||||||
Repo: []string{"cli/cli"},
|
Repo: []string{"cli/cli"},
|
||||||
},
|
},
|
||||||
TextMatch: true,
|
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
errMsg string
|
errMsg string
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ type Query struct {
|
||||||
Page int
|
Page int
|
||||||
Qualifiers Qualifiers
|
Qualifiers Qualifiers
|
||||||
Sort string
|
Sort string
|
||||||
TextMatch bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Qualifiers struct {
|
type Qualifiers struct {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,14 @@ var CodeFields = []string{
|
||||||
"url",
|
"url",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var TextMatchFields = []string{
|
||||||
|
"fragment",
|
||||||
|
"matches",
|
||||||
|
"url",
|
||||||
|
"type",
|
||||||
|
"property",
|
||||||
|
}
|
||||||
|
|
||||||
var CommitFields = []string{
|
var CommitFields = []string{
|
||||||
"author",
|
"author",
|
||||||
"commit",
|
"commit",
|
||||||
|
|
@ -115,11 +123,11 @@ type Code struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type TextMatch struct {
|
type TextMatch struct {
|
||||||
Fragment string `json:"fragment"`
|
Fragment string `json:"fragment"`
|
||||||
Matches []Match `json:"matches"`
|
Matches []Match `json:"matches"`
|
||||||
ObjectURL string `json:"object_url"`
|
URL string `json:"object_url"`
|
||||||
ObjectType string `json:"object_type"`
|
Type string `json:"object_type"`
|
||||||
Property string `json:"property"`
|
Property string `json:"property"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Match struct {
|
type Match struct {
|
||||||
|
|
@ -274,6 +282,25 @@ func (code Code) ExportData(fields []string) map[string]interface{} {
|
||||||
switch f {
|
switch f {
|
||||||
case "repository":
|
case "repository":
|
||||||
data[f] = code.Repo.ExportData(RepositoryFields)
|
data[f] = code.Repo.ExportData(RepositoryFields)
|
||||||
|
case "textMatches":
|
||||||
|
matches := make([]interface{}, 0, len(code.TextMatches))
|
||||||
|
for _, match := range code.TextMatches {
|
||||||
|
matches = append(matches, match.ExportData(TextMatchFields))
|
||||||
|
}
|
||||||
|
data[f] = matches
|
||||||
|
default:
|
||||||
|
sf := fieldByName(v, f)
|
||||||
|
data[f] = sf.Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (textMatch TextMatch) ExportData(fields []string) map[string]interface{} {
|
||||||
|
v := reflect.ValueOf(textMatch)
|
||||||
|
data := map[string]interface{}{}
|
||||||
|
for _, f := range fields {
|
||||||
|
switch f {
|
||||||
default:
|
default:
|
||||||
sf := fieldByName(v, f)
|
sf := fieldByName(v, f)
|
||||||
data[f] = sf.Interface()
|
data[f] = sf.Interface()
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,13 @@ func TestCodeExportData(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Property: "property",
|
||||||
|
Type: "type",
|
||||||
|
URL: "url",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output: `{"name":"name","path":"path","textMatches":[{"fragment":"fragment","matches":[{"indices":[0,1],"text":"fr"}],"object_url":"","object_type":"","property":""}]}`,
|
output: `{"name":"name","path":"path","textMatches":[{"fragment":"fragment","matches":[{"indices":[0,1],"text":"fr"}],"property":"property","type":"type","url":"url"}]}`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ func (s searcher) search(query Query, result interface{}) (*http.Response, error
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
req.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||||
req.Header.Set("Accept", "application/vnd.github.v3+json")
|
req.Header.Set("Accept", "application/vnd.github.v3+json")
|
||||||
if query.TextMatch {
|
if query.Kind == KindCode {
|
||||||
req.Header.Set("Accept", "application/vnd.github.text-match+json")
|
req.Header.Set("Accept", "application/vnd.github.text-match+json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,9 @@ import (
|
||||||
|
|
||||||
func TestSearcherCode(t *testing.T) {
|
func TestSearcherCode(t *testing.T) {
|
||||||
query := Query{
|
query := Query{
|
||||||
Keywords: []string{"keyword"},
|
Keywords: []string{"keyword"},
|
||||||
Kind: "code",
|
Kind: "code",
|
||||||
Limit: 30,
|
Limit: 30,
|
||||||
TextMatch: true,
|
|
||||||
Qualifiers: Qualifiers{
|
Qualifiers: Qualifiers{
|
||||||
Language: "go",
|
Language: "go",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue