Skip to content

Commit 4f20e43

Browse files
author
Stephen Gutekanst
committed
add 'src search' command for using Sourcegraph search from the command line
This adds a 'src search' subcommand for two primary use cases: 1. Searching from the command line (the default, `src search 'some query'`). 2. Getting results from our search API without having to specify all the GraphQL fields (via the json flag, `src search -json 'some query'`). For usage information, consult `src search -h`: > Usage of 'src search': > -explain-json > Explain the JSON output schema and exit. > -get-curl > Print the curl command for executing this query and exit (WARNING: includes printing your access token!) > -json > Whether or not to output results as JSON > -less > Pipe output to 'less -R' (only if stdout is terminal) (default true) > > Examples: > > Perform a search and get results: > > $ src search 'repogroup:sample error' > > Perform a search and get results as JSON: > > $ src search -json 'repogroup:sample error' > > Other tips: > > Make 'type:diff' searches have colored diffs by installing https://colordiff.org > - Ubuntu/Debian: $ sudo apt-get install colordiff > - Mac OS: $ brew install colordiff > > Disable color output by setting NO_COLOR=t (see https://no-color.org). > > Force color output on (not on by default when piped to other programs) by setting COLOR=t > > Query syntax: https://about.sourcegraph.com/docs/search/query-syntax/ >
1 parent 8c2ce12 commit 4f20e43

22 files changed

Lines changed: 2078 additions & 1 deletion

cmd/src/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.got.txt

cmd/src/colors.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"regexp"
7+
"strconv"
8+
9+
"github.com/mattn/go-isatty"
10+
)
11+
12+
// Returns the string for a foreground ANSI 8 bit color code.
13+
func fg256Color(code int) string {
14+
return fmt.Sprintf("\033[38;5;%dm", code)
15+
}
16+
17+
// Returns the string for a background ANSI 8 bit color code.
18+
func bg256Color(code int) string {
19+
return fmt.Sprintf("\033[48;5;%dm", code)
20+
}
21+
22+
// See https://i.stack.imgur.com/KTSQa.png or https://jonasjacek.github.io/colors/
23+
var ansiColors = map[string]string{
24+
"nc": "\033[0m",
25+
"logo": fg256Color(57),
26+
"warning": fg256Color(124),
27+
"success": fg256Color(2),
28+
29+
// Search-specific colors.
30+
"search-query": fg256Color(68),
31+
"search-border": fg256Color(239),
32+
"search-link": fg256Color(237),
33+
"search-repository": fg256Color(23),
34+
"search-filename": fg256Color(69),
35+
"search-match": fg256Color(0) + bg256Color(2),
36+
"search-line-numbers": fg256Color(69),
37+
"search-commit-author": fg256Color(2),
38+
"search-commit-subject": fg256Color(68),
39+
"search-commit-date": fg256Color(23),
40+
}
41+
42+
// Borrowed from https://github.com/acarl005/stripansi/blob/master/stripansi.go
43+
// MIT licensed, see https://github.com/acarl005/stripansi/blob/master/LICENSE
44+
var ansiRegexp = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))")
45+
46+
var isTest bool
47+
var colorDisabled bool
48+
49+
func init() {
50+
if !isTest {
51+
// We comply with the no-color.org spec here.
52+
if os.Getenv("NO_COLOR") != "" {
53+
colorDisabled = true
54+
} else {
55+
// If they specify COLOR=true or COLOR=false, we respect that.
56+
if color := os.Getenv("COLOR"); color != "" {
57+
colorEnabled, _ := strconv.ParseBool(color)
58+
colorDisabled = !colorEnabled
59+
} else {
60+
// If our program is being piped into another one, then disable
61+
// color. This is usually desired, and can be overridden with COLOR=true.
62+
colorDisabled = !isatty.IsTerminal(os.Stdout.Fd())
63+
}
64+
}
65+
}
66+
if colorDisabled {
67+
for color := range ansiColors {
68+
ansiColors[color] = ""
69+
}
70+
}
71+
72+
if os.Getenv("DEBUG_PRINT_COLORS") == "t" {
73+
fmt.Println("The following colors are available:")
74+
for color, code := range ansiColors {
75+
if color == "nc" {
76+
continue
77+
}
78+
fmt.Println(code + color + ansiColors["nc"])
79+
}
80+
os.Exit(1)
81+
}
82+
}

cmd/src/format.go

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,85 @@ import (
44
"encoding/json"
55
"fmt"
66
"os"
7+
"strings"
78
"text/template"
9+
"time"
10+
11+
humanize "github.com/dustin/go-humanize"
812
)
913

1014
func parseTemplate(text string) (*template.Template, error) {
1115
tmpl := template.New("")
1216
tmpl.Funcs(map[string]interface{}{
17+
"join": strings.Join,
1318
"json": func(v interface{}) (string, error) {
14-
b, err := json.MarshalIndent(v, "", " ")
19+
b, err := marshalIndent(v)
1520
return string(b), err
1621
},
22+
"msDuration": func(ms int) time.Duration {
23+
return time.Duration(ms) * time.Millisecond
24+
},
25+
"repoNames": func(repos []map[string]interface{}) (names []string) {
26+
for _, r := range repos {
27+
names = append(names, r["name"].(string))
28+
}
29+
return
30+
},
31+
"pad": func(value interface{}, padding int, padCharacter string) string {
32+
val := fmt.Sprint(value)
33+
repeat := padding - len(val)
34+
if repeat < 0 {
35+
repeat = 0
36+
}
37+
return strings.Repeat(padCharacter, repeat) + val
38+
},
39+
"padRight": func(value interface{}, padding int, padCharacter string) string {
40+
val := fmt.Sprint(value)
41+
repeat := padding - len(val)
42+
if repeat < 0 {
43+
repeat = 0
44+
}
45+
return val + strings.Repeat(padCharacter, repeat)
46+
},
47+
"indent": func(lines, indention string) string {
48+
split := strings.Split(lines, "\n")
49+
for i, l := range split {
50+
if l != "" {
51+
split[i] = indention + l
52+
}
53+
}
54+
return strings.Join(split, "\n")
55+
},
56+
"addFloat": func(x, y float64) float64 {
57+
return x + y
58+
},
59+
"debug": func(v interface{}) string {
60+
data, _ := marshalIndent(v)
61+
fmt.Println(string(data))
62+
63+
// Template functions must return something. In our case, it is
64+
// useful to actually print the string above now as the template
65+
// could fail due to e.g. syntax errors that someone is trying to
66+
// debug,and we want the spew above to show regardless.
67+
return ""
68+
},
69+
"color": func(name string) string {
70+
return ansiColors[name]
71+
},
72+
"humanizeRFC3339": func(date string) (string, error) {
73+
t, err := time.Parse(time.RFC3339, date)
74+
if err != nil {
75+
return "", err
76+
}
77+
return humanize.Time(t), nil
78+
},
79+
80+
// Register search-specific template functions
81+
"searchSequentialLineNumber": searchTemplateFuncs["searchSequentialLineNumber"],
82+
"searchHighlightMatch": searchTemplateFuncs["searchHighlightMatch"],
83+
"searchHighlightPreview": searchTemplateFuncs["searchHighlightPreview"],
84+
"searchHighlightDiffPreview": searchTemplateFuncs["searchHighlightDiffPreview"],
85+
"searchMaxRepoNameLength": searchTemplateFuncs["searchMaxRepoNameLength"],
1786
})
1887
return tmpl.Parse(text)
1988
}

cmd/src/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ The options are:
2626
2727
The commands are:
2828
29+
search search for results on Sourcegraph
2930
api interacts with the Sourcegraph GraphQL API
3031
repos,repo manages repositories
3132
users,user manages users

0 commit comments

Comments
 (0)