2020-06-17 21:02:45 +00:00
|
|
|
package schema
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"io"
|
|
|
|
"unicode"
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
"github.com/pgavlin/goldmark"
|
|
|
|
"github.com/pgavlin/goldmark/ast"
|
|
|
|
"github.com/pgavlin/goldmark/parser"
|
|
|
|
"github.com/pgavlin/goldmark/text"
|
|
|
|
"github.com/pgavlin/goldmark/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// ExamplesShortcode is the name for the `{{% examples %}}` shortcode, which demarcates a set of example sections.
|
|
|
|
ExamplesShortcode = "examples"
|
|
|
|
|
|
|
|
// ExampleShortcode is the name for the `{{% example %}}` shortcode, which demarcates the content for a single
|
|
|
|
// example.
|
|
|
|
ExampleShortcode = "example"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Shortcode represents a shortcode element and its contents, e.g. `{{% examples %}}`.
|
|
|
|
type Shortcode struct {
|
|
|
|
ast.BaseBlock
|
|
|
|
|
|
|
|
// Name is the name of the shortcode.
|
|
|
|
Name []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Shortcode) Dump(w io.Writer, source []byte, level int) {
|
|
|
|
m := map[string]string{
|
|
|
|
"Name": string(s.Name),
|
|
|
|
}
|
|
|
|
ast.DumpHelper(w, s, source, level, m, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// KindShortcode is an ast.NodeKind for the Shortcode node.
|
|
|
|
var KindShortcode = ast.NewNodeKind("Shortcode")
|
|
|
|
|
|
|
|
// Kind implements ast.Node.Kind.
|
|
|
|
func (*Shortcode) Kind() ast.NodeKind {
|
|
|
|
return KindShortcode
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewShortcode creates a new shortcode with the given name.
|
|
|
|
func NewShortcode(name []byte) *Shortcode {
|
|
|
|
return &Shortcode{Name: name}
|
|
|
|
}
|
|
|
|
|
|
|
|
type shortcodeParser int
|
|
|
|
|
|
|
|
// NewShortcodeParser returns a BlockParser that parses shortcode (e.g. `{{% examples %}}`).
|
|
|
|
func NewShortcodeParser() parser.BlockParser {
|
|
|
|
return shortcodeParser(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (shortcodeParser) Trigger() []byte {
|
|
|
|
return []byte{'{'}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (shortcodeParser) parseShortcode(line []byte, pos int) (int, int, int, bool, bool) {
|
|
|
|
// Look for `{{%` to open the shortcode.
|
|
|
|
text := line[pos:]
|
|
|
|
if len(text) < 3 || text[0] != '{' || text[1] != '{' || text[2] != '%' {
|
|
|
|
return 0, 0, 0, false, false
|
|
|
|
}
|
|
|
|
text, pos = text[3:], pos+3
|
|
|
|
|
|
|
|
// Scan through whitespace.
|
|
|
|
for {
|
|
|
|
if len(text) == 0 {
|
|
|
|
return 0, 0, 0, false, false
|
|
|
|
}
|
|
|
|
|
|
|
|
r, sz := utf8.DecodeRune(text)
|
|
|
|
if !unicode.IsSpace(r) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
text, pos = text[sz:], pos+sz
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for a '/' to indicate that this is a closing shortcode.
|
|
|
|
isClose := false
|
|
|
|
if text[0] == '/' {
|
|
|
|
isClose = true
|
|
|
|
text, pos = text[1:], pos+1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the end of the name and the closing delimiter (`%}}`) for this shortcode.
|
|
|
|
nameStart, nameEnd, inName := pos, pos, true
|
|
|
|
for {
|
|
|
|
if len(text) == 0 {
|
|
|
|
return 0, 0, 0, false, false
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(text) >= 3 && text[0] == '%' && text[1] == '}' && text[2] == '}' {
|
|
|
|
if inName {
|
|
|
|
nameEnd = pos
|
|
|
|
}
|
golangci-lint: Enable staticcheck
Remove staticcheck from the list of disabled linters.
It's enabled by default in golangci-lint.
This also fixes minor remaining staticcheck issues
that don't merit their own pull requests,
or opts out of those that cannot be fixed yet.
Notably, we're opting out of:
- Resource.Name is deprecated (#9469)
- github.com/golang/protobuf is deprecated (#11869)
- strings.Title has been deprecated (#11870)
Besides that, other issues addressed in this change are:
```
// all issues are in pkg
codegen/schema/docs_parser.go:103:4: SA4006: this value of `text` is never used (staticcheck)
codegen/schema/loader.go:253:3: SA9003: empty branch (staticcheck)
resource/deploy/step_executor.go:328:12: SA9003: empty branch (staticcheck)
resource/deploy/step_generator.go:141:10: SA9003: empty branch (staticcheck)
codegen/pcl/invoke.go:97:10: SA9003: empty branch (staticcheck)
codegen/hcl2/model/type_const.go:57:2: SA9003: empty branch (staticcheck)
codegen/hcl2/model/type_enum.go:99:9: SA4001: &*x will be simplified to x. It will not copy x. (staticcheck)
codegen/go/gen_test.go:399:19: SA4017: HasPrefix is a pure function but its return value is ignored (staticcheck)
```
Depends on #11857, #11858, #11859, #11860, #11862, #11865, #11866, #11867, #11868
Resolves #11808
2023-01-11 19:53:41 +00:00
|
|
|
pos = pos + 3
|
|
|
|
// We don't need to update text
|
|
|
|
// because we return after this break.
|
2020-06-17 21:02:45 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
r, sz := utf8.DecodeRune(text)
|
|
|
|
if inName && unicode.IsSpace(r) {
|
|
|
|
nameEnd, inName = pos, false
|
|
|
|
}
|
|
|
|
text, pos = text[sz:], pos+sz
|
|
|
|
}
|
|
|
|
|
|
|
|
return nameStart, nameEnd, pos, isClose, true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p shortcodeParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
|
|
|
|
line, _ := reader.PeekLine()
|
|
|
|
pos := pc.BlockOffset()
|
|
|
|
if pos < 0 {
|
|
|
|
return nil, parser.NoChildren
|
|
|
|
}
|
|
|
|
|
|
|
|
nameStart, nameEnd, shortcodeEnd, isClose, ok := p.parseShortcode(line, pos)
|
|
|
|
if !ok || isClose {
|
|
|
|
return nil, parser.NoChildren
|
|
|
|
}
|
|
|
|
name := line[nameStart:nameEnd]
|
|
|
|
|
|
|
|
reader.Advance(shortcodeEnd)
|
|
|
|
|
|
|
|
return NewShortcode(name), parser.HasChildren
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p shortcodeParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
|
2020-07-16 23:59:06 +00:00
|
|
|
line, seg := reader.PeekLine()
|
2020-06-17 21:02:45 +00:00
|
|
|
pos := pc.BlockOffset()
|
|
|
|
if pos < 0 {
|
|
|
|
return parser.Continue | parser.HasChildren
|
2020-07-16 23:59:06 +00:00
|
|
|
} else if pos > seg.Len() {
|
|
|
|
return parser.Continue | parser.HasChildren
|
2020-06-17 21:02:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
nameStart, nameEnd, shortcodeEnd, isClose, ok := p.parseShortcode(line, pos)
|
|
|
|
if !ok || !isClose {
|
|
|
|
return parser.Continue | parser.HasChildren
|
|
|
|
}
|
|
|
|
|
|
|
|
shortcode := node.(*Shortcode)
|
|
|
|
if !bytes.Equal(line[nameStart:nameEnd], shortcode.Name) {
|
|
|
|
return parser.Continue | parser.HasChildren
|
|
|
|
}
|
|
|
|
|
|
|
|
reader.Advance(shortcodeEnd)
|
|
|
|
return parser.Close
|
|
|
|
}
|
|
|
|
|
|
|
|
func (shortcodeParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
|
|
|
|
}
|
|
|
|
|
|
|
|
// CanInterruptParagraph returns true for shortcodes.
|
|
|
|
func (shortcodeParser) CanInterruptParagraph() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// CanAcceptIndentedLine returns false for shortcodes; all shortcodes must start at the first column.
|
|
|
|
func (shortcodeParser) CanAcceptIndentedLine() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseDocs parses the given documentation text as Markdown with shortcodes and returns the AST.
|
|
|
|
func ParseDocs(docs []byte) ast.Node {
|
|
|
|
p := goldmark.DefaultParser()
|
|
|
|
p.AddOptions(parser.WithBlockParsers(util.Prioritized(shortcodeParser(0), 50)))
|
|
|
|
return p.Parse(text.NewReader(docs))
|
|
|
|
}
|