💅 json export
This commit is contained in:
parent
46797e20e5
commit
94a6312eaf
5 changed files with 68 additions and 48 deletions
|
|
@ -31,18 +31,17 @@ func NewCmdCode(f *cmdutil.Factory, runF func(*CodeOptions) error) *cobra.Comman
|
|||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "code [<query>]",
|
||||
Short: "Search for code",
|
||||
Use: "code <query>",
|
||||
Short: "Search within code",
|
||||
Long: heredoc.Doc(`
|
||||
Search for code on GitHub.
|
||||
Search within code in GitHub repositories.
|
||||
|
||||
The command supports constructing queries using the GitHub search syntax,
|
||||
using the parameter and qualifier flags, or a combination of the two.
|
||||
|
||||
At least one search term is required when searching code.
|
||||
|
||||
GitHub search syntax is documented at:
|
||||
The search syntax is documented at:
|
||||
<https://docs.github.com/search-github/searching-on-github/searching-code>
|
||||
|
||||
Note that these search results are powered by what is now a legacy GitHub code search engine.
|
||||
The results might not match what is seen on GitHub.com, and new features like regex search
|
||||
are not yet available via the GitHub API.
|
||||
`),
|
||||
Example: heredoc.Doc(`
|
||||
# search code matching "react" and "lifecycle"
|
||||
|
|
@ -57,7 +56,7 @@ func NewCmdCode(f *cmdutil.Factory, runF func(*CodeOptions) error) *cobra.Comman
|
|||
# search code matching "cli" in repositories owned by microsoft organization
|
||||
$ gh search code cli --owner=microsoft
|
||||
|
||||
# search code matching "panic" in cli repository
|
||||
# search code matching "panic" in the GitHub CLI repository
|
||||
$ gh search code panic --repo cli/cli
|
||||
|
||||
# search code matching keyword "lint" in package.json files
|
||||
|
|
@ -105,6 +104,8 @@ func NewCmdCode(f *cmdutil.Factory, runF func(*CodeOptions) error) *cobra.Comman
|
|||
func codeRun(opts *CodeOptions) error {
|
||||
io := opts.IO
|
||||
if opts.WebMode {
|
||||
// FIXME: convert legacy `filename` and `extension` ES qualifiers to Blackbird's `path` qualifier
|
||||
// when opening web search, otherwise the Blackbird search UI will complain.
|
||||
url := opts.Searcher.URL(opts.Query)
|
||||
if io.IsStdoutTTY() {
|
||||
fmt.Fprintf(io.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(url))
|
||||
|
|
@ -140,7 +141,7 @@ func displayResults(io *iostreams.IOStreams, results search.CodeResult) error {
|
|||
if i > 0 {
|
||||
fmt.Fprint(io.Out, "\n")
|
||||
}
|
||||
fmt.Fprintf(io.Out, "%s %s\n", cs.Blue(code.Repo.FullName), cs.GreenBold(code.Path))
|
||||
fmt.Fprintf(io.Out, "%s %s\n", cs.Blue(code.Repository.FullName), cs.GreenBold(code.Path))
|
||||
for _, match := range code.TextMatches {
|
||||
lines := formatMatch(match.Fragment, match.Matches, io.ColorEnabled())
|
||||
for _, line := range lines {
|
||||
|
|
@ -154,7 +155,7 @@ func displayResults(io *iostreams.IOStreams, results search.CodeResult) error {
|
|||
for _, match := range code.TextMatches {
|
||||
lines := formatMatch(match.Fragment, match.Matches, io.ColorEnabled())
|
||||
for _, line := range lines {
|
||||
fmt.Fprintf(io.Out, "%s:%s: %s\n", cs.Blue(code.Repo.FullName), cs.GreenBold(code.Path), strings.TrimSpace(line))
|
||||
fmt.Fprintf(io.Out, "%s:%s: %s\n", cs.Blue(code.Repository.FullName), cs.GreenBold(code.Path), strings.TrimSpace(line))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ func TestCodeRun(t *testing.T) {
|
|||
{
|
||||
Name: "context.go",
|
||||
Path: "context/context.go",
|
||||
Repo: search.Repository{
|
||||
Repository: search.Repository{
|
||||
FullName: "cli/cli",
|
||||
},
|
||||
TextMatches: []search.TextMatch{
|
||||
|
|
@ -181,7 +181,7 @@ func TestCodeRun(t *testing.T) {
|
|||
{
|
||||
Name: "pr.go",
|
||||
Path: "pkg/cmd/pr/pr.go",
|
||||
Repo: search.Repository{
|
||||
Repository: search.Repository{
|
||||
FullName: "cli/cli",
|
||||
},
|
||||
TextMatches: []search.TextMatch{
|
||||
|
|
@ -218,7 +218,7 @@ func TestCodeRun(t *testing.T) {
|
|||
{
|
||||
Name: "context.go",
|
||||
Path: "context/context.go",
|
||||
Repo: search.Repository{
|
||||
Repository: search.Repository{
|
||||
FullName: "cli/cli",
|
||||
},
|
||||
TextMatches: []search.TextMatch{
|
||||
|
|
@ -240,7 +240,7 @@ func TestCodeRun(t *testing.T) {
|
|||
{
|
||||
Name: "pr.go",
|
||||
Path: "pkg/cmd/pr/pr.go",
|
||||
Repo: search.Repository{
|
||||
Repository: search.Repository{
|
||||
FullName: "cli/cli",
|
||||
},
|
||||
TextMatches: []search.TextMatch{
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package search
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var CodeFields = []string{
|
||||
"name",
|
||||
"path",
|
||||
"repository",
|
||||
"sha",
|
||||
|
|
@ -15,10 +15,9 @@ var CodeFields = []string{
|
|||
"url",
|
||||
}
|
||||
|
||||
var TextMatchFields = []string{
|
||||
var textMatchFields = []string{
|
||||
"fragment",
|
||||
"matches",
|
||||
"url",
|
||||
"type",
|
||||
"property",
|
||||
}
|
||||
|
|
@ -116,7 +115,7 @@ type IssuesResult struct {
|
|||
type Code struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Repo Repository `json:"repository"`
|
||||
Repository Repository `json:"repository"`
|
||||
Sha string `json:"sha"`
|
||||
TextMatches []TextMatch `json:"text_matches"`
|
||||
URL string `json:"html_url"`
|
||||
|
|
@ -125,7 +124,6 @@ type Code struct {
|
|||
type TextMatch struct {
|
||||
Fragment string `json:"fragment"`
|
||||
Matches []Match `json:"matches"`
|
||||
URL string `json:"object_url"`
|
||||
Type string `json:"object_type"`
|
||||
Property string `json:"property"`
|
||||
}
|
||||
|
|
@ -280,12 +278,10 @@ func (code Code) ExportData(fields []string) map[string]interface{} {
|
|||
data := map[string]interface{}{}
|
||||
for _, f := range fields {
|
||||
switch f {
|
||||
case "repository":
|
||||
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))
|
||||
matches = append(matches, match.ExportData(textMatchFields))
|
||||
}
|
||||
data[f] = matches
|
||||
default:
|
||||
|
|
@ -385,6 +381,16 @@ func (repo Repository) ExportData(fields []string) map[string]interface{} {
|
|||
return data
|
||||
}
|
||||
|
||||
func (repo Repository) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"id": repo.ID,
|
||||
"nameWithOwner": repo.FullName,
|
||||
"url": repo.URL,
|
||||
"isPrivate": repo.IsPrivate,
|
||||
"isFork": repo.IsFork,
|
||||
})
|
||||
}
|
||||
|
||||
// The state of an issue or a pull request, may be either open or closed.
|
||||
// For a pull request, the "merged" state is inferred from a value for merged_at and
|
||||
// which we take return instead of the "closed" state.
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ func TestCodeExportData(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
name: "exports requested fields",
|
||||
fields: []string{"name", "path", "textMatches"},
|
||||
fields: []string{"path", "textMatches"},
|
||||
code: Code{
|
||||
Repo: Repository{
|
||||
Repository: Repository{
|
||||
Name: "repo",
|
||||
},
|
||||
Path: "path",
|
||||
|
|
@ -41,11 +41,10 @@ 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"}],"property":"property","type":"type","url":"url"}]}`,
|
||||
output: `{"path":"path","textMatches":[{"fragment":"fragment","matches":[{"indices":[0,1],"text":"fr"}],"property":"property","type":"type"}]}`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
|
|
|||
|
|
@ -358,10 +358,14 @@ func TestSearcherRepositories(t *testing.T) {
|
|||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.QueryMatcher("GET", "search/repositories", values),
|
||||
httpmock.JSONResponse(RepositoriesResult{
|
||||
IncompleteResults: false,
|
||||
Items: []Repository{{Name: "test"}},
|
||||
Total: 1,
|
||||
httpmock.JSONResponse(map[string]interface{}{
|
||||
"incomplete_results": false,
|
||||
"total_count": 1,
|
||||
"items": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "test",
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
|
@ -378,10 +382,14 @@ func TestSearcherRepositories(t *testing.T) {
|
|||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.QueryMatcher("GET", "api/v3/search/repositories", values),
|
||||
httpmock.JSONResponse(RepositoriesResult{
|
||||
IncompleteResults: false,
|
||||
Items: []Repository{{Name: "test"}},
|
||||
Total: 1,
|
||||
httpmock.JSONResponse(map[string]interface{}{
|
||||
"incomplete_results": false,
|
||||
"total_count": 1,
|
||||
"items": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "test",
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
|
@ -396,12 +404,15 @@ func TestSearcherRepositories(t *testing.T) {
|
|||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
firstReq := httpmock.QueryMatcher("GET", "search/repositories", values)
|
||||
firstRes := httpmock.JSONResponse(RepositoriesResult{
|
||||
IncompleteResults: false,
|
||||
Items: []Repository{{Name: "test"}},
|
||||
Total: 2,
|
||||
},
|
||||
)
|
||||
firstRes := httpmock.JSONResponse(map[string]interface{}{
|
||||
"incomplete_results": false,
|
||||
"total_count": 2,
|
||||
"items": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "test",
|
||||
},
|
||||
},
|
||||
})
|
||||
firstRes = httpmock.WithHeader(firstRes, "Link", `<https://api.github.com/search/repositories?page=2&per_page=100&q=org%3Agithub>; rel="next"`)
|
||||
secondReq := httpmock.QueryMatcher("GET", "search/repositories", url.Values{
|
||||
"page": []string{"2"},
|
||||
|
|
@ -411,12 +422,15 @@ func TestSearcherRepositories(t *testing.T) {
|
|||
"q": []string{"keyword stars:>=5 topic:topic"},
|
||||
},
|
||||
)
|
||||
secondRes := httpmock.JSONResponse(RepositoriesResult{
|
||||
IncompleteResults: false,
|
||||
Items: []Repository{{Name: "cli"}},
|
||||
Total: 2,
|
||||
},
|
||||
)
|
||||
secondRes := httpmock.JSONResponse(map[string]interface{}{
|
||||
"incomplete_results": false,
|
||||
"total_count": 2,
|
||||
"items": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "cli",
|
||||
},
|
||||
},
|
||||
})
|
||||
reg.Register(firstReq, firstRes)
|
||||
reg.Register(secondReq, secondRes)
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue