-
Notifications
You must be signed in to change notification settings - Fork 7
/
search.go
113 lines (100 loc) · 3.02 KB
/
search.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package docsite
import (
"bytes"
"context"
"html"
"html/template"
"net/url"
"strings"
"github.com/russross/blackfriday/v2"
"github.com/sourcegraph/docsite/internal/search"
"github.com/sourcegraph/docsite/internal/search/index"
"github.com/sourcegraph/docsite/internal/search/query"
"github.com/sourcegraph/docsite/markdown"
)
// Search searches all documents at the version for a query.
func (s *Site) Search(ctx context.Context, contentVersion string, queryStr string) (*search.Result, error) {
pages, err := s.AllContentPages(ctx, contentVersion)
if err != nil {
return nil, err
}
idx, err := index.New()
if err != nil {
return nil, err
}
for _, page := range pages {
ast := markdown.NewParser(nil).Parse(page.Data)
data, err := s.renderTextContent(ctx, page, ast, contentVersion)
if err != nil {
return nil, err
}
if err := idx.Add(ctx, index.Document{
ID: index.DocID(page.FilePath),
Title: markdown.GetTitle(ast),
URL: s.Base.ResolveReference(&url.URL{Path: page.Path}).String(),
Data: string(data),
}); err != nil {
return nil, err
}
}
return search.Search(ctx, query.Parse(queryStr), idx)
}
func (s *Site) renderTextContent(ctx context.Context, page *ContentPage, ast *blackfriday.Node, contentVersion string) ([]byte, error) {
// Evaluate <div markdown-func> elements if present (use a heuristic to determine if
// present, to avoid needless work).
maybeHasMarkdownFunc := bytes.Contains(page.Data, []byte("markdown-func"))
if !maybeHasMarkdownFunc {
return page.Data, nil
}
opt := s.markdownOptions(page.FilePath, contentVersion)
ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
switch node.Type {
case blackfriday.HTMLBlock, blackfriday.HTMLSpan:
if entering {
if v, err := markdown.EvalMarkdownFuncs(ctx, node.Literal, opt); err == nil {
page.Data = bytes.Replace(page.Data, node.Literal, v, 1)
}
}
}
return blackfriday.GoToNext
})
return page.Data, nil
}
func (s *Site) renderSearchPage(queryStr string, result *search.Result) ([]byte, error) {
query := query.Parse(queryStr)
tmpl, err := s.getTemplate(s.Templates, searchTemplateName, template.FuncMap{
"highlight": func(text string) template.HTML { return highlight(query, text) },
})
if err != nil {
return nil, err
}
data := struct {
Query string
Result *search.Result
}{
Query: queryStr,
Result: result,
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// highlight returns an HTML fragment with matches of the pattern in text wrapped in <strong>.
func highlight(query query.Query, text string) template.HTML {
var s []string
c := 0
for _, match := range query.FindAllIndex(text) {
start, end := match[0], match[1]
if start > c {
s = append(s, html.EscapeString(text[c:start]))
}
s = append(s, "<strong>"+html.EscapeString(text[start:end])+"</strong>")
c = end
}
if c < len(text) {
s = append(s, html.EscapeString(text[c:]))
}
return template.HTML(strings.Join(s, ""))
}