From 5ba845c4bdaa7a4cd67f7391a18d5d0f4dccc50f Mon Sep 17 00:00:00 2001 From: Andrew Morozko Date: Sun, 8 Sep 2024 01:19:17 +0400 Subject: [PATCH 01/12] Bump markdown renderer and goldmark --- go.mod | 3 ++- go.sum | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6b28ed09..a1540177 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/sprig/v3 v3.2.3 github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 + github.com/blackstork-io/goldmark-markdown v0.1.3 github.com/elastic/go-elasticsearch/v8 v8.14.0 github.com/evanphx/go-hclog-slog v0.0.0-20240717231540-be48fc4c4df5 github.com/gobwas/glob v0.2.3 @@ -34,7 +35,7 @@ require ( github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.32.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.32.0 github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.46 - github.com/yuin/goldmark v1.7.1 + github.com/yuin/goldmark v1.7.4 github.com/zclconf/go-cty v1.14.4 go.opentelemetry.io/contrib/bridges/otelslog v0.4.0 go.opentelemetry.io/contrib/instrumentation/host v0.51.0 diff --git a/go.sum b/go.sum index c984f559..58f7d77f 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,8 @@ github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x0 github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/blackstork-io/goldmark-markdown v0.1.3 h1:L8s779mSocytvXAMhypOxqnQFrTUKQlyUjI4ZR+Ni9Q= +github.com/blackstork-io/goldmark-markdown v0.1.3/go.mod h1:0r4jctdZOy9oirGo/NikVBdPL4h+3TSs3e2hYRkGEs8= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= @@ -304,8 +306,8 @@ github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.46/go.mod h1:YCJyt5TSr4luj4 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= -github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= +github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= From 757b6bf3a38d42419f76f4a18267c2d29a8c495a Mon Sep 17 00:00:00 2001 From: Andrew Morozko Date: Fri, 6 Sep 2024 12:54:16 +0400 Subject: [PATCH 02/12] Skip testcontainer tests if docker is missing --- internal/elastic/data_elasticsearch_test.go | 9 ++++++++- internal/postgresql/data_postgresql_test.go | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/internal/elastic/data_elasticsearch_test.go b/internal/elastic/data_elasticsearch_test.go index ff7c1fae..4edd6988 100644 --- a/internal/elastic/data_elasticsearch_test.go +++ b/internal/elastic/data_elasticsearch_test.go @@ -8,6 +8,7 @@ import ( "log/slog" "net/http" "os" + "strings" "testing" es "github.com/elastic/go-elasticsearch/v8" @@ -51,8 +52,14 @@ func (s *IntegrationTestSuite) SetupSuite() { s.ctx, "docker.io/elasticsearch:8.9.0", elasticsearch.WithPassword("password123"), ) + if err != nil { + if strings.Contains(err.Error(), "Cannot connect to the Docker daemon") { + s.T().Skip("Docker not available for integration tests") + } else { + s.Require().NoError(err, "failed to start elasticsearch container") + } + } - s.Require().NoError(err, "failed to start elasticsearch container") s.container = container client, err := es.NewClient(es.Config{ Addresses: []string{ diff --git a/internal/postgresql/data_postgresql_test.go b/internal/postgresql/data_postgresql_test.go index eb6e93d4..01f499e6 100644 --- a/internal/postgresql/data_postgresql_test.go +++ b/internal/postgresql/data_postgresql_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "path/filepath" + "strings" "testing" "time" @@ -49,7 +50,13 @@ func (s *IntegrationTestSuite) SetupSuite() { WithOccurrence(2). WithStartupTimeout(5*time.Second)), ) - s.Require().NoError(err, "failed to start postgres container") + if err != nil { + if strings.Contains(err.Error(), "Cannot connect to the Docker daemon") { + s.T().Skip("Docker not available for integration tests") + } else { + s.Require().NoError(err, "failed to start postgres container") + } + } s.container = container connURL, err := container.ConnectionString(s.ctx, "sslmode=disable") s.Require().NoError(err, "failed to get postgres connection string") From 76d1cab2d51572ffcfcf902cafeb006dc4f5829d Mon Sep 17 00:00:00 2001 From: Andrew Morozko Date: Mon, 30 Sep 2024 04:01:25 +0400 Subject: [PATCH 03/12] Added slog source rewriter --- cmd/root.go | 9 ++-- pkg/diagnostics/diagnostics.go | 4 +- pkg/utils/slogutil/slogutil.go | 71 ++++++++++++++++++++++++++++++++ plugin/pluginapi/v1/is_plugin.go | 6 ++- 4 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 pkg/utils/slogutil/slogutil.go diff --git a/cmd/root.go b/cmd/root.go index 5b007424..ffefc09e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -24,6 +24,7 @@ import ( "github.com/blackstork-io/fabric/cmd/fabctx" "github.com/blackstork-io/fabric/cmd/internal/multilog" "github.com/blackstork-io/fabric/cmd/internal/telemetry" + "github.com/blackstork-io/fabric/pkg/utils/slogutil" ) var ( @@ -147,7 +148,7 @@ var rootCmd = &cobra.Command{ } var logger *slog.Logger if env.otelpEnabled || rawArgs.debug { - logger = slog.New(multilog.Handler{ + handler = multilog.Handler{ Level: level, Handlers: []slog.Handler{ handler, @@ -156,10 +157,10 @@ var rootCmd = &cobra.Command{ otelslog.WithVersion(version), ), }, - }) - } else { - logger = slog.New(handler) + } } + handler = slogutil.NewSourceRewriter(handler) + logger = slog.New(handler) logger = logger.With("command", cmd.Name()) slog.SetDefault(logger) slog.SetLogLoggerLevel(slog.LevelDebug) diff --git a/pkg/diagnostics/diagnostics.go b/pkg/diagnostics/diagnostics.go index 951fabd2..2dcbddf4 100644 --- a/pkg/diagnostics/diagnostics.go +++ b/pkg/diagnostics/diagnostics.go @@ -8,12 +8,14 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/zclconf/go-cty/cty" + + "github.com/blackstork-io/fabric/pkg/utils/slogutil" ) type Diag hcl.Diagnostics // Diagnostics does implement error interface, but not, itself, an error. func (d Diag) Error() string { - slog.Debug("Treated diagnostic.Diag as error") + slog.Debug("Treated diagnostic.Diag as error", slogutil.SourceOverride(1)) return hcl.Diagnostics(d).Error() } diff --git a/pkg/utils/slogutil/slogutil.go b/pkg/utils/slogutil/slogutil.go new file mode 100644 index 00000000..94e61a75 --- /dev/null +++ b/pkg/utils/slogutil/slogutil.go @@ -0,0 +1,71 @@ +package slogutil + +import ( + "context" + "log/slog" + "runtime" +) + +// SourceRewriter is a slog.Handler that rewrites the source of log entries. +type SourceRewriter struct { + slog.Handler +} + +// WithAttrs implements slog.Handler. +func (sr SourceRewriter) WithAttrs(attrs []slog.Attr) slog.Handler { + return SourceRewriter{Handler: sr.Handler.WithAttrs(attrs)} +} + +// WithGroup implements slog.Handler. +func (sr SourceRewriter) WithGroup(name string) slog.Handler { + return SourceRewriter{Handler: sr.Handler.WithGroup(name)} +} + +// NewSourceRewriter returns a new SourceRewriter that wraps the given handler. +// The returned SourceRewriter applies the SourceOverride commands. +func NewSourceRewriter(h slog.Handler) SourceRewriter { + return SourceRewriter{Handler: h} +} + +const sourceKey = "Source Override. Use SourceRewriter handler to apply." + +func (sr SourceRewriter) Handle(ctx context.Context, r slog.Record) error { + var attrCountAfterOverride int + r.Attrs(func(a slog.Attr) bool { + if !(a.Key == sourceKey && a.Value.Kind() == slog.KindUint64) { + attrCountAfterOverride++ + } + return true + }) + if attrCountAfterOverride == r.NumAttrs() { + return sr.Handler.Handle(ctx, r) + } + attrs := make([]slog.Attr, 0, attrCountAfterOverride) + newRecord := slog.Record{ + Time: r.Time, + Message: r.Message, + Level: r.Level, + PC: r.PC, + } + r.Attrs(func(a slog.Attr) bool { + if a.Key == sourceKey && a.Value.Kind() == slog.KindUint64 { + newRecord.PC = uintptr(a.Value.Uint64()) + } else { + attrs = append(attrs, a) + } + return true + }) + newRecord.AddAttrs(attrs...) + return sr.Handler.Handle(ctx, newRecord) +} + +var _ slog.Handler = SourceRewriter{} + +// SourceOverride returns a slog.Attr with the source of the caller at the given offset. +// SourceOverride(0) is the caller of SourceOverride, SourceOverride(1) is the caller of the caller, etc. +func SourceOverride(offset int) slog.Attr { + var pcs [1]uintptr + // skip [runtime.Callers, this function, +offset functions] + runtime.Callers(2+offset, pcs[:]) + return slog.Uint64(sourceKey, uint64(pcs[0])) +} diff --git a/plugin/pluginapi/v1/is_plugin.go b/plugin/pluginapi/v1/is_plugin.go index e52d1212..e745ef65 100644 --- a/plugin/pluginapi/v1/is_plugin.go +++ b/plugin/pluginapi/v1/is_plugin.go @@ -10,6 +10,8 @@ import ( "github.com/evanphx/go-hclog-slog/hclogslog" "github.com/hashicorp/go-hclog" + + "github.com/blackstork-io/fabric/pkg/utils/slogutil" ) var logMutex sync.Mutex @@ -27,7 +29,7 @@ func init() { // Make slog use hclogger // NOTE: slog.SetDefault also calls log.SetOutput, the order of operations is important - slog.SetDefault(slog.New(hclogslog.Adapt(hclog.New(&hclog.LoggerOptions{ + slog.SetDefault(slog.New(slogutil.NewSourceRewriter(hclogslog.Adapt(hclog.New(&hclog.LoggerOptions{ Level: hclog.Trace, Output: os.Stderr, JSONFormat: true, @@ -35,7 +37,7 @@ func init() { IncludeLocation: true, AdditionalLocationOffset: 3, // for slog Mutex: &logMutex, - })))) + }))))) // Make standard logger use hclogger log.SetOutput(hclog.Default().StandardWriter(&hclog.StandardLoggerOptions{ From d2fd38f3ea5cccb7870cbf4f35fb266249270ab0 Mon Sep 17 00:00:00 2001 From: Andrew Morozko Date: Fri, 6 Sep 2024 12:54:16 +0400 Subject: [PATCH 04/12] Util for panicing errors and recovering them --- plugin/ast/v1/bubble_error.go | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 plugin/ast/v1/bubble_error.go diff --git a/plugin/ast/v1/bubble_error.go b/plugin/ast/v1/bubble_error.go new file mode 100644 index 00000000..4a54b24e --- /dev/null +++ b/plugin/ast/v1/bubble_error.go @@ -0,0 +1,43 @@ +package astv1 + +import ( + "errors" +) + +// bubbleError is a wrapper for errors that are meant to be panic'ed +// and caught by the higher level caller. +// This is considered to be a bad practice, but return-error propagation +// is not practical in deeply nested encoding/decoding functions. +// Standard library uses this pattern in some places, i.e. encoding/json. +type bubbleError struct { + err error +} + +// bubbleWrap wraps an error in a bubbleError and returns it (not panics). +func bubbleWrap(err error) bubbleError { + return bubbleError{err: err} +} + +// bubbleUp wraps an error in a bubbleError and panics it. +func bubbleUp(err error) { + panic(bubbleWrap(err)) +} + +// recoverBubbleError expects to be called in a defer statement with +// the current error and a recover(). If bubbleError is recovered, it +// would be [errors.Join]'ed with the current error (if non nil) and +// returned. +// If there was no panic, the passed in error would be returned. +// If there is any other panic, it would be re-panicked. +func recoverBubbleError(err error, recovered any) error { + if recovered == nil { + return err + } + if pErr, ok := recovered.(bubbleError); ok { + if err != nil { + return errors.Join(err, pErr.err) + } + return pErr.err + } + panic(recovered) +} From e7b03bf5af92f2c1de50a5ae5b1674b97f84eb88 Mon Sep 17 00:00:00 2001 From: Andrew Morozko Date: Fri, 6 Sep 2024 12:54:16 +0400 Subject: [PATCH 05/12] AST encoder, decoder and builder --- plugin/ast/astsrc/astsrc.go | 47 + plugin/ast/builder.go | 306 ++ plugin/ast/nodes/content_node.go | 53 + plugin/ast/nodes/custom_nodes.go | 78 + plugin/ast/nodes/metadata.go | 20 + plugin/ast/v1/ast.pb.go | 3085 +++++++++++++++++ plugin/ast/v1/ast_decoder.go | 263 ++ plugin/ast/v1/ast_encoder.go | 390 +++ plugin/ast/v1/ast_test.go | 98 + plugin/ast/v1/enum_codec.go | 124 + plugin/ast/v1/interfaces.go | 319 ++ .../fuzz/FuzzEncoder/6b5e47038908d6af | 2 + plugin/content.go | 207 +- plugin/pluginapi/v1/content.pb.go | 149 +- plugin/pluginapi/v1/content_decoder.go | 53 +- plugin/pluginapi/v1/content_encoder.go | 77 +- plugin/schema.go | 4 +- print/htmlprint/printer.go | 28 +- print/mdprint/printer.go | 57 +- print/pdfprint/printer.go | 34 +- print/printer.go | 50 + proto/ast/v1/ast.proto | 232 ++ proto/pluginapi/v1/content.proto | 10 +- 23 files changed, 5472 insertions(+), 214 deletions(-) create mode 100644 plugin/ast/astsrc/astsrc.go create mode 100644 plugin/ast/builder.go create mode 100644 plugin/ast/nodes/content_node.go create mode 100644 plugin/ast/nodes/custom_nodes.go create mode 100644 plugin/ast/nodes/metadata.go create mode 100644 plugin/ast/v1/ast.pb.go create mode 100644 plugin/ast/v1/ast_decoder.go create mode 100644 plugin/ast/v1/ast_encoder.go create mode 100644 plugin/ast/v1/ast_test.go create mode 100644 plugin/ast/v1/enum_codec.go create mode 100644 plugin/ast/v1/interfaces.go create mode 100644 plugin/ast/v1/testdata/fuzz/FuzzEncoder/6b5e47038908d6af create mode 100644 proto/ast/v1/ast.proto diff --git a/plugin/ast/astsrc/astsrc.go b/plugin/ast/astsrc/astsrc.go new file mode 100644 index 00000000..0bc0fd01 --- /dev/null +++ b/plugin/ast/astsrc/astsrc.go @@ -0,0 +1,47 @@ +package astsrc + +import ( + "fmt" + + "github.com/yuin/goldmark/text" +) + +// ASTSource holds the source of the markdown AST (a read-only byte slice). +type ASTSource []byte + +// Append appends bytes to the source and returns corresponding segment. +func (s *ASTSource) Append(data []byte) text.Segment { + start := len(*s) + *s = append(*s, data...) + return text.NewSegment(start, len(*s)) +} + +// AppendMultiple appends multiple byte slices to the source and returns corresponding segments. +func (s *ASTSource) AppendMultiple(data [][]byte) *text.Segments { + values := make([]text.Segment, len(data)) + for i, segment := range data { + values[i] = s.Append(segment) + } + res := text.NewSegments() + res.AppendAll(values) + return res +} + +// AppendString appends a string to the source and returns corresponding segment. +func (s *ASTSource) AppendString(data string) text.Segment { + return s.Append([]byte(data)) +} + +// Appendf appends formatted string to the source and returns corresponding segment. +func (s *ASTSource) Appendf(format string, args ...interface{}) text.Segment { + start := len(*s) + *s = fmt.Appendf(*s, format, args...) + return text.NewSegment(start, len(*s)) +} + +// AsBytes returns the source as a byte slice. +// Returned bytes should be treated as read-only or modified with care, +// ensuring that the offsets are not changed. +func (s ASTSource) AsBytes() []byte { + return s +} diff --git a/plugin/ast/builder.go b/plugin/ast/builder.go new file mode 100644 index 00000000..4297767c --- /dev/null +++ b/plugin/ast/builder.go @@ -0,0 +1,306 @@ +package ast + +import ( + "bytes" + "regexp" + + astv1 "github.com/blackstork-io/fabric/plugin/ast/v1" +) + +// inlines + +func CodeSpan(code []byte) *astv1.Node_CodeSpan { + return &astv1.Node_CodeSpan{ + CodeSpan: &astv1.CodeSpan{ + Base: &astv1.BaseNode{ + Children: []*astv1.Node{{ + Kind: &astv1.Node_Text{ + Text: &astv1.Text{ + Base: &astv1.BaseNode{}, + Segment: code, + Raw: true, + }, + }, + }}, + }, + }, + } +} + +func emphasis(level int64, children []astv1.InlineContent) *astv1.Node_Emphasis { + return &astv1.Node_Emphasis{ + Emphasis: &astv1.Emphasis{ + Base: &astv1.BaseNode{ + Children: astv1.Inlines.ExtendNodes(children, nil), + }, + Level: level, + }, + } +} + +func Emphasis(children ...astv1.InlineContent) *astv1.Node_Emphasis { + return emphasis(1, children) +} + +var Italic = Emphasis + +func StrongEmphasis(children ...astv1.InlineContent) *astv1.Node_Emphasis { + return emphasis(2, children) +} + +var Bold = StrongEmphasis + +func Strikethrough(children ...astv1.InlineContent) *astv1.Node_Strikethrough { + return &astv1.Node_Strikethrough{ + Strikethrough: &astv1.Strikethrough{ + Base: &astv1.BaseNode{ + Children: astv1.Inlines.ExtendNodes(children, nil), + }, + }, + } +} + +func Link(text ...astv1.InlineContent) *astv1.LinkOrImage { + return &astv1.LinkOrImage{ + Base: &astv1.BaseNode{ + Children: astv1.Inlines.ExtendNodes(text, nil), + }, + IsImage: false, + } +} + +func Image(alt ...astv1.InlineContent) *astv1.LinkOrImage { + return &astv1.LinkOrImage{ + Base: &astv1.BaseNode{ + Children: astv1.Inlines.ExtendNodes(alt, nil), + }, + IsImage: true, + } +} + +func AutoLink(url string) *astv1.Node_AutoLink { + before, after, found := bytes.Cut([]byte(url), []byte("://")) + if found { + return &astv1.Node_AutoLink{ + AutoLink: &astv1.AutoLink{ + Base: &astv1.BaseNode{}, + Type: astv1.AutoLinkType_AUTO_LINK_TYPE_URL, + Protocol: before, + Value: after, + }, + } + } else { + return &astv1.Node_AutoLink{ + AutoLink: &astv1.AutoLink{ + Base: &astv1.BaseNode{}, + Type: astv1.AutoLinkType_AUTO_LINK_TYPE_EMAIL, + Value: before, + }, + } + } +} + +func AutoLinkEmail(email string) *astv1.Node_AutoLink { + return &astv1.Node_AutoLink{ + AutoLink: &astv1.AutoLink{ + Base: &astv1.BaseNode{}, + Type: astv1.AutoLinkType_AUTO_LINK_TYPE_EMAIL, + Value: []byte(email), + }, + } +} + +func InlineHTML(html string) *astv1.Node_RawHtml { + return &astv1.Node_RawHtml{ + RawHtml: &astv1.RawHTML{ + Base: &astv1.BaseNode{}, + Segments: [][]byte{[]byte(html)}, + }, + } +} + +func LineBreak() *astv1.Node_Text { + return &astv1.Node_Text{ + Text: &astv1.Text{ + Base: &astv1.BaseNode{}, + Segment: nil, + HardLineBreak: true, + }, + } +} + +var textHardBreakRegexp = regexp.MustCompile(`( {2,}\n *|\\\n *)`) + +func splitBytes(re *regexp.Regexp, s []byte, n int) [][]byte { + if n == 0 { + return nil + } + + if len(s) == 0 { + return [][]byte{nil} + } + + matches := re.FindAllIndex(s, n) + subBytes := make([][]byte, 0, len(matches)) + + beg := 0 + end := 0 + for _, match := range matches { + if n > 0 && len(subBytes) >= n-1 { + break + } + + end = match[0] + if match[1] != 0 { + subBytes = append(subBytes, s[beg:end]) + } + beg = match[1] + } + + if end != len(s) { + subBytes = append(subBytes, s[beg:]) + } + + return subBytes +} + +func convertSoftBreaks(txt []byte) (res []*astv1.Text) { + split := bytes.Split(txt, []byte("\n")) + res = make([]*astv1.Text, 0, len(split)) + res = append(res, &astv1.Text{ + Segment: split[0], + }) + for i, s := range split[1:] { + res[i].SoftLineBreak = true + res = append(res, &astv1.Text{ + Segment: s, + }) + } + return +} + +func convertHardBreaks(txt []byte) (res []*astv1.Text) { + split := splitBytes(textHardBreakRegexp, txt, -1) + res = make([]*astv1.Text, 0, len(split)) + res = append(res, convertSoftBreaks(split[0])...) + for i, s := range split[1:] { + res[i].HardLineBreak = true + res = append(res, convertSoftBreaks(s)...) + } + return +} + +func Text(text string) (res astv1.Inlines) { + txt := convertHardBreaks([]byte(text)) + res = make(astv1.Inlines, len(txt)) + for i, t := range txt { + res[i] = &astv1.Node_Text{ + Text: t, + } + } + return +} + +// container blocks +func Blockquote(children ...astv1.BlockContent) *astv1.Node_Blockquote { + return &astv1.Node_Blockquote{ + Blockquote: &astv1.Blockquote{ + Base: &astv1.BaseNode{ + Children: astv1.Blocks.ExtendNodes(children, nil), + }, + }, + } +} + +type listMarker uint32 + +const ( + Period listMarker = '.' + Paren listMarker = ')' + Star listMarker = '*' + Plus listMarker = '+' + Hyphen listMarker = '-' +) + +func List(marker listMarker) *astv1.Node_List { + return &astv1.Node_List{ + List: &astv1.List{ + Base: &astv1.BaseNode{}, + Marker: uint32(marker), + }, + } +} + +func ThematicBreak() *astv1.Node_ThematicBreak { + return &astv1.Node_ThematicBreak{ + ThematicBreak: &astv1.ThematicBreak{ + Base: &astv1.BaseNode{}, + }, + } +} + +func Header(level uint32, children ...astv1.InlineContent) *astv1.Node_Heading { + return &astv1.Node_Heading{ + Heading: &astv1.Heading{ + Base: &astv1.BaseNode{ + Children: astv1.Inlines.ExtendNodes(children, nil), + }, + Level: max(1, min(level, 6)), + }, + } +} + +func IndentedCodeBlock(code []byte) *astv1.Node_CodeBlock { + return &astv1.Node_CodeBlock{ + CodeBlock: &astv1.CodeBlock{ + Base: &astv1.BaseNode{}, + Lines: bytes.Split(code, []byte("\n")), + }, + } +} + +func FencedCodeBlock(code []byte) *astv1.Node_FencedCodeBlock { + return &astv1.Node_FencedCodeBlock{ + FencedCodeBlock: &astv1.FencedCodeBlock{ + Base: &astv1.BaseNode{}, + Info: nil, + Lines: bytes.Split(code, []byte("\n")), + }, + } +} + +func HTMLBlock(html []byte) *astv1.Node_HtmlBlock { + return &astv1.Node_HtmlBlock{ + HtmlBlock: &astv1.HTMLBlock{ + Base: &astv1.BaseNode{}, + // setting the value to most general type, hopefully renderers don't care + Type: astv1.HTMLBlockType_HTML_BLOCK_TYPE_7, + Lines: bytes.Split(html, []byte("\n")), + }, + } +} + +func Paragraph(children ...astv1.InlineContent) *astv1.Node_Paragraph { + return &astv1.Node_Paragraph{ + Paragraph: &astv1.Paragraph{ + Base: &astv1.BaseNode{ + Children: astv1.Inlines.ExtendNodes(children, nil), + }, + }, + } +} + +var ( + AlignLeft = astv1.CellAlignment_CELL_ALIGNMENT_LEFT + AlignRight = astv1.CellAlignment_CELL_ALIGNMENT_RIGHT + AlignCenter = astv1.CellAlignment_CELL_ALIGNMENT_CENTER + AlignNone = astv1.CellAlignment_CELL_ALIGNMENT_NONE +) + +func Table() *astv1.Node_Table { + return &astv1.Node_Table{ + Table: &astv1.Table{ + Base: &astv1.BaseNode{}, + }, + } +} diff --git a/plugin/ast/nodes/content_node.go b/plugin/ast/nodes/content_node.go new file mode 100644 index 00000000..6d57273b --- /dev/null +++ b/plugin/ast/nodes/content_node.go @@ -0,0 +1,53 @@ +package nodes + +import "github.com/yuin/goldmark/ast" + +type FabricContentNode struct { + ast.BaseBlock + Meta *ContentMeta +} + +func ToFabricContentNode(node ast.Node) (meta *FabricContentNode) { + switch n := node.(type) { + case *FabricContentNode: + meta = n + case *ast.Document: + meta = &FabricContentNode{} + child := n.FirstChild() + for child != nil { + c := child + child = child.NextSibling() + meta.AppendChild(meta, c) + } + case nil: + // meta is nil + default: + meta = &FabricContentNode{} + meta.AppendChild(meta, n) + } + return +} + +// Dump implements ast.Node. +func (m *FabricContentNode) Dump(source []byte, level int) { + var kv map[string]string + if m == nil || m.Meta == nil { + kv = map[string]string{ + "meta": "nil", + } + } else { + kv = map[string]string{ + "meta.provider": m.Meta.Provider, + "meta.plugin": m.Meta.Plugin, + "meta.version": m.Meta.Version, + } + } + ast.DumpHelper(m, source, level, kv, nil) +} + +// Kind implements ast.Node. +func (m *FabricContentNode) Kind() ast.NodeKind { + return ContentNodeKind +} + +var _ ast.Node = &FabricContentNode{} diff --git a/plugin/ast/nodes/custom_nodes.go b/plugin/ast/nodes/custom_nodes.go new file mode 100644 index 00000000..283246fa --- /dev/null +++ b/plugin/ast/nodes/custom_nodes.go @@ -0,0 +1,78 @@ +package nodes + +import ( + "github.com/yuin/goldmark/ast" + "google.golang.org/protobuf/types/known/anypb" +) + +var ( + ContentNodeKind = ast.NewNodeKind("FabricContentNode") + CustomBlockKind = ast.NewNodeKind("FabricCustomBlock") + CustomInlineKind = ast.NewNodeKind("FabricCustomInline") +) + +func NewCustomNode(isInline bool, data *anypb.Any) ast.Node { + if isInline { + return &CustomInline{ + Data: data, + } + } else { + return &CustomBlock{ + Data: data, + } + } +} + +type CustomInline struct { + ast.BaseInline + Data *anypb.Any +} + +var _ ast.Node = &CustomInline{} + +// Kind implements ast.Node. +func (o *CustomInline) Kind() ast.NodeKind { + return CustomInlineKind +} + +// Dump implements ast.Node. +func (o *CustomInline) Dump(source []byte, level int) { + var kv map[string]string + if o == nil || o.Data == nil { + kv = map[string]string{ + "other": "nil", + } + } else { + kv = map[string]string{ + "other.TypeUrl": o.Data.GetTypeUrl(), + } + } + ast.DumpHelper(o, source, level, kv, nil) +} + +type CustomBlock struct { + ast.BaseBlock + Data *anypb.Any +} + +var _ ast.Node = &CustomBlock{} + +// Kind implements ast.Node. +func (o *CustomBlock) Kind() ast.NodeKind { + return CustomBlockKind +} + +// Dump implements ast.Node. +func (o *CustomBlock) Dump(source []byte, level int) { + var kv map[string]string + if o == nil || o.Data == nil { + kv = map[string]string{ + "other": "nil", + } + } else { + kv = map[string]string{ + "other.TypeUrl": o.Data.GetTypeUrl(), + } + } + ast.DumpHelper(o, source, level, kv, nil) +} diff --git a/plugin/ast/nodes/metadata.go b/plugin/ast/nodes/metadata.go new file mode 100644 index 00000000..bdbdce49 --- /dev/null +++ b/plugin/ast/nodes/metadata.go @@ -0,0 +1,20 @@ +package nodes + +import "github.com/blackstork-io/fabric/plugin/plugindata" + +type ContentMeta struct { + Provider string + Plugin string + Version string +} + +func (meta *ContentMeta) AsData() plugindata.Data { + if meta == nil { + return nil + } + return plugindata.Map{ + "provider": plugindata.String(meta.Provider), + "plugin": plugindata.String(meta.Plugin), + "version": plugindata.String(meta.Version), + } +} diff --git a/plugin/ast/v1/ast.pb.go b/plugin/ast/v1/ast.pb.go new file mode 100644 index 00000000..4c662af5 --- /dev/null +++ b/plugin/ast/v1/ast.pb.go @@ -0,0 +1,3085 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc (unknown) +// source: ast/v1/ast.proto + +package astv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type HTMLBlockType int32 + +const ( + HTMLBlockType_HTML_BLOCK_TYPE_UNSPECIFIED HTMLBlockType = 0 + HTMLBlockType_HTML_BLOCK_TYPE_1 HTMLBlockType = 1 + HTMLBlockType_HTML_BLOCK_TYPE_2 HTMLBlockType = 2 + HTMLBlockType_HTML_BLOCK_TYPE_3 HTMLBlockType = 3 + HTMLBlockType_HTML_BLOCK_TYPE_4 HTMLBlockType = 4 + HTMLBlockType_HTML_BLOCK_TYPE_5 HTMLBlockType = 5 + HTMLBlockType_HTML_BLOCK_TYPE_6 HTMLBlockType = 6 + HTMLBlockType_HTML_BLOCK_TYPE_7 HTMLBlockType = 7 +) + +// Enum value maps for HTMLBlockType. +var ( + HTMLBlockType_name = map[int32]string{ + 0: "HTML_BLOCK_TYPE_UNSPECIFIED", + 1: "HTML_BLOCK_TYPE_1", + 2: "HTML_BLOCK_TYPE_2", + 3: "HTML_BLOCK_TYPE_3", + 4: "HTML_BLOCK_TYPE_4", + 5: "HTML_BLOCK_TYPE_5", + 6: "HTML_BLOCK_TYPE_6", + 7: "HTML_BLOCK_TYPE_7", + } + HTMLBlockType_value = map[string]int32{ + "HTML_BLOCK_TYPE_UNSPECIFIED": 0, + "HTML_BLOCK_TYPE_1": 1, + "HTML_BLOCK_TYPE_2": 2, + "HTML_BLOCK_TYPE_3": 3, + "HTML_BLOCK_TYPE_4": 4, + "HTML_BLOCK_TYPE_5": 5, + "HTML_BLOCK_TYPE_6": 6, + "HTML_BLOCK_TYPE_7": 7, + } +) + +func (x HTMLBlockType) Enum() *HTMLBlockType { + p := new(HTMLBlockType) + *p = x + return p +} + +func (x HTMLBlockType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (HTMLBlockType) Descriptor() protoreflect.EnumDescriptor { + return file_ast_v1_ast_proto_enumTypes[0].Descriptor() +} + +func (HTMLBlockType) Type() protoreflect.EnumType { + return &file_ast_v1_ast_proto_enumTypes[0] +} + +func (x HTMLBlockType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use HTMLBlockType.Descriptor instead. +func (HTMLBlockType) EnumDescriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{0} +} + +type AutoLinkType int32 + +const ( + AutoLinkType_AUTO_LINK_TYPE_UNSPECIFIED AutoLinkType = 0 + AutoLinkType_AUTO_LINK_TYPE_EMAIL AutoLinkType = 1 + AutoLinkType_AUTO_LINK_TYPE_URL AutoLinkType = 2 +) + +// Enum value maps for AutoLinkType. +var ( + AutoLinkType_name = map[int32]string{ + 0: "AUTO_LINK_TYPE_UNSPECIFIED", + 1: "AUTO_LINK_TYPE_EMAIL", + 2: "AUTO_LINK_TYPE_URL", + } + AutoLinkType_value = map[string]int32{ + "AUTO_LINK_TYPE_UNSPECIFIED": 0, + "AUTO_LINK_TYPE_EMAIL": 1, + "AUTO_LINK_TYPE_URL": 2, + } +) + +func (x AutoLinkType) Enum() *AutoLinkType { + p := new(AutoLinkType) + *p = x + return p +} + +func (x AutoLinkType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AutoLinkType) Descriptor() protoreflect.EnumDescriptor { + return file_ast_v1_ast_proto_enumTypes[1].Descriptor() +} + +func (AutoLinkType) Type() protoreflect.EnumType { + return &file_ast_v1_ast_proto_enumTypes[1] +} + +func (x AutoLinkType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AutoLinkType.Descriptor instead. +func (AutoLinkType) EnumDescriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{1} +} + +type CellAlignment int32 + +const ( + CellAlignment_CELL_ALIGNMENT_UNSPECIFIED CellAlignment = 0 + CellAlignment_CELL_ALIGNMENT_LEFT CellAlignment = 1 + CellAlignment_CELL_ALIGNMENT_RIGHT CellAlignment = 2 + CellAlignment_CELL_ALIGNMENT_CENTER CellAlignment = 3 + CellAlignment_CELL_ALIGNMENT_NONE CellAlignment = 4 +) + +// Enum value maps for CellAlignment. +var ( + CellAlignment_name = map[int32]string{ + 0: "CELL_ALIGNMENT_UNSPECIFIED", + 1: "CELL_ALIGNMENT_LEFT", + 2: "CELL_ALIGNMENT_RIGHT", + 3: "CELL_ALIGNMENT_CENTER", + 4: "CELL_ALIGNMENT_NONE", + } + CellAlignment_value = map[string]int32{ + "CELL_ALIGNMENT_UNSPECIFIED": 0, + "CELL_ALIGNMENT_LEFT": 1, + "CELL_ALIGNMENT_RIGHT": 2, + "CELL_ALIGNMENT_CENTER": 3, + "CELL_ALIGNMENT_NONE": 4, + } +) + +func (x CellAlignment) Enum() *CellAlignment { + p := new(CellAlignment) + *p = x + return p +} + +func (x CellAlignment) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CellAlignment) Descriptor() protoreflect.EnumDescriptor { + return file_ast_v1_ast_proto_enumTypes[2].Descriptor() +} + +func (CellAlignment) Type() protoreflect.EnumType { + return &file_ast_v1_ast_proto_enumTypes[2] +} + +func (x CellAlignment) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CellAlignment.Descriptor instead. +func (CellAlignment) EnumDescriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{2} +} + +type Attribute struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Types that are assignable to Value: + // + // *Attribute_Bytes + // *Attribute_Str + Value isAttribute_Value `protobuf_oneof:"value"` +} + +func (x *Attribute) Reset() { + *x = Attribute{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Attribute) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Attribute) ProtoMessage() {} + +func (x *Attribute) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Attribute.ProtoReflect.Descriptor instead. +func (*Attribute) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{0} +} + +func (x *Attribute) GetName() []byte { + if x != nil { + return x.Name + } + return nil +} + +func (m *Attribute) GetValue() isAttribute_Value { + if m != nil { + return m.Value + } + return nil +} + +func (x *Attribute) GetBytes() []byte { + if x, ok := x.GetValue().(*Attribute_Bytes); ok { + return x.Bytes + } + return nil +} + +func (x *Attribute) GetStr() string { + if x, ok := x.GetValue().(*Attribute_Str); ok { + return x.Str + } + return "" +} + +type isAttribute_Value interface { + isAttribute_Value() +} + +type Attribute_Bytes struct { + Bytes []byte `protobuf:"bytes,2,opt,name=bytes,proto3,oneof"` +} + +type Attribute_Str struct { + Str string `protobuf:"bytes,3,opt,name=str,proto3,oneof"` +} + +func (*Attribute_Bytes) isAttribute_Value() {} + +func (*Attribute_Str) isAttribute_Value() {} + +type BaseNode struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Children []*Node `protobuf:"bytes,1,rep,name=children,proto3" json:"children,omitempty"` + Attributes []*Attribute `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty"` + // value meaningful only for blocks + BlankPreviousLines bool `protobuf:"varint,3,opt,name=blank_previous_lines,json=blankPreviousLines,proto3" json:"blank_previous_lines,omitempty"` +} + +func (x *BaseNode) Reset() { + *x = BaseNode{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BaseNode) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BaseNode) ProtoMessage() {} + +func (x *BaseNode) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BaseNode.ProtoReflect.Descriptor instead. +func (*BaseNode) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{1} +} + +func (x *BaseNode) GetChildren() []*Node { + if x != nil { + return x.Children + } + return nil +} + +func (x *BaseNode) GetAttributes() []*Attribute { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *BaseNode) GetBlankPreviousLines() bool { + if x != nil { + return x.BlankPreviousLines + } + return false +} + +type Node struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Kind: + // + // *Node_Document + // *Node_TextBlock + // *Node_Paragraph + // *Node_Heading + // *Node_ThematicBreak + // *Node_CodeBlock + // *Node_FencedCodeBlock + // *Node_Blockquote + // *Node_List + // *Node_ListItem + // *Node_HtmlBlock + // *Node_Text + // *Node_String_ + // *Node_CodeSpan + // *Node_Emphasis + // *Node_LinkOrImage + // *Node_AutoLink + // *Node_RawHtml + // *Node_Table + // *Node_TableRow + // *Node_TableCell + // *Node_TaskCheckbox + // *Node_Strikethrough + // *Node_ContentNode + // *Node_Custom + Kind isNode_Kind `protobuf_oneof:"kind"` +} + +func (x *Node) Reset() { + *x = Node{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Node) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Node) ProtoMessage() {} + +func (x *Node) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Node.ProtoReflect.Descriptor instead. +func (*Node) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{2} +} + +func (m *Node) GetKind() isNode_Kind { + if m != nil { + return m.Kind + } + return nil +} + +func (x *Node) GetDocument() *Document { + if x, ok := x.GetKind().(*Node_Document); ok { + return x.Document + } + return nil +} + +func (x *Node) GetTextBlock() *TextBlock { + if x, ok := x.GetKind().(*Node_TextBlock); ok { + return x.TextBlock + } + return nil +} + +func (x *Node) GetParagraph() *Paragraph { + if x, ok := x.GetKind().(*Node_Paragraph); ok { + return x.Paragraph + } + return nil +} + +func (x *Node) GetHeading() *Heading { + if x, ok := x.GetKind().(*Node_Heading); ok { + return x.Heading + } + return nil +} + +func (x *Node) GetThematicBreak() *ThematicBreak { + if x, ok := x.GetKind().(*Node_ThematicBreak); ok { + return x.ThematicBreak + } + return nil +} + +func (x *Node) GetCodeBlock() *CodeBlock { + if x, ok := x.GetKind().(*Node_CodeBlock); ok { + return x.CodeBlock + } + return nil +} + +func (x *Node) GetFencedCodeBlock() *FencedCodeBlock { + if x, ok := x.GetKind().(*Node_FencedCodeBlock); ok { + return x.FencedCodeBlock + } + return nil +} + +func (x *Node) GetBlockquote() *Blockquote { + if x, ok := x.GetKind().(*Node_Blockquote); ok { + return x.Blockquote + } + return nil +} + +func (x *Node) GetList() *List { + if x, ok := x.GetKind().(*Node_List); ok { + return x.List + } + return nil +} + +func (x *Node) GetListItem() *ListItem { + if x, ok := x.GetKind().(*Node_ListItem); ok { + return x.ListItem + } + return nil +} + +func (x *Node) GetHtmlBlock() *HTMLBlock { + if x, ok := x.GetKind().(*Node_HtmlBlock); ok { + return x.HtmlBlock + } + return nil +} + +func (x *Node) GetText() *Text { + if x, ok := x.GetKind().(*Node_Text); ok { + return x.Text + } + return nil +} + +func (x *Node) GetString_() *String { + if x, ok := x.GetKind().(*Node_String_); ok { + return x.String_ + } + return nil +} + +func (x *Node) GetCodeSpan() *CodeSpan { + if x, ok := x.GetKind().(*Node_CodeSpan); ok { + return x.CodeSpan + } + return nil +} + +func (x *Node) GetEmphasis() *Emphasis { + if x, ok := x.GetKind().(*Node_Emphasis); ok { + return x.Emphasis + } + return nil +} + +func (x *Node) GetLinkOrImage() *LinkOrImage { + if x, ok := x.GetKind().(*Node_LinkOrImage); ok { + return x.LinkOrImage + } + return nil +} + +func (x *Node) GetAutoLink() *AutoLink { + if x, ok := x.GetKind().(*Node_AutoLink); ok { + return x.AutoLink + } + return nil +} + +func (x *Node) GetRawHtml() *RawHTML { + if x, ok := x.GetKind().(*Node_RawHtml); ok { + return x.RawHtml + } + return nil +} + +func (x *Node) GetTable() *Table { + if x, ok := x.GetKind().(*Node_Table); ok { + return x.Table + } + return nil +} + +func (x *Node) GetTableRow() *TableRow { + if x, ok := x.GetKind().(*Node_TableRow); ok { + return x.TableRow + } + return nil +} + +func (x *Node) GetTableCell() *TableCell { + if x, ok := x.GetKind().(*Node_TableCell); ok { + return x.TableCell + } + return nil +} + +func (x *Node) GetTaskCheckbox() *TaskCheckbox { + if x, ok := x.GetKind().(*Node_TaskCheckbox); ok { + return x.TaskCheckbox + } + return nil +} + +func (x *Node) GetStrikethrough() *Strikethrough { + if x, ok := x.GetKind().(*Node_Strikethrough); ok { + return x.Strikethrough + } + return nil +} + +func (x *Node) GetContentNode() *FabricContentNode { + if x, ok := x.GetKind().(*Node_ContentNode); ok { + return x.ContentNode + } + return nil +} + +func (x *Node) GetCustom() *CustomNode { + if x, ok := x.GetKind().(*Node_Custom); ok { + return x.Custom + } + return nil +} + +type isNode_Kind interface { + isNode_Kind() +} + +type Node_Document struct { + // Blocks + Document *Document `protobuf:"bytes,1,opt,name=document,proto3,oneof"` +} + +type Node_TextBlock struct { + TextBlock *TextBlock `protobuf:"bytes,5,opt,name=text_block,json=textBlock,proto3,oneof"` +} + +type Node_Paragraph struct { + Paragraph *Paragraph `protobuf:"bytes,6,opt,name=paragraph,proto3,oneof"` +} + +type Node_Heading struct { + Heading *Heading `protobuf:"bytes,7,opt,name=heading,proto3,oneof"` +} + +type Node_ThematicBreak struct { + ThematicBreak *ThematicBreak `protobuf:"bytes,8,opt,name=thematic_break,json=thematicBreak,proto3,oneof"` +} + +type Node_CodeBlock struct { + CodeBlock *CodeBlock `protobuf:"bytes,9,opt,name=code_block,json=codeBlock,proto3,oneof"` +} + +type Node_FencedCodeBlock struct { + FencedCodeBlock *FencedCodeBlock `protobuf:"bytes,10,opt,name=fenced_code_block,json=fencedCodeBlock,proto3,oneof"` +} + +type Node_Blockquote struct { + Blockquote *Blockquote `protobuf:"bytes,11,opt,name=blockquote,proto3,oneof"` +} + +type Node_List struct { + List *List `protobuf:"bytes,12,opt,name=list,proto3,oneof"` +} + +type Node_ListItem struct { + ListItem *ListItem `protobuf:"bytes,13,opt,name=list_item,json=listItem,proto3,oneof"` +} + +type Node_HtmlBlock struct { + HtmlBlock *HTMLBlock `protobuf:"bytes,14,opt,name=html_block,json=htmlBlock,proto3,oneof"` +} + +type Node_Text struct { + // inlines + Text *Text `protobuf:"bytes,15,opt,name=text,proto3,oneof"` +} + +type Node_String_ struct { + String_ *String `protobuf:"bytes,16,opt,name=string,proto3,oneof"` +} + +type Node_CodeSpan struct { + CodeSpan *CodeSpan `protobuf:"bytes,17,opt,name=code_span,json=codeSpan,proto3,oneof"` +} + +type Node_Emphasis struct { + Emphasis *Emphasis `protobuf:"bytes,18,opt,name=emphasis,proto3,oneof"` +} + +type Node_LinkOrImage struct { + LinkOrImage *LinkOrImage `protobuf:"bytes,19,opt,name=link_or_image,json=linkOrImage,proto3,oneof"` +} + +type Node_AutoLink struct { + AutoLink *AutoLink `protobuf:"bytes,20,opt,name=auto_link,json=autoLink,proto3,oneof"` +} + +type Node_RawHtml struct { + RawHtml *RawHTML `protobuf:"bytes,21,opt,name=raw_html,json=rawHtml,proto3,oneof"` +} + +type Node_Table struct { + // Github Flavored Markdown + // blocks + Table *Table `protobuf:"bytes,22,opt,name=table,proto3,oneof"` +} + +type Node_TableRow struct { + TableRow *TableRow `protobuf:"bytes,23,opt,name=table_row,json=tableRow,proto3,oneof"` +} + +type Node_TableCell struct { + TableCell *TableCell `protobuf:"bytes,24,opt,name=table_cell,json=tableCell,proto3,oneof"` +} + +type Node_TaskCheckbox struct { + // inline + TaskCheckbox *TaskCheckbox `protobuf:"bytes,25,opt,name=task_checkbox,json=taskCheckbox,proto3,oneof"` +} + +type Node_Strikethrough struct { + Strikethrough *Strikethrough `protobuf:"bytes,26,opt,name=strikethrough,proto3,oneof"` +} + +type Node_ContentNode struct { + // Root of the plugin-rendered data + ContentNode *FabricContentNode `protobuf:"bytes,254,opt,name=content_node,json=contentNode,proto3,oneof"` +} + +type Node_Custom struct { + // Custom node types can be serialized using this + Custom *CustomNode `protobuf:"bytes,255,opt,name=custom,proto3,oneof"` +} + +func (*Node_Document) isNode_Kind() {} + +func (*Node_TextBlock) isNode_Kind() {} + +func (*Node_Paragraph) isNode_Kind() {} + +func (*Node_Heading) isNode_Kind() {} + +func (*Node_ThematicBreak) isNode_Kind() {} + +func (*Node_CodeBlock) isNode_Kind() {} + +func (*Node_FencedCodeBlock) isNode_Kind() {} + +func (*Node_Blockquote) isNode_Kind() {} + +func (*Node_List) isNode_Kind() {} + +func (*Node_ListItem) isNode_Kind() {} + +func (*Node_HtmlBlock) isNode_Kind() {} + +func (*Node_Text) isNode_Kind() {} + +func (*Node_String_) isNode_Kind() {} + +func (*Node_CodeSpan) isNode_Kind() {} + +func (*Node_Emphasis) isNode_Kind() {} + +func (*Node_LinkOrImage) isNode_Kind() {} + +func (*Node_AutoLink) isNode_Kind() {} + +func (*Node_RawHtml) isNode_Kind() {} + +func (*Node_Table) isNode_Kind() {} + +func (*Node_TableRow) isNode_Kind() {} + +func (*Node_TableCell) isNode_Kind() {} + +func (*Node_TaskCheckbox) isNode_Kind() {} + +func (*Node_Strikethrough) isNode_Kind() {} + +func (*Node_ContentNode) isNode_Kind() {} + +func (*Node_Custom) isNode_Kind() {} + +type Document struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` +} + +func (x *Document) Reset() { + *x = Document{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Document) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Document) ProtoMessage() {} + +func (x *Document) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Document.ProtoReflect.Descriptor instead. +func (*Document) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{3} +} + +func (x *Document) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +type TextBlock struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` +} + +func (x *TextBlock) Reset() { + *x = TextBlock{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TextBlock) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TextBlock) ProtoMessage() {} + +func (x *TextBlock) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TextBlock.ProtoReflect.Descriptor instead. +func (*TextBlock) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{4} +} + +func (x *TextBlock) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +type Paragraph struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` +} + +func (x *Paragraph) Reset() { + *x = Paragraph{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Paragraph) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Paragraph) ProtoMessage() {} + +func (x *Paragraph) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Paragraph.ProtoReflect.Descriptor instead. +func (*Paragraph) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{5} +} + +func (x *Paragraph) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +type Heading struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Level uint32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` +} + +func (x *Heading) Reset() { + *x = Heading{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Heading) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Heading) ProtoMessage() {} + +func (x *Heading) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Heading.ProtoReflect.Descriptor instead. +func (*Heading) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{6} +} + +func (x *Heading) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *Heading) GetLevel() uint32 { + if x != nil { + return x.Level + } + return 0 +} + +type ThematicBreak struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` +} + +func (x *ThematicBreak) Reset() { + *x = ThematicBreak{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ThematicBreak) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ThematicBreak) ProtoMessage() {} + +func (x *ThematicBreak) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ThematicBreak.ProtoReflect.Descriptor instead. +func (*ThematicBreak) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{7} +} + +func (x *ThematicBreak) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +type CodeBlock struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Lines [][]byte `protobuf:"bytes,2,rep,name=lines,proto3" json:"lines,omitempty"` +} + +func (x *CodeBlock) Reset() { + *x = CodeBlock{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CodeBlock) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CodeBlock) ProtoMessage() {} + +func (x *CodeBlock) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CodeBlock.ProtoReflect.Descriptor instead. +func (*CodeBlock) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{8} +} + +func (x *CodeBlock) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *CodeBlock) GetLines() [][]byte { + if x != nil { + return x.Lines + } + return nil +} + +type FencedCodeBlock struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Info *Text `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"` + Lines [][]byte `protobuf:"bytes,3,rep,name=lines,proto3" json:"lines,omitempty"` +} + +func (x *FencedCodeBlock) Reset() { + *x = FencedCodeBlock{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FencedCodeBlock) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FencedCodeBlock) ProtoMessage() {} + +func (x *FencedCodeBlock) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FencedCodeBlock.ProtoReflect.Descriptor instead. +func (*FencedCodeBlock) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{9} +} + +func (x *FencedCodeBlock) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *FencedCodeBlock) GetInfo() *Text { + if x != nil { + return x.Info + } + return nil +} + +func (x *FencedCodeBlock) GetLines() [][]byte { + if x != nil { + return x.Lines + } + return nil +} + +type Blockquote struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` +} + +func (x *Blockquote) Reset() { + *x = Blockquote{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Blockquote) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Blockquote) ProtoMessage() {} + +func (x *Blockquote) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Blockquote.ProtoReflect.Descriptor instead. +func (*Blockquote) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{10} +} + +func (x *Blockquote) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +type List struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Marker uint32 `protobuf:"varint,2,opt,name=marker,proto3" json:"marker,omitempty"` + IsTight bool `protobuf:"varint,3,opt,name=is_tight,json=isTight,proto3" json:"is_tight,omitempty"` + Start uint32 `protobuf:"varint,4,opt,name=start,proto3" json:"start,omitempty"` +} + +func (x *List) Reset() { + *x = List{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *List) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*List) ProtoMessage() {} + +func (x *List) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use List.ProtoReflect.Descriptor instead. +func (*List) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{11} +} + +func (x *List) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *List) GetMarker() uint32 { + if x != nil { + return x.Marker + } + return 0 +} + +func (x *List) GetIsTight() bool { + if x != nil { + return x.IsTight + } + return false +} + +func (x *List) GetStart() uint32 { + if x != nil { + return x.Start + } + return 0 +} + +type ListItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Offset int64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` +} + +func (x *ListItem) Reset() { + *x = ListItem{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListItem) ProtoMessage() {} + +func (x *ListItem) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListItem.ProtoReflect.Descriptor instead. +func (*ListItem) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{12} +} + +func (x *ListItem) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *ListItem) GetOffset() int64 { + if x != nil { + return x.Offset + } + return 0 +} + +type HTMLBlock struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Type HTMLBlockType `protobuf:"varint,2,opt,name=type,proto3,enum=ast.v1.HTMLBlockType" json:"type,omitempty"` + Lines [][]byte `protobuf:"bytes,3,rep,name=lines,proto3" json:"lines,omitempty"` + ClosureLine []byte `protobuf:"bytes,4,opt,name=closure_line,json=closureLine,proto3" json:"closure_line,omitempty"` +} + +func (x *HTMLBlock) Reset() { + *x = HTMLBlock{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HTMLBlock) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HTMLBlock) ProtoMessage() {} + +func (x *HTMLBlock) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HTMLBlock.ProtoReflect.Descriptor instead. +func (*HTMLBlock) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{13} +} + +func (x *HTMLBlock) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *HTMLBlock) GetType() HTMLBlockType { + if x != nil { + return x.Type + } + return HTMLBlockType_HTML_BLOCK_TYPE_UNSPECIFIED +} + +func (x *HTMLBlock) GetLines() [][]byte { + if x != nil { + return x.Lines + } + return nil +} + +func (x *HTMLBlock) GetClosureLine() []byte { + if x != nil { + return x.ClosureLine + } + return nil +} + +type Text struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Segment []byte `protobuf:"bytes,2,opt,name=segment,proto3" json:"segment,omitempty"` + SoftLineBreak bool `protobuf:"varint,3,opt,name=soft_line_break,json=softLineBreak,proto3" json:"soft_line_break,omitempty"` + HardLineBreak bool `protobuf:"varint,4,opt,name=hard_line_break,json=hardLineBreak,proto3" json:"hard_line_break,omitempty"` + Raw bool `protobuf:"varint,5,opt,name=raw,proto3" json:"raw,omitempty"` +} + +func (x *Text) Reset() { + *x = Text{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Text) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Text) ProtoMessage() {} + +func (x *Text) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Text.ProtoReflect.Descriptor instead. +func (*Text) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{14} +} + +func (x *Text) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *Text) GetSegment() []byte { + if x != nil { + return x.Segment + } + return nil +} + +func (x *Text) GetSoftLineBreak() bool { + if x != nil { + return x.SoftLineBreak + } + return false +} + +func (x *Text) GetHardLineBreak() bool { + if x != nil { + return x.HardLineBreak + } + return false +} + +func (x *Text) GetRaw() bool { + if x != nil { + return x.Raw + } + return false +} + +type String struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + Raw bool `protobuf:"varint,3,opt,name=raw,proto3" json:"raw,omitempty"` + Code bool `protobuf:"varint,4,opt,name=code,proto3" json:"code,omitempty"` +} + +func (x *String) Reset() { + *x = String{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *String) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*String) ProtoMessage() {} + +func (x *String) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use String.ProtoReflect.Descriptor instead. +func (*String) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{15} +} + +func (x *String) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *String) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *String) GetRaw() bool { + if x != nil { + return x.Raw + } + return false +} + +func (x *String) GetCode() bool { + if x != nil { + return x.Code + } + return false +} + +type CodeSpan struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` +} + +func (x *CodeSpan) Reset() { + *x = CodeSpan{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CodeSpan) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CodeSpan) ProtoMessage() {} + +func (x *CodeSpan) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CodeSpan.ProtoReflect.Descriptor instead. +func (*CodeSpan) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{16} +} + +func (x *CodeSpan) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +type Emphasis struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Level int64 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` +} + +func (x *Emphasis) Reset() { + *x = Emphasis{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Emphasis) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Emphasis) ProtoMessage() {} + +func (x *Emphasis) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Emphasis.ProtoReflect.Descriptor instead. +func (*Emphasis) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{17} +} + +func (x *Emphasis) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *Emphasis) GetLevel() int64 { + if x != nil { + return x.Level + } + return 0 +} + +type LinkOrImage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Destination []byte `protobuf:"bytes,2,opt,name=destination,proto3" json:"destination,omitempty"` + Title []byte `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"` + IsImage bool `protobuf:"varint,4,opt,name=is_image,json=isImage,proto3" json:"is_image,omitempty"` +} + +func (x *LinkOrImage) Reset() { + *x = LinkOrImage{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LinkOrImage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LinkOrImage) ProtoMessage() {} + +func (x *LinkOrImage) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LinkOrImage.ProtoReflect.Descriptor instead. +func (*LinkOrImage) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{18} +} + +func (x *LinkOrImage) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *LinkOrImage) GetDestination() []byte { + if x != nil { + return x.Destination + } + return nil +} + +func (x *LinkOrImage) GetTitle() []byte { + if x != nil { + return x.Title + } + return nil +} + +func (x *LinkOrImage) GetIsImage() bool { + if x != nil { + return x.IsImage + } + return false +} + +type AutoLink struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Type AutoLinkType `protobuf:"varint,2,opt,name=type,proto3,enum=ast.v1.AutoLinkType" json:"type,omitempty"` + Protocol []byte `protobuf:"bytes,3,opt,name=protocol,proto3" json:"protocol,omitempty"` + Value []byte `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *AutoLink) Reset() { + *x = AutoLink{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AutoLink) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AutoLink) ProtoMessage() {} + +func (x *AutoLink) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AutoLink.ProtoReflect.Descriptor instead. +func (*AutoLink) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{19} +} + +func (x *AutoLink) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *AutoLink) GetType() AutoLinkType { + if x != nil { + return x.Type + } + return AutoLinkType_AUTO_LINK_TYPE_UNSPECIFIED +} + +func (x *AutoLink) GetProtocol() []byte { + if x != nil { + return x.Protocol + } + return nil +} + +func (x *AutoLink) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type RawHTML struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Segments [][]byte `protobuf:"bytes,2,rep,name=segments,proto3" json:"segments,omitempty"` +} + +func (x *RawHTML) Reset() { + *x = RawHTML{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RawHTML) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RawHTML) ProtoMessage() {} + +func (x *RawHTML) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RawHTML.ProtoReflect.Descriptor instead. +func (*RawHTML) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{20} +} + +func (x *RawHTML) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *RawHTML) GetSegments() [][]byte { + if x != nil { + return x.Segments + } + return nil +} + +type Table struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Alignments []CellAlignment `protobuf:"varint,2,rep,packed,name=alignments,proto3,enum=ast.v1.CellAlignment" json:"alignments,omitempty"` +} + +func (x *Table) Reset() { + *x = Table{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Table) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Table) ProtoMessage() {} + +func (x *Table) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Table.ProtoReflect.Descriptor instead. +func (*Table) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{21} +} + +func (x *Table) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *Table) GetAlignments() []CellAlignment { + if x != nil { + return x.Alignments + } + return nil +} + +type TableRow struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Alignments []CellAlignment `protobuf:"varint,2,rep,packed,name=alignments,proto3,enum=ast.v1.CellAlignment" json:"alignments,omitempty"` + IsHeader bool `protobuf:"varint,4,opt,name=is_header,json=isHeader,proto3" json:"is_header,omitempty"` +} + +func (x *TableRow) Reset() { + *x = TableRow{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TableRow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TableRow) ProtoMessage() {} + +func (x *TableRow) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TableRow.ProtoReflect.Descriptor instead. +func (*TableRow) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{22} +} + +func (x *TableRow) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *TableRow) GetAlignments() []CellAlignment { + if x != nil { + return x.Alignments + } + return nil +} + +func (x *TableRow) GetIsHeader() bool { + if x != nil { + return x.IsHeader + } + return false +} + +type TableCell struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + Alignment CellAlignment `protobuf:"varint,2,opt,name=alignment,proto3,enum=ast.v1.CellAlignment" json:"alignment,omitempty"` +} + +func (x *TableCell) Reset() { + *x = TableCell{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TableCell) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TableCell) ProtoMessage() {} + +func (x *TableCell) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TableCell.ProtoReflect.Descriptor instead. +func (*TableCell) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{23} +} + +func (x *TableCell) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *TableCell) GetAlignment() CellAlignment { + if x != nil { + return x.Alignment + } + return CellAlignment_CELL_ALIGNMENT_UNSPECIFIED +} + +type TaskCheckbox struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + IsChecked bool `protobuf:"varint,2,opt,name=is_checked,json=isChecked,proto3" json:"is_checked,omitempty"` +} + +func (x *TaskCheckbox) Reset() { + *x = TaskCheckbox{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TaskCheckbox) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaskCheckbox) ProtoMessage() {} + +func (x *TaskCheckbox) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaskCheckbox.ProtoReflect.Descriptor instead. +func (*TaskCheckbox) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{24} +} + +func (x *TaskCheckbox) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +func (x *TaskCheckbox) GetIsChecked() bool { + if x != nil { + return x.IsChecked + } + return false +} + +type Strikethrough struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Base *BaseNode `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` +} + +func (x *Strikethrough) Reset() { + *x = Strikethrough{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Strikethrough) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Strikethrough) ProtoMessage() {} + +func (x *Strikethrough) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Strikethrough.ProtoReflect.Descriptor instead. +func (*Strikethrough) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{25} +} + +func (x *Strikethrough) GetBase() *BaseNode { + if x != nil { + return x.Base + } + return nil +} + +type CustomNode struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Indicates that this block is an inline element + IsInline bool `protobuf:"varint,1,opt,name=is_inline,json=isInline,proto3" json:"is_inline,omitempty"` + Data *anypb.Any `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + BlankPreviousLines bool `protobuf:"varint,3,opt,name=blank_previous_lines,json=blankPreviousLines,proto3" json:"blank_previous_lines,omitempty"` +} + +func (x *CustomNode) Reset() { + *x = CustomNode{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CustomNode) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CustomNode) ProtoMessage() {} + +func (x *CustomNode) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CustomNode.ProtoReflect.Descriptor instead. +func (*CustomNode) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{26} +} + +func (x *CustomNode) GetIsInline() bool { + if x != nil { + return x.IsInline + } + return false +} + +func (x *CustomNode) GetData() *anypb.Any { + if x != nil { + return x.Data + } + return nil +} + +func (x *CustomNode) GetBlankPreviousLines() bool { + if x != nil { + return x.BlankPreviousLines + } + return false +} + +type Metadata struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // ie "blackstork/builtin" + Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + // ie "title" + Plugin string `protobuf:"bytes,2,opt,name=plugin,proto3" json:"plugin,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *Metadata) Reset() { + *x = Metadata{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Metadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Metadata) ProtoMessage() {} + +func (x *Metadata) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Metadata.ProtoReflect.Descriptor instead. +func (*Metadata) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{27} +} + +func (x *Metadata) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *Metadata) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +func (x *Metadata) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +// Root of the plugin-rendered data +type FabricContentNode struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Metadata *Metadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + Root *BaseNode `protobuf:"bytes,2,opt,name=root,proto3" json:"root,omitempty"` // direct content, no document node +} + +func (x *FabricContentNode) Reset() { + *x = FabricContentNode{} + if protoimpl.UnsafeEnabled { + mi := &file_ast_v1_ast_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FabricContentNode) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FabricContentNode) ProtoMessage() {} + +func (x *FabricContentNode) ProtoReflect() protoreflect.Message { + mi := &file_ast_v1_ast_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FabricContentNode.ProtoReflect.Descriptor instead. +func (*FabricContentNode) Descriptor() ([]byte, []int) { + return file_ast_v1_ast_proto_rawDescGZIP(), []int{28} +} + +func (x *FabricContentNode) GetMetadata() *Metadata { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *FabricContentNode) GetRoot() *BaseNode { + if x != nil { + return x.Root + } + return nil +} + +var File_ast_v1_ast_proto protoreflect.FileDescriptor + +var file_ast_v1_ast_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x61, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x06, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x54, 0x0a, 0x09, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x12, + 0x0a, 0x03, 0x73, 0x74, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x73, + 0x74, 0x72, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x99, 0x01, 0x0a, 0x08, + 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, + 0x64, 0x72, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x73, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, + 0x65, 0x6e, 0x12, 0x31, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x5f, 0x70, + 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x12, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x50, 0x72, 0x65, 0x76, 0x69, 0x6f, + 0x75, 0x73, 0x4c, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x8d, 0x0a, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65, + 0x12, 0x2e, 0x0a, 0x08, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x6f, 0x63, 0x75, + 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x08, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x12, 0x32, 0x0a, 0x0a, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, + 0x78, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x09, 0x74, 0x65, 0x78, 0x74, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x31, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x61, 0x67, 0x72, 0x61, 0x70, + 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x61, 0x72, 0x61, 0x67, 0x72, 0x61, 0x70, 0x68, 0x48, 0x00, 0x52, 0x09, 0x70, 0x61, + 0x72, 0x61, 0x67, 0x72, 0x61, 0x70, 0x68, 0x12, 0x2b, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x69, + 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x00, 0x52, 0x07, 0x68, 0x65, 0x61, + 0x64, 0x69, 0x6e, 0x67, 0x12, 0x3e, 0x0a, 0x0e, 0x74, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, + 0x5f, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, + 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x42, 0x72, + 0x65, 0x61, 0x6b, 0x48, 0x00, 0x52, 0x0d, 0x74, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x42, + 0x72, 0x65, 0x61, 0x6b, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x09, 0x63, + 0x6f, 0x64, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x45, 0x0a, 0x11, 0x66, 0x65, 0x6e, 0x63, + 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, 0x6e, + 0x63, 0x65, 0x64, 0x43, 0x6f, 0x64, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x0f, + 0x66, 0x65, 0x6e, 0x63, 0x65, 0x64, 0x43, 0x6f, 0x64, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, + 0x34, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x71, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x48, 0x00, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x09, 0x6c, 0x69, 0x73, + 0x74, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, + 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x48, 0x00, + 0x52, 0x08, 0x6c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x32, 0x0a, 0x0a, 0x68, 0x74, + 0x6d, 0x6c, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, + 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x54, 0x4d, 0x4c, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x48, 0x00, 0x52, 0x09, 0x68, 0x74, 0x6d, 0x6c, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x22, + 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, + 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x48, 0x00, 0x52, 0x04, 0x74, 0x65, + 0x78, 0x74, 0x12, 0x28, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x10, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x2f, 0x0a, 0x09, + 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x73, 0x70, 0x61, 0x6e, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x53, 0x70, 0x61, + 0x6e, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x2e, 0x0a, + 0x08, 0x65, 0x6d, 0x70, 0x68, 0x61, 0x73, 0x69, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6d, 0x70, 0x68, 0x61, 0x73, 0x69, + 0x73, 0x48, 0x00, 0x52, 0x08, 0x65, 0x6d, 0x70, 0x68, 0x61, 0x73, 0x69, 0x73, 0x12, 0x39, 0x0a, + 0x0d, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x6f, 0x72, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x13, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, + 0x6e, 0x6b, 0x4f, 0x72, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x6c, 0x69, 0x6e, + 0x6b, 0x4f, 0x72, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x6f, + 0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x6f, 0x4c, 0x69, 0x6e, 0x6b, 0x48, 0x00, 0x52, + 0x08, 0x61, 0x75, 0x74, 0x6f, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x2c, 0x0a, 0x08, 0x72, 0x61, 0x77, + 0x5f, 0x68, 0x74, 0x6d, 0x6c, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x73, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x77, 0x48, 0x54, 0x4d, 0x4c, 0x48, 0x00, 0x52, 0x07, + 0x72, 0x61, 0x77, 0x48, 0x74, 0x6d, 0x6c, 0x12, 0x25, 0x0a, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x61, 0x62, 0x6c, 0x65, 0x48, 0x00, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x2f, + 0x0a, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x6f, 0x77, 0x18, 0x17, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, + 0x52, 0x6f, 0x77, 0x48, 0x00, 0x52, 0x08, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x12, + 0x32, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x65, 0x6c, 0x6c, 0x18, 0x18, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x48, 0x00, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x43, + 0x65, 0x6c, 0x6c, 0x12, 0x3b, 0x0a, 0x0d, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x62, 0x6f, 0x78, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x73, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78, + 0x48, 0x00, 0x52, 0x0c, 0x74, 0x61, 0x73, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78, + 0x12, 0x3d, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6b, 0x65, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, + 0x68, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6b, 0x65, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x48, 0x00, + 0x52, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6b, 0x65, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x12, + 0x3f, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, + 0xfe, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x46, 0x61, 0x62, 0x72, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4e, 0x6f, 0x64, + 0x65, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4e, 0x6f, 0x64, 0x65, + 0x12, 0x2d, 0x0a, 0x06, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x18, 0xff, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x48, 0x00, 0x52, 0x06, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x42, + 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x30, 0x0a, 0x08, 0x44, 0x6f, 0x63, 0x75, 0x6d, + 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, + 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x22, 0x31, 0x0a, 0x09, 0x54, 0x65, 0x78, + 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, + 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x22, 0x31, 0x0a, 0x09, + 0x50, 0x61, 0x72, 0x61, 0x67, 0x72, 0x61, 0x70, 0x68, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x22, + 0x45, 0x0a, 0x07, 0x48, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, + 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x35, 0x0a, 0x0d, 0x54, 0x68, 0x65, 0x6d, 0x61, 0x74, + 0x69, 0x63, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, + 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x22, 0x47, 0x0a, + 0x09, 0x43, 0x6f, 0x64, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, + 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, + 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x6f, 0x0a, 0x0f, 0x46, 0x65, 0x6e, 0x63, 0x65, 0x64, + 0x43, 0x6f, 0x64, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, + 0x20, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, + 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x52, 0x04, 0x69, 0x6e, 0x66, + 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, + 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x32, 0x0a, 0x0a, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x71, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, + 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x22, 0x75, 0x0a, 0x04, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, + 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x72, + 0x6b, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6d, 0x61, 0x72, 0x6b, 0x65, + 0x72, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x74, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x54, 0x69, 0x67, 0x68, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x22, 0x48, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x24, + 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, + 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, + 0x62, 0x61, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x95, 0x01, 0x0a, + 0x09, 0x48, 0x54, 0x4d, 0x4c, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, + 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, + 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, + 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x54, 0x4d, 0x4c, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, + 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, + 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x75, 0x72, 0x65, 0x5f, 0x6c, 0x69, 0x6e, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x75, 0x72, 0x65, + 0x4c, 0x69, 0x6e, 0x65, 0x22, 0xa8, 0x01, 0x0a, 0x04, 0x54, 0x65, 0x78, 0x74, 0x12, 0x24, 0x0a, + 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, + 0x61, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x26, 0x0a, + 0x0f, 0x73, 0x6f, 0x66, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x62, 0x72, 0x65, 0x61, 0x6b, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x6f, 0x66, 0x74, 0x4c, 0x69, 0x6e, 0x65, + 0x42, 0x72, 0x65, 0x61, 0x6b, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x61, 0x72, 0x64, 0x5f, 0x6c, 0x69, + 0x6e, 0x65, 0x5f, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, + 0x68, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x12, 0x10, 0x0a, + 0x03, 0x72, 0x61, 0x77, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x72, 0x61, 0x77, 0x22, + 0x6a, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x30, 0x0a, 0x08, 0x43, + 0x6f, 0x64, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, + 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x22, 0x46, 0x0a, + 0x08, 0x45, 0x6d, 0x70, 0x68, 0x61, 0x73, 0x69, 0x73, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, + 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x86, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x6e, 0x6b, 0x4f, 0x72, + 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, + 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x8c, + 0x01, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x6f, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x24, 0x0a, 0x04, 0x62, + 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, + 0x65, 0x12, 0x28, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x14, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x6f, 0x4c, 0x69, 0x6e, + 0x6b, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4b, 0x0a, + 0x07, 0x52, 0x61, 0x77, 0x48, 0x54, 0x4d, 0x4c, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, + 0x52, 0x08, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x64, 0x0a, 0x05, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, + 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x0a, 0x61, 0x6c, 0x69, + 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x15, 0x2e, + 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x65, 0x6c, 0x6c, 0x41, 0x6c, 0x69, 0x67, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0a, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x22, 0x84, 0x01, 0x0a, 0x08, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x12, 0x24, 0x0a, + 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, + 0x61, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x0a, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x65, 0x6c, 0x6c, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0a, + 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, + 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, + 0x73, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0x66, 0x0a, 0x09, 0x54, 0x61, 0x62, 0x6c, 0x65, + 0x43, 0x65, 0x6c, 0x6c, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, + 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x61, 0x6c, + 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, + 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x65, 0x6c, 0x6c, 0x41, 0x6c, 0x69, 0x67, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x22, + 0x53, 0x0a, 0x0c, 0x54, 0x61, 0x73, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78, 0x12, + 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, + 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, + 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x65, 0x64, 0x22, 0x35, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x69, 0x6b, 0x65, 0x74, 0x68, + 0x72, 0x6f, 0x75, 0x67, 0x68, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, + 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x22, 0x85, 0x01, 0x0a, 0x0a, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, + 0x5f, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, + 0x73, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x12, 0x30, 0x0a, 0x14, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x69, + 0x6f, 0x75, 0x73, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x12, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x50, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x69, + 0x6e, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, + 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x67, 0x0a, + 0x11, 0x46, 0x61, 0x62, 0x72, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4e, 0x6f, + 0x64, 0x65, 0x12, 0x2c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x12, 0x24, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x64, 0x65, + 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x2a, 0xd1, 0x01, 0x0a, 0x0d, 0x48, 0x54, 0x4d, 0x4c, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4d, 0x4c, + 0x5f, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x48, 0x54, 0x4d, + 0x4c, 0x5f, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x31, 0x10, 0x01, + 0x12, 0x15, 0x0a, 0x11, 0x48, 0x54, 0x4d, 0x4c, 0x5f, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x32, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x48, 0x54, 0x4d, 0x4c, 0x5f, + 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x33, 0x10, 0x03, 0x12, 0x15, + 0x0a, 0x11, 0x48, 0x54, 0x4d, 0x4c, 0x5f, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x34, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x48, 0x54, 0x4d, 0x4c, 0x5f, 0x42, 0x4c, + 0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x35, 0x10, 0x05, 0x12, 0x15, 0x0a, 0x11, + 0x48, 0x54, 0x4d, 0x4c, 0x5f, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x36, 0x10, 0x06, 0x12, 0x15, 0x0a, 0x11, 0x48, 0x54, 0x4d, 0x4c, 0x5f, 0x42, 0x4c, 0x4f, 0x43, + 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x37, 0x10, 0x07, 0x2a, 0x60, 0x0a, 0x0c, 0x41, 0x75, + 0x74, 0x6f, 0x4c, 0x69, 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x1a, 0x41, 0x55, + 0x54, 0x4f, 0x5f, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x55, + 0x54, 0x4f, 0x5f, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4d, 0x41, + 0x49, 0x4c, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x4c, 0x49, 0x4e, + 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x52, 0x4c, 0x10, 0x02, 0x2a, 0x96, 0x01, 0x0a, + 0x0d, 0x43, 0x65, 0x6c, 0x6c, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, + 0x0a, 0x1a, 0x43, 0x45, 0x4c, 0x4c, 0x5f, 0x41, 0x4c, 0x49, 0x47, 0x4e, 0x4d, 0x45, 0x4e, 0x54, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, + 0x0a, 0x13, 0x43, 0x45, 0x4c, 0x4c, 0x5f, 0x41, 0x4c, 0x49, 0x47, 0x4e, 0x4d, 0x45, 0x4e, 0x54, + 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x45, 0x4c, 0x4c, 0x5f, + 0x41, 0x4c, 0x49, 0x47, 0x4e, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x49, 0x47, 0x48, 0x54, 0x10, + 0x02, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x45, 0x4c, 0x4c, 0x5f, 0x41, 0x4c, 0x49, 0x47, 0x4e, 0x4d, + 0x45, 0x4e, 0x54, 0x5f, 0x43, 0x45, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, + 0x43, 0x45, 0x4c, 0x4c, 0x5f, 0x41, 0x4c, 0x49, 0x47, 0x4e, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, + 0x4f, 0x4e, 0x45, 0x10, 0x04, 0x42, 0x84, 0x01, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x73, + 0x74, 0x2e, 0x76, 0x31, 0x42, 0x08, 0x41, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x61, + 0x63, 0x6b, 0x73, 0x74, 0x6f, 0x72, 0x6b, 0x2d, 0x69, 0x6f, 0x2f, 0x66, 0x61, 0x62, 0x72, 0x69, + 0x63, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x61, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x3b, + 0x61, 0x73, 0x74, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x06, 0x41, 0x73, + 0x74, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x06, 0x41, 0x73, 0x74, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x12, + 0x41, 0x73, 0x74, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0xea, 0x02, 0x07, 0x41, 0x73, 0x74, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_ast_v1_ast_proto_rawDescOnce sync.Once + file_ast_v1_ast_proto_rawDescData = file_ast_v1_ast_proto_rawDesc +) + +func file_ast_v1_ast_proto_rawDescGZIP() []byte { + file_ast_v1_ast_proto_rawDescOnce.Do(func() { + file_ast_v1_ast_proto_rawDescData = protoimpl.X.CompressGZIP(file_ast_v1_ast_proto_rawDescData) + }) + return file_ast_v1_ast_proto_rawDescData +} + +var file_ast_v1_ast_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_ast_v1_ast_proto_msgTypes = make([]protoimpl.MessageInfo, 29) +var file_ast_v1_ast_proto_goTypes = []any{ + (HTMLBlockType)(0), // 0: ast.v1.HTMLBlockType + (AutoLinkType)(0), // 1: ast.v1.AutoLinkType + (CellAlignment)(0), // 2: ast.v1.CellAlignment + (*Attribute)(nil), // 3: ast.v1.Attribute + (*BaseNode)(nil), // 4: ast.v1.BaseNode + (*Node)(nil), // 5: ast.v1.Node + (*Document)(nil), // 6: ast.v1.Document + (*TextBlock)(nil), // 7: ast.v1.TextBlock + (*Paragraph)(nil), // 8: ast.v1.Paragraph + (*Heading)(nil), // 9: ast.v1.Heading + (*ThematicBreak)(nil), // 10: ast.v1.ThematicBreak + (*CodeBlock)(nil), // 11: ast.v1.CodeBlock + (*FencedCodeBlock)(nil), // 12: ast.v1.FencedCodeBlock + (*Blockquote)(nil), // 13: ast.v1.Blockquote + (*List)(nil), // 14: ast.v1.List + (*ListItem)(nil), // 15: ast.v1.ListItem + (*HTMLBlock)(nil), // 16: ast.v1.HTMLBlock + (*Text)(nil), // 17: ast.v1.Text + (*String)(nil), // 18: ast.v1.String + (*CodeSpan)(nil), // 19: ast.v1.CodeSpan + (*Emphasis)(nil), // 20: ast.v1.Emphasis + (*LinkOrImage)(nil), // 21: ast.v1.LinkOrImage + (*AutoLink)(nil), // 22: ast.v1.AutoLink + (*RawHTML)(nil), // 23: ast.v1.RawHTML + (*Table)(nil), // 24: ast.v1.Table + (*TableRow)(nil), // 25: ast.v1.TableRow + (*TableCell)(nil), // 26: ast.v1.TableCell + (*TaskCheckbox)(nil), // 27: ast.v1.TaskCheckbox + (*Strikethrough)(nil), // 28: ast.v1.Strikethrough + (*CustomNode)(nil), // 29: ast.v1.CustomNode + (*Metadata)(nil), // 30: ast.v1.Metadata + (*FabricContentNode)(nil), // 31: ast.v1.FabricContentNode + (*anypb.Any)(nil), // 32: google.protobuf.Any +} +var file_ast_v1_ast_proto_depIdxs = []int32{ + 5, // 0: ast.v1.BaseNode.children:type_name -> ast.v1.Node + 3, // 1: ast.v1.BaseNode.attributes:type_name -> ast.v1.Attribute + 6, // 2: ast.v1.Node.document:type_name -> ast.v1.Document + 7, // 3: ast.v1.Node.text_block:type_name -> ast.v1.TextBlock + 8, // 4: ast.v1.Node.paragraph:type_name -> ast.v1.Paragraph + 9, // 5: ast.v1.Node.heading:type_name -> ast.v1.Heading + 10, // 6: ast.v1.Node.thematic_break:type_name -> ast.v1.ThematicBreak + 11, // 7: ast.v1.Node.code_block:type_name -> ast.v1.CodeBlock + 12, // 8: ast.v1.Node.fenced_code_block:type_name -> ast.v1.FencedCodeBlock + 13, // 9: ast.v1.Node.blockquote:type_name -> ast.v1.Blockquote + 14, // 10: ast.v1.Node.list:type_name -> ast.v1.List + 15, // 11: ast.v1.Node.list_item:type_name -> ast.v1.ListItem + 16, // 12: ast.v1.Node.html_block:type_name -> ast.v1.HTMLBlock + 17, // 13: ast.v1.Node.text:type_name -> ast.v1.Text + 18, // 14: ast.v1.Node.string:type_name -> ast.v1.String + 19, // 15: ast.v1.Node.code_span:type_name -> ast.v1.CodeSpan + 20, // 16: ast.v1.Node.emphasis:type_name -> ast.v1.Emphasis + 21, // 17: ast.v1.Node.link_or_image:type_name -> ast.v1.LinkOrImage + 22, // 18: ast.v1.Node.auto_link:type_name -> ast.v1.AutoLink + 23, // 19: ast.v1.Node.raw_html:type_name -> ast.v1.RawHTML + 24, // 20: ast.v1.Node.table:type_name -> ast.v1.Table + 25, // 21: ast.v1.Node.table_row:type_name -> ast.v1.TableRow + 26, // 22: ast.v1.Node.table_cell:type_name -> ast.v1.TableCell + 27, // 23: ast.v1.Node.task_checkbox:type_name -> ast.v1.TaskCheckbox + 28, // 24: ast.v1.Node.strikethrough:type_name -> ast.v1.Strikethrough + 31, // 25: ast.v1.Node.content_node:type_name -> ast.v1.FabricContentNode + 29, // 26: ast.v1.Node.custom:type_name -> ast.v1.CustomNode + 4, // 27: ast.v1.Document.base:type_name -> ast.v1.BaseNode + 4, // 28: ast.v1.TextBlock.base:type_name -> ast.v1.BaseNode + 4, // 29: ast.v1.Paragraph.base:type_name -> ast.v1.BaseNode + 4, // 30: ast.v1.Heading.base:type_name -> ast.v1.BaseNode + 4, // 31: ast.v1.ThematicBreak.base:type_name -> ast.v1.BaseNode + 4, // 32: ast.v1.CodeBlock.base:type_name -> ast.v1.BaseNode + 4, // 33: ast.v1.FencedCodeBlock.base:type_name -> ast.v1.BaseNode + 17, // 34: ast.v1.FencedCodeBlock.info:type_name -> ast.v1.Text + 4, // 35: ast.v1.Blockquote.base:type_name -> ast.v1.BaseNode + 4, // 36: ast.v1.List.base:type_name -> ast.v1.BaseNode + 4, // 37: ast.v1.ListItem.base:type_name -> ast.v1.BaseNode + 4, // 38: ast.v1.HTMLBlock.base:type_name -> ast.v1.BaseNode + 0, // 39: ast.v1.HTMLBlock.type:type_name -> ast.v1.HTMLBlockType + 4, // 40: ast.v1.Text.base:type_name -> ast.v1.BaseNode + 4, // 41: ast.v1.String.base:type_name -> ast.v1.BaseNode + 4, // 42: ast.v1.CodeSpan.base:type_name -> ast.v1.BaseNode + 4, // 43: ast.v1.Emphasis.base:type_name -> ast.v1.BaseNode + 4, // 44: ast.v1.LinkOrImage.base:type_name -> ast.v1.BaseNode + 4, // 45: ast.v1.AutoLink.base:type_name -> ast.v1.BaseNode + 1, // 46: ast.v1.AutoLink.type:type_name -> ast.v1.AutoLinkType + 4, // 47: ast.v1.RawHTML.base:type_name -> ast.v1.BaseNode + 4, // 48: ast.v1.Table.base:type_name -> ast.v1.BaseNode + 2, // 49: ast.v1.Table.alignments:type_name -> ast.v1.CellAlignment + 4, // 50: ast.v1.TableRow.base:type_name -> ast.v1.BaseNode + 2, // 51: ast.v1.TableRow.alignments:type_name -> ast.v1.CellAlignment + 4, // 52: ast.v1.TableCell.base:type_name -> ast.v1.BaseNode + 2, // 53: ast.v1.TableCell.alignment:type_name -> ast.v1.CellAlignment + 4, // 54: ast.v1.TaskCheckbox.base:type_name -> ast.v1.BaseNode + 4, // 55: ast.v1.Strikethrough.base:type_name -> ast.v1.BaseNode + 32, // 56: ast.v1.CustomNode.data:type_name -> google.protobuf.Any + 30, // 57: ast.v1.FabricContentNode.metadata:type_name -> ast.v1.Metadata + 4, // 58: ast.v1.FabricContentNode.root:type_name -> ast.v1.BaseNode + 59, // [59:59] is the sub-list for method output_type + 59, // [59:59] is the sub-list for method input_type + 59, // [59:59] is the sub-list for extension type_name + 59, // [59:59] is the sub-list for extension extendee + 0, // [0:59] is the sub-list for field type_name +} + +func init() { file_ast_v1_ast_proto_init() } +func file_ast_v1_ast_proto_init() { + if File_ast_v1_ast_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_ast_v1_ast_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*Attribute); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*BaseNode); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*Node); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*Document); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*TextBlock); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*Paragraph); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*Heading); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*ThematicBreak); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*CodeBlock); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*FencedCodeBlock); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[10].Exporter = func(v any, i int) any { + switch v := v.(*Blockquote); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[11].Exporter = func(v any, i int) any { + switch v := v.(*List); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[12].Exporter = func(v any, i int) any { + switch v := v.(*ListItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[13].Exporter = func(v any, i int) any { + switch v := v.(*HTMLBlock); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[14].Exporter = func(v any, i int) any { + switch v := v.(*Text); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[15].Exporter = func(v any, i int) any { + switch v := v.(*String); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[16].Exporter = func(v any, i int) any { + switch v := v.(*CodeSpan); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[17].Exporter = func(v any, i int) any { + switch v := v.(*Emphasis); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[18].Exporter = func(v any, i int) any { + switch v := v.(*LinkOrImage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[19].Exporter = func(v any, i int) any { + switch v := v.(*AutoLink); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[20].Exporter = func(v any, i int) any { + switch v := v.(*RawHTML); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[21].Exporter = func(v any, i int) any { + switch v := v.(*Table); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[22].Exporter = func(v any, i int) any { + switch v := v.(*TableRow); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[23].Exporter = func(v any, i int) any { + switch v := v.(*TableCell); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[24].Exporter = func(v any, i int) any { + switch v := v.(*TaskCheckbox); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[25].Exporter = func(v any, i int) any { + switch v := v.(*Strikethrough); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[26].Exporter = func(v any, i int) any { + switch v := v.(*CustomNode); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[27].Exporter = func(v any, i int) any { + switch v := v.(*Metadata); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ast_v1_ast_proto_msgTypes[28].Exporter = func(v any, i int) any { + switch v := v.(*FabricContentNode); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_ast_v1_ast_proto_msgTypes[0].OneofWrappers = []any{ + (*Attribute_Bytes)(nil), + (*Attribute_Str)(nil), + } + file_ast_v1_ast_proto_msgTypes[2].OneofWrappers = []any{ + (*Node_Document)(nil), + (*Node_TextBlock)(nil), + (*Node_Paragraph)(nil), + (*Node_Heading)(nil), + (*Node_ThematicBreak)(nil), + (*Node_CodeBlock)(nil), + (*Node_FencedCodeBlock)(nil), + (*Node_Blockquote)(nil), + (*Node_List)(nil), + (*Node_ListItem)(nil), + (*Node_HtmlBlock)(nil), + (*Node_Text)(nil), + (*Node_String_)(nil), + (*Node_CodeSpan)(nil), + (*Node_Emphasis)(nil), + (*Node_LinkOrImage)(nil), + (*Node_AutoLink)(nil), + (*Node_RawHtml)(nil), + (*Node_Table)(nil), + (*Node_TableRow)(nil), + (*Node_TableCell)(nil), + (*Node_TaskCheckbox)(nil), + (*Node_Strikethrough)(nil), + (*Node_ContentNode)(nil), + (*Node_Custom)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_ast_v1_ast_proto_rawDesc, + NumEnums: 3, + NumMessages: 29, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_ast_v1_ast_proto_goTypes, + DependencyIndexes: file_ast_v1_ast_proto_depIdxs, + EnumInfos: file_ast_v1_ast_proto_enumTypes, + MessageInfos: file_ast_v1_ast_proto_msgTypes, + }.Build() + File_ast_v1_ast_proto = out.File + file_ast_v1_ast_proto_rawDesc = nil + file_ast_v1_ast_proto_goTypes = nil + file_ast_v1_ast_proto_depIdxs = nil +} diff --git a/plugin/ast/v1/ast_decoder.go b/plugin/ast/v1/ast_decoder.go new file mode 100644 index 00000000..e0142f99 --- /dev/null +++ b/plugin/ast/v1/ast_decoder.go @@ -0,0 +1,263 @@ +package astv1 + +import ( + "fmt" + "math" + + "github.com/yuin/goldmark/ast" + east "github.com/yuin/goldmark/extension/ast" + "github.com/yuin/goldmark/text" + + "github.com/blackstork-io/fabric/plugin/ast/astsrc" + "github.com/blackstork-io/fabric/plugin/ast/nodes" +) + +type DecoderOption interface { + apply(dec *decoder) +} + +type ( + AttributeDecoder func(*Attribute) (ast.Attribute, error) + NodeDecoder func(*Node) (ast.Node, error) +) + +func (f AttributeDecoder) apply(dec *decoder) { + if f != nil { + dec.attributeDecoder = f + } +} + +func (f NodeDecoder) apply(dec *decoder) { + if f != nil { + dec.nodeDecoder = f + } +} + +func defaultNodeDecoder(node *Node) (ast.Node, error) { + return nil, fmt.Errorf("%w: %T", ErrUnsupportedNodeType, node) +} + +func Decode(root *Node, opts ...DecoderOption) (node ast.Node, source astsrc.ASTSource, err error) { + defer func() { + err = recoverBubbleError(err, recover()) + }() + dec := &decoder{ + attributeDecoder: DefaultAttributeDecoder, + nodeDecoder: defaultNodeDecoder, + } + for _, opt := range opts { + opt.apply(dec) + } + + node = dec.decodeNode(root) + source = dec.source + return +} + +type decoder struct { + source astsrc.ASTSource + attributeDecoder AttributeDecoder + nodeDecoder NodeDecoder +} + +func DefaultAttributeDecoder(attr *Attribute) (res ast.Attribute, err error) { + switch val := attr.GetValue().(type) { + case nil: + case *Attribute_Bytes: + res.Value = val.Bytes + case *Attribute_Str: + res.Value = val.Str + default: + err = fmt.Errorf("%w: %T", ErrUnsupportedAttributeType, val) + return + } + res.Name = attr.GetName() + return +} + +func (d *decoder) decodeBaseNode(base *BaseNode, node ast.Node) { + if base == nil { + return + } + for _, encAttr := range base.GetAttributes() { + attr, err := d.attributeDecoder(encAttr) + if err != nil { + bubbleUp(err) + } + node.SetAttribute(attr.Name, attr.Value) + } + + for _, encChild := range base.GetChildren() { + node.AppendChild(node, d.decodeNode(encChild)) + } +} + +func (d *decoder) decodeText(txt *Text) *ast.Text { + if txt == nil { + return nil + } + res := ast.NewText() + res.Segment = d.source.Append(txt.GetSegment()) + res.SetSoftLineBreak(txt.GetSoftLineBreak()) + res.SetHardLineBreak(txt.GetHardLineBreak()) + res.SetRaw(txt.GetRaw()) + return res +} + +func (d *decoder) decodeNode(node *Node) (res ast.Node) { + var base *BaseNode + switch val := node.GetKind().(type) { + case *Node_Document: + base = val.Document.GetBase() + res = ast.NewDocument() + res.SetBlankPreviousLines(true) + case *Node_TextBlock: + base = val.TextBlock.GetBase() + res = ast.NewTextBlock() + case *Node_Paragraph: + base = val.Paragraph.GetBase() + res = ast.NewParagraph() + case *Node_Heading: + base = val.Heading.GetBase() + res = ast.NewHeading(int(val.Heading.GetLevel())) + case *Node_ThematicBreak: + base = val.ThematicBreak.GetBase() + res = ast.NewThematicBreak() + case *Node_CodeBlock: + base = val.CodeBlock.GetBase() + codeBlock := ast.NewCodeBlock() + codeBlock.SetLines(d.source.AppendMultiple(val.CodeBlock.GetLines())) + res = codeBlock + case *Node_FencedCodeBlock: + base = val.FencedCodeBlock.GetBase() + fencedCodeBlock := ast.NewFencedCodeBlock(d.decodeText(val.FencedCodeBlock.GetInfo())) + fencedCodeBlock.SetLines(d.source.AppendMultiple(val.FencedCodeBlock.GetLines())) + res = fencedCodeBlock + case *Node_Blockquote: + base = val.Blockquote.GetBase() + res = ast.NewBlockquote() + case *Node_List: + base = val.List.GetBase() + marker := val.List.GetMarker() + if marker == 0 || marker > math.MaxUint8 { + bubbleUp(fmt.Errorf("invalid marker character code: %d", marker)) + } + list := ast.NewList(byte(marker)) + list.IsTight = val.List.GetIsTight() + list.Start = int(val.List.GetStart()) + res = list + case *Node_ListItem: + base = val.ListItem.GetBase() + res = ast.NewListItem(int(val.ListItem.GetOffset())) + case *Node_HtmlBlock: + base = val.HtmlBlock.GetBase() + htmlBlock := ast.NewHTMLBlock(val.HtmlBlock.GetType().decode()) + if closure := val.HtmlBlock.GetClosureLine(); closure != nil { + htmlBlock.ClosureLine = d.source.Append(val.HtmlBlock.GetClosureLine()) + } else { + htmlBlock.ClosureLine = text.NewSegment(-1, -1) + } + htmlBlock.SetLines(d.source.AppendMultiple(val.HtmlBlock.GetLines())) + res = htmlBlock + case *Node_Text: + base = val.Text.GetBase() + res = d.decodeText(val.Text) + case *Node_String_: + base = val.String_.GetBase() + str := ast.NewString(val.String_.GetValue()) + str.SetRaw(val.String_.GetRaw()) + str.SetCode(val.String_.GetCode()) + res = str + case *Node_CodeSpan: + base = val.CodeSpan.GetBase() + res = ast.NewCodeSpan() + case *Node_Emphasis: + base = val.Emphasis.GetBase() + res = ast.NewEmphasis(int(val.Emphasis.GetLevel())) + case *Node_LinkOrImage: + base = val.LinkOrImage.GetBase() + tRes := ast.NewLink() + tRes.Title = val.LinkOrImage.GetTitle() + tRes.Destination = val.LinkOrImage.GetDestination() + if val.LinkOrImage.GetIsImage() { + res = ast.NewImage(tRes) + } else { + res = tRes + } + case *Node_AutoLink: + base = val.AutoLink.GetBase() + tRes := ast.NewAutoLink(val.AutoLink.GetType().decode(), d.decodeText(&Text{ + Segment: val.AutoLink.GetValue(), + })) + tRes.Protocol = val.AutoLink.GetProtocol() + res = tRes + + case *Node_RawHtml: + base = val.RawHtml.GetBase() + tRes := ast.NewRawHTML() + tRes.Segments = d.source.AppendMultiple(val.RawHtml.GetSegments()) + res = tRes + + case *Node_Table: + base = val.Table.GetBase() + tRes := east.NewTable() + tRes.Alignments = decodeCellAlignments(val.Table.GetAlignments()) + res = tRes + + case *Node_TableRow: + base = val.TableRow.GetBase() + tRes := east.NewTableRow(decodeCellAlignments(val.TableRow.GetAlignments())) + if val.TableRow.GetIsHeader() { + res = east.NewTableHeader(tRes) + } else { + res = tRes + } + case *Node_TableCell: + base = val.TableCell.GetBase() + tRes := east.NewTableCell() + tRes.Alignment = val.TableCell.GetAlignment().decode() + res = tRes + + case *Node_Strikethrough: + base = val.Strikethrough.GetBase() + res = east.NewStrikethrough() + + case *Node_TaskCheckbox: + base = val.TaskCheckbox.GetBase() + res = east.NewTaskCheckBox(val.TaskCheckbox.GetIsChecked()) + + case *Node_ContentNode: + res = &nodes.FabricContentNode{ + Meta: DecodeMetadata(val.ContentNode.GetMetadata()), + } + base = val.ContentNode.GetRoot() + case *Node_Custom: + res = nodes.NewCustomNode(val.Custom.GetIsInline(), val.Custom.GetData()) + base = &BaseNode{ + BlankPreviousLines: val.Custom.GetBlankPreviousLines(), + } + default: + bubbleUp(fmt.Errorf("Unsupported block kind: %T", node.GetKind())) + } + switch res.Type() { + case ast.TypeBlock: + res.SetBlankPreviousLines(base.GetBlankPreviousLines()) + fallthrough + case ast.TypeInline, ast.TypeDocument: + d.decodeBaseNode(base, res) + default: + bubbleUp(fmt.Errorf("Unsupported block type: %+v", res.Type())) + } + return +} + +func DecodeMetadata(meta *Metadata) *nodes.ContentMeta { + if meta == nil { + return nil + } + return &nodes.ContentMeta{ + Provider: meta.GetProvider(), + Plugin: meta.GetPlugin(), + Version: meta.GetVersion(), + } +} diff --git a/plugin/ast/v1/ast_encoder.go b/plugin/ast/v1/ast_encoder.go new file mode 100644 index 00000000..714f9c53 --- /dev/null +++ b/plugin/ast/v1/ast_encoder.go @@ -0,0 +1,390 @@ +package astv1 + +import ( + "errors" + "fmt" + + "github.com/yuin/goldmark/ast" + east "github.com/yuin/goldmark/extension/ast" + "github.com/yuin/goldmark/text" + + "github.com/blackstork-io/fabric/plugin/ast/nodes" +) + +// ErrSkipAttribute should be returned from AttributeEncoder if the +// attribute needs to be be skipped. +var ErrSkipAttribute = errors.New("skip") + +var ( + ErrUnsupportedNodeType = errors.New("unsupported node type") + ErrUnsupportedAttributeType = errors.New("unsupported attribute type") + ErrUnsupportedAlignment = errors.New("unsupported alignment") +) + +type ( + AttributeEncoder func(*ast.Attribute) (*Attribute, error) + NodeEncoder func(ast.Node) (isNode_Kind, error) +) + +func (f AttributeEncoder) apply(enc *encoder) { + if f != nil { + enc.attributeEncoder = f + } +} + +func (f NodeEncoder) apply(enc *encoder) { + if f != nil { + enc.nodeEncoder = f + } +} + +type EncoderOption interface { + apply(enc *encoder) +} + +type encoder struct { + attributeEncoder AttributeEncoder + nodeEncoder NodeEncoder + source []byte +} + +func defaultNodeEncoder(node ast.Node) (isNode_Kind, error) { + return nil, fmt.Errorf("%w: %s", ErrUnsupportedNodeType, node.Kind()) +} + +func Encode(root ast.Node, source []byte, opts ...EncoderOption) (node *Node, err error) { + defer func() { + err = recoverBubbleError(err, recover()) + }() + enc := &encoder{ + attributeEncoder: DefaultAttributeEncoder, + source: source, + nodeEncoder: defaultNodeEncoder, + } + for _, opt := range opts { + opt.apply(enc) + } + node = enc.encodeNode(root) + return +} + +func (e *encoder) encodeSegment(seg text.Segment) []byte { + if seg.Start > seg.Stop || seg.Start < 0 || seg.Stop > len(e.source) { + // Invalid segment. + return nil + } + // Note: zero-length segments (seg.Start == seg.Stop) are valid, + // and even could be meaningful if padding is > 0. + // Render them using seg.Value. + return seg.Value(e.source) +} + +func (e *encoder) encodeBaseNode(node *ast.BaseNode) *BaseNode { + var res BaseNode + attrs := node.Attributes() + res.Attributes = make([]*Attribute, 0, len(attrs)) + for _, attr := range attrs { + encoded, err := e.attributeEncoder(&attr) + if err != nil { + bubbleUp(err) + } + res.Attributes = append(res.Attributes, encoded) + } + res.Children = make([]*Node, 0, node.ChildCount()) + for child := node.FirstChild(); child != nil; child = child.NextSibling() { + res.Children = append(res.Children, e.encodeNode(child)) + } + return &res +} + +func (e *encoder) encodeBaseBlock(node *ast.BaseBlock) (res *BaseNode) { + res = e.encodeBaseNode(&node.BaseNode) + res.BlankPreviousLines = node.HasBlankPreviousLines() + return +} + +func (e *encoder) encodeNode(node ast.Node) *Node { + var kind isNode_Kind + + switch n := node.(type) { + case *ast.Document: + kind = &Node_Document{ + Document: &Document{ + Base: e.encodeBaseBlock(&n.BaseBlock), + }, + } + case *ast.TextBlock: + kind = &Node_TextBlock{ + TextBlock: &TextBlock{ + Base: e.encodeBaseBlock(&n.BaseBlock), + }, + } + case *ast.Paragraph: + kind = &Node_Paragraph{ + Paragraph: &Paragraph{ + Base: e.encodeBaseBlock(&n.BaseBlock), + }, + } + case *ast.Heading: + kind = &Node_Heading{ + Heading: &Heading{ + Base: e.encodeBaseBlock(&n.BaseBlock), + Level: uint32(n.Level), + }, + } + case *ast.ThematicBreak: + kind = &Node_ThematicBreak{ + ThematicBreak: &ThematicBreak{ + Base: e.encodeBaseBlock(&n.BaseBlock), + }, + } + case *ast.CodeBlock: + kind = &Node_CodeBlock{ + CodeBlock: &CodeBlock{ + Base: e.encodeBaseBlock(&n.BaseBlock), + Lines: e.encodeSegments(n.Lines()), + }, + } + case *ast.FencedCodeBlock: + var txt *Text + if n.Info != nil { + txt = &Text{ + Segment: e.encodeSegment(n.Info.Segment), + Raw: n.Info.IsRaw(), + } + } + kind = &Node_FencedCodeBlock{ + FencedCodeBlock: &FencedCodeBlock{ + Base: e.encodeBaseBlock(&n.BaseBlock), + Info: txt, + Lines: e.encodeSegments(n.Lines()), + }, + } + case *ast.Blockquote: + kind = &Node_Blockquote{ + Blockquote: &Blockquote{ + Base: e.encodeBaseBlock(&n.BaseBlock), + }, + } + case *ast.List: + kind = &Node_List{ + List: &List{ + Base: e.encodeBaseBlock(&n.BaseBlock), + Marker: uint32(n.Marker), + IsTight: n.IsTight, + Start: uint32(n.Start), + }, + } + case *ast.ListItem: + kind = &Node_ListItem{ + ListItem: &ListItem{ + Base: e.encodeBaseBlock(&n.BaseBlock), + Offset: int64(n.Offset), + }, + } + case *ast.HTMLBlock: + // Doing encoding manually to prevent a change in the internal representation + // of HTMLBlockType enum from breaking our encoding. + kind = &Node_HtmlBlock{ + HtmlBlock: &HTMLBlock{ + Base: e.encodeBaseBlock(&n.BaseBlock), + Type: encodeHTMLBlockType(n.HTMLBlockType), + Lines: e.encodeSegments(n.Lines()), + ClosureLine: e.encodeSegment(n.ClosureLine), + }, + } + case *ast.Text: + kind = &Node_Text{ + Text: &Text{ + Base: e.encodeBaseNode(&n.BaseNode), + Segment: e.encodeSegment(n.Segment), + SoftLineBreak: n.SoftLineBreak(), + HardLineBreak: n.HardLineBreak(), + Raw: n.IsRaw(), + }, + } + case *ast.String: + kind = &Node_String_{ + String_: &String{ + Base: e.encodeBaseNode(&n.BaseNode), + Value: n.Value, + Raw: n.IsRaw(), + Code: n.IsCode(), + }, + } + case *ast.CodeSpan: + kind = &Node_CodeSpan{ + CodeSpan: &CodeSpan{ + Base: e.encodeBaseNode(&n.BaseNode), + }, + } + case *ast.Emphasis: + kind = &Node_Emphasis{ + Emphasis: &Emphasis{ + Base: e.encodeBaseNode(&n.BaseNode), + Level: int64(n.Level), + }, + } + case *ast.Link: + kind = &Node_LinkOrImage{ + LinkOrImage: &LinkOrImage{ + Base: e.encodeBaseNode(&n.BaseNode), + Destination: n.Destination, + Title: n.Title, + IsImage: false, + }, + } + case *ast.Image: + kind = &Node_LinkOrImage{ + LinkOrImage: &LinkOrImage{ + Base: e.encodeBaseNode(&n.BaseNode), + Destination: n.Destination, + Title: n.Title, + IsImage: true, + }, + } + case *ast.AutoLink: + kind = &Node_AutoLink{ + AutoLink: &AutoLink{ + Base: e.encodeBaseNode(&n.BaseNode), + Type: encodeAutoLinkType(n.AutoLinkType), + Protocol: n.Protocol, + Value: n.Label(e.source), + }, + } + case *ast.RawHTML: + kind = &Node_RawHtml{ + RawHtml: &RawHTML{ + Base: e.encodeBaseNode(&n.BaseNode), + Segments: e.encodeSegments(n.Segments), + }, + } + // GitHub Flavored Markdown + case *east.Table: + kind = &Node_Table{ + Table: &Table{ + Base: e.encodeBaseBlock(&n.BaseBlock), + Alignments: encodeCellAlignments(n.Alignments), + }, + } + + case *east.TableRow: + kind = &Node_TableRow{ + TableRow: &TableRow{ + Base: e.encodeBaseBlock(&n.BaseBlock), + Alignments: encodeCellAlignments(n.Alignments), + IsHeader: false, + }, + } + case *east.TableHeader: + kind = &Node_TableRow{ + TableRow: &TableRow{ + Base: e.encodeBaseBlock(&n.BaseBlock), + Alignments: encodeCellAlignments(n.Alignments), + IsHeader: true, + }, + } + case *east.TableCell: + kind = &Node_TableCell{ + TableCell: &TableCell{ + Base: e.encodeBaseBlock(&n.BaseBlock), + Alignment: encodeCellAlignment(n.Alignment), + }, + } + case *east.Strikethrough: + kind = &Node_Strikethrough{ + Strikethrough: &Strikethrough{ + Base: e.encodeBaseNode(&n.BaseNode), + }, + } + case *east.TaskCheckBox: + kind = &Node_TaskCheckbox{ + TaskCheckbox: &TaskCheckbox{ + Base: e.encodeBaseNode(&n.BaseNode), + IsChecked: n.IsChecked, + }, + } + case *nodes.FabricContentNode: + kind = &Node_ContentNode{ + ContentNode: &FabricContentNode{ + Metadata: EncodeMetadata(n.Meta), + Root: e.encodeBaseBlock(&n.BaseBlock), + }, + } + case *nodes.CustomBlock: + kind = &Node_Custom{ + Custom: &CustomNode{ + IsInline: false, + Data: n.Data, + }, + } + case *nodes.CustomInline: + kind = &Node_Custom{ + Custom: &CustomNode{ + IsInline: true, + Data: n.Data, + }, + } + default: + var err error + kind, err = e.nodeEncoder(node) + if err != nil { + bubbleUp(err) + } + } + + return &Node{ + Kind: kind, + } +} + +func (e *encoder) encodeSegments(seg *text.Segments) [][]byte { + res := make([][]byte, seg.Len()) + for i := range res { + res[i] = e.encodeSegment(seg.At(i)) + } + return res +} + +func DefaultAttributeEncoder(attr *ast.Attribute) (*Attribute, error) { + var res isAttribute_Value + switch val := attr.Value.(type) { + case []byte: + res = &Attribute_Bytes{ + Bytes: val, + } + case string: + res = &Attribute_Str{ + Str: val, + } + case uint, uint8, uint16, uint32, uintptr, uint64, int, int8, int16, int32, int64: + res = &Attribute_Str{ + Str: fmt.Sprintf("%d", val), + } + case bool: + res = &Attribute_Bytes{ + Bytes: attr.Name, + } + case float32, float64: + res = &Attribute_Str{ + Str: fmt.Sprintf("%f", val), + } + default: + return nil, fmt.Errorf("%w: %T", ErrUnsupportedAttributeType, attr.Value) + } + return &Attribute{ + Name: attr.Name, + Value: res, + }, nil +} + +func EncodeMetadata(meta *nodes.ContentMeta) *Metadata { + if meta == nil { + return nil + } + return &Metadata{ + Provider: meta.Provider, + Plugin: meta.Plugin, + Version: meta.Version, + } +} diff --git a/plugin/ast/v1/ast_test.go b/plugin/ast/v1/ast_test.go new file mode 100644 index 00000000..13342940 --- /dev/null +++ b/plugin/ast/v1/ast_test.go @@ -0,0 +1,98 @@ +package astv1_test + +import ( + "bytes" + "testing" + + "github.com/blackstork-io/goldmark-markdown/pkg/mdexamples" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/text" + + astv1 "github.com/blackstork-io/fabric/plugin/ast/v1" +) + +func roundtrip(t *testing.T, source []byte) { + t.Helper() + md := goldmark.New( + goldmark.WithExtensions( + extension.Table, + extension.Strikethrough, + extension.TaskList, + ), + ) + + tree := md.Parser().Parse(text.NewReader(source)) + + // roundtrip + encTree, err := astv1.Encode(tree, source) + if err != nil { + t.Fatalf("encode: %v", err) + } + decTree, decSrc, err := astv1.Decode(encTree) + if err != nil { + t.Fatalf("decode: %v", err) + } + + // assume that trees are identical if their html render is identical + // can't use equality checks because the segments hold no reference to *which* + // source they are referring to + var bufOrig, bufRoundtrip bytes.Buffer + + err = md.Renderer().Render(&bufOrig, source, tree) + if err != nil { + t.Fatalf("render original: %v", err) + } + + err = md.Renderer().Render(&bufRoundtrip, decSrc, decTree) + if err != nil { + t.Fatalf("render decoded: %v", err) + } + + if !bytes.Equal(bufOrig.Bytes(), bufRoundtrip.Bytes()) { + t.Errorf("rendered html differs") + t.Logf("---------- original -----------\n%s\n\n", bufOrig.String()) + t.Logf("---------- roundtrip ----------\n%s\n\n", bufRoundtrip.String()) + } +} + +func TestSpecExamplesRoundtrip(t *testing.T) { + for _, exFile := range mdexamples.ReadAllSpecExamples() { + for _, ex := range exFile.Examples { + t.Run(exFile.Name+":"+ex.Link, func(t *testing.T) { + t.Parallel() + // Skipped tests are ok to use, we're only interested in the roundtrip + roundtrip(t, ex.Markdown) + }) + } + } +} + +func TestDocumentsRoundtrip(t *testing.T) { + for _, ex := range mdexamples.ReadAllDocumentExamples() { + t.Run(ex.Name, func(t *testing.T) { + t.Parallel() + roundtrip(t, ex.Data) + }) + } +} + +func TestFuzzCase(t *testing.T) { + t.Skip("Expected to fail: bugs in goldmark") + roundtrip(t, []byte("* 0\n-|\n\t0")) + roundtrip(t, []byte("> ```\n>\t0")) +} + +func FuzzEncoder(f *testing.F) { + // for _, exFile := range test.ReadAllSpecExamples() { + // for _, ex := range exFile.Examples { + // f.Add(ex.Markdown) + // } + // } + for _, ex := range mdexamples.ReadAllDocumentExamples() { + f.Add(ex.Data) + } + f.Fuzz(func(t *testing.T, data []byte) { + roundtrip(t, data) + }) +} diff --git a/plugin/ast/v1/enum_codec.go b/plugin/ast/v1/enum_codec.go new file mode 100644 index 00000000..e8dd3e21 --- /dev/null +++ b/plugin/ast/v1/enum_codec.go @@ -0,0 +1,124 @@ +package astv1 + +import ( + "fmt" + + "github.com/yuin/goldmark/ast" + east "github.com/yuin/goldmark/extension/ast" +) + +func encodeHTMLBlockType(ty ast.HTMLBlockType) HTMLBlockType { + switch ty { + case ast.HTMLBlockType1: + return HTMLBlockType_HTML_BLOCK_TYPE_1 + case ast.HTMLBlockType2: + return HTMLBlockType_HTML_BLOCK_TYPE_2 + case ast.HTMLBlockType3: + return HTMLBlockType_HTML_BLOCK_TYPE_3 + case ast.HTMLBlockType4: + return HTMLBlockType_HTML_BLOCK_TYPE_4 + case ast.HTMLBlockType5: + return HTMLBlockType_HTML_BLOCK_TYPE_5 + case ast.HTMLBlockType6: + return HTMLBlockType_HTML_BLOCK_TYPE_6 + case ast.HTMLBlockType7: + return HTMLBlockType_HTML_BLOCK_TYPE_7 + default: + panic(bubbleWrap(fmt.Errorf("unsupported HTML block type: %v", ty))) + } +} + +func (bt HTMLBlockType) decode() ast.HTMLBlockType { + switch bt { + case HTMLBlockType_HTML_BLOCK_TYPE_1: + return ast.HTMLBlockType1 + case HTMLBlockType_HTML_BLOCK_TYPE_2: + return ast.HTMLBlockType2 + case HTMLBlockType_HTML_BLOCK_TYPE_3: + return ast.HTMLBlockType3 + case HTMLBlockType_HTML_BLOCK_TYPE_4: + return ast.HTMLBlockType4 + case HTMLBlockType_HTML_BLOCK_TYPE_5: + return ast.HTMLBlockType5 + case HTMLBlockType_HTML_BLOCK_TYPE_6: + return ast.HTMLBlockType6 + case HTMLBlockType_HTML_BLOCK_TYPE_7: + return ast.HTMLBlockType7 + case HTMLBlockType_HTML_BLOCK_TYPE_UNSPECIFIED: + fallthrough + default: + panic(bubbleWrap(fmt.Errorf("unsupported HTML block type: %v", bt))) + } +} + +func encodeAutoLinkType(ty ast.AutoLinkType) AutoLinkType { + switch ty { + case ast.AutoLinkURL: + return AutoLinkType_AUTO_LINK_TYPE_URL + case ast.AutoLinkEmail: + return AutoLinkType_AUTO_LINK_TYPE_EMAIL + default: + panic(bubbleWrap(fmt.Errorf("unsupported auto link type: %v", ty))) + } +} + +func (ty AutoLinkType) decode() ast.AutoLinkType { + switch ty { + case AutoLinkType_AUTO_LINK_TYPE_URL: + return ast.AutoLinkURL + case AutoLinkType_AUTO_LINK_TYPE_EMAIL: + return ast.AutoLinkEmail + case AutoLinkType_AUTO_LINK_TYPE_UNSPECIFIED: + fallthrough + default: + panic(bubbleWrap(fmt.Errorf("unsupported auto link type: %v", ty))) + } +} + +func encodeCellAlignment(alignment east.Alignment) CellAlignment { + switch alignment { + case east.AlignLeft: + return CellAlignment_CELL_ALIGNMENT_LEFT + case east.AlignRight: + return CellAlignment_CELL_ALIGNMENT_RIGHT + case east.AlignCenter: + return CellAlignment_CELL_ALIGNMENT_CENTER + case east.AlignNone: + return CellAlignment_CELL_ALIGNMENT_NONE + default: + panic(bubbleWrap(fmt.Errorf("unsupported cell alignment: %v", alignment))) + } +} + +func (ca CellAlignment) decode() east.Alignment { + switch ca { + case CellAlignment_CELL_ALIGNMENT_NONE: + return east.AlignNone + case CellAlignment_CELL_ALIGNMENT_LEFT: + return east.AlignLeft + case CellAlignment_CELL_ALIGNMENT_RIGHT: + return east.AlignRight + case CellAlignment_CELL_ALIGNMENT_CENTER: + return east.AlignCenter + case CellAlignment_CELL_ALIGNMENT_UNSPECIFIED: + fallthrough + default: + panic(bubbleWrap(fmt.Errorf("unsupported cell alignment: %v", ca))) + } +} + +func encodeCellAlignments(align []east.Alignment) []CellAlignment { + res := make([]CellAlignment, len(align)) + for i, a := range align { + res[i] = encodeCellAlignment(a) + } + return res +} + +func decodeCellAlignments(align []CellAlignment) []east.Alignment { + res := make([]east.Alignment, len(align)) + for i, a := range align { + res[i] = a.decode() + } + return res +} diff --git a/plugin/ast/v1/interfaces.go b/plugin/ast/v1/interfaces.go new file mode 100644 index 00000000..c9a27128 --- /dev/null +++ b/plugin/ast/v1/interfaces.go @@ -0,0 +1,319 @@ +package astv1 + +import "slices" + +type Content interface { + ExtendNodes(nodes []*Node) []*Node +} + +type Contents []Content + +func (c Contents) ExtendNodes(nodes []*Node) []*Node { + for _, content := range c { + nodes = content.ExtendNodes(nodes) + } + return nodes +} + +// InlineContent represents any inline node or something convertible to inline nodes. +type InlineContent interface { + Content + isInline() +} + +// Inlines is a list of InlineContent. +type Inlines []InlineContent + +func (n Inlines) isInline() {} +func (n Inlines) ExtendNodes(nodes []*Node) []*Node { + for _, inlines := range n { + nodes = inlines.ExtendNodes(nodes) + } + return nodes +} + +func (n *Node_CodeSpan) isInline() {} +func (n *Node_CodeSpan) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_Emphasis) isInline() {} +func (n *Node_Emphasis) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_LinkOrImage) isInline() {} +func (n *Node_LinkOrImage) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_LinkOrImage) SetDestination(url string) *Node_LinkOrImage { + n.LinkOrImage.Destination = []byte(url) + return n +} + +func (n *Node_LinkOrImage) SetTitle(title string) *Node_LinkOrImage { + n.LinkOrImage.Title = []byte(title) + return n +} + +func (n *Node_AutoLink) isInline() {} +func (n *Node_AutoLink) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_RawHtml) isInline() {} +func (n *Node_RawHtml) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_Text) isInline() {} +func (n *Node_Text) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_String_) isInline() {} +func (n *Node_String_) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_Strikethrough) isInline() {} +func (n *Node_Strikethrough) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +// checkbox is inline content, but it can be used only in a list item. + +// BlockContent represents any block node or something convertible to block nodes. +type BlockContent interface { + Content + isBlock() +} +type Blocks []BlockContent + +func (b Blocks) isBlock() {} +func (b Blocks) ExtendNodes(nodes []*Node) []*Node { + for _, inlines := range b { + nodes = inlines.ExtendNodes(nodes) + } + return nodes +} + +func (n *Node_Blockquote) isBlock() {} +func (n *Node_Blockquote) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_List) isBlock() {} +func (n *Node_List) ExtendNodes(nodes []*Node) []*Node { + if n == nil || n.List == nil || len(n.List.GetBase().GetChildren()) == 0 { + return nodes + } + return append(nodes, &Node{ + Kind: n, + }) +} + +// SetStart sets the start number of the list (only works on ordered lists, "." and ")" markers). +func (n *Node_List) SetStart(start uint32) *Node_List { + switch n.List.GetMarker() { + case '.', ')': + n.List.Start = start + } + return n +} + +func prependCheckbox(checked bool, content []BlockContent) (itemChildren []*Node) { + itemChildren = Blocks.ExtendNodes(content, nil) + + checkbox := &Node{ + Kind: &Node_TaskCheckbox{ + TaskCheckbox: &TaskCheckbox{ + IsChecked: checked, + }, + }, + } + + if len(itemChildren) > 0 { + if text, ok := itemChildren[0].GetKind().(*Node_Paragraph); ok { + text.Paragraph.Base.Children = slices.Insert(text.Paragraph.GetBase().GetChildren(), 0, checkbox) + return + } + } + // Checkbox was not prepended to existing paragraph, create a new paragraph. + itemChildren = slices.Insert(itemChildren, 0, &Node{ + Kind: &Node_Paragraph{ + Paragraph: &Paragraph{ + Base: &BaseNode{ + Children: []*Node{checkbox}, + }, + }, + }, + }) + return +} + +func (n *Node_List) appendItem(children []*Node) { + n.List.Base.Children = append(n.List.Base.Children, &Node{ + Kind: &Node_ListItem{ + ListItem: &ListItem{ + Base: &BaseNode{ + Children: children, + }, + }, + }, + }) +} + +// AppendItem appends a list item to the list. +func (n *Node_List) AppendItem(content ...BlockContent) *Node_List { + n.appendItem(Blocks.ExtendNodes(content, nil)) + return n +} + +// AppendTaskItem appends a task list item to the list. +func (n *Node_List) AppendTaskItem(checked bool, content ...BlockContent) *Node_List { + n.appendItem(prependCheckbox(checked, content)) + return n +} + +// Thematic breaks +func (n *Node_ThematicBreak) isBlock() {} + +func (n *Node_ThematicBreak) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_Heading) isBlock() {} + +func (n *Node_Heading) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_CodeBlock) isBlock() {} + +func (n *Node_CodeBlock) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_FencedCodeBlock) isBlock() {} + +func (n *Node_FencedCodeBlock) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_FencedCodeBlock) SetLanguage(language string) *Node_FencedCodeBlock { + n.FencedCodeBlock.Info = &Text{ + Segment: []byte(language), + } + return n +} + +func (n *Node_HtmlBlock) isBlock() {} +func (n *Node_HtmlBlock) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_Paragraph) isBlock() {} +func (n *Node_Paragraph) ExtendNodes(nodes []*Node) []*Node { + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_Table) isBlock() {} +func (n *Node_Table) ExtendNodes(nodes []*Node) []*Node { + if n == nil { + return nodes + } + rows := n.Table.GetBase().GetChildren() + if len(rows) == 0 { + return nodes + } + headerCellCount := len(rows[0].GetTableRow().GetBase().GetChildren()) + if headerCellCount == 0 { + // must have at least one table cell + return nodes + } + unspecifiedAlignments := headerCellCount - len(n.Table.Alignments) + if unspecifiedAlignments <= 0 { + // Trim the alignments to the number of header cells + n.Table.Alignments = n.Table.Alignments[:len(n.Table.Alignments)+unspecifiedAlignments] + } else { + // Extend the alignments to the number of header cells, filling missing alignments with NONE + n.Table.Alignments = slices.Grow(n.Table.Alignments, unspecifiedAlignments) + for range unspecifiedAlignments { + n.Table.Alignments = append(n.Table.Alignments, CellAlignment_CELL_ALIGNMENT_NONE) + } + } + for rowIdx, row := range rows { + tr := row.GetTableRow() + if tr == nil { + return nodes + } + tr.IsHeader = rowIdx == 0 + cells := tr.GetBase().GetChildren() + tr.Alignments = n.Table.Alignments[:min(len(cells), len(n.Table.Alignments))] + for i, cell := range cells { + c := cell.GetTableCell() + if c == nil { + return nodes + } + if i < len(tr.GetAlignments()) { + c.Alignment = tr.GetAlignments()[i] + } else { + c.Alignment = CellAlignment_CELL_ALIGNMENT_NONE + } + } + } + return append(nodes, &Node{Kind: n}) +} + +func (n *Node_Table) SetColumnAlignments(alignments ...CellAlignment) *Node_Table { + n.Table.Alignments = alignments + return n +} + +func (n *Node_Table) AppendRow(cells ...[]InlineContent) *Node_Table { + if len(cells) == 0 { + return n + } + cellNodes := make([]*Node, len(cells)) + for i, cellContent := range cells { + if cellContent == nil { + continue + } + cellNodes[i] = &Node{ + Kind: &Node_TableCell{ + TableCell: &TableCell{ + Base: &BaseNode{ + Children: Inlines.ExtendNodes(cellContent, nil), + }, + }, + }, + } + } + + n.Table.Base.Children = append(n.Table.Base.Children, &Node{ + Kind: &Node_TableRow{ + TableRow: &TableRow{ + Base: &BaseNode{ + Children: cellNodes, + }, + IsHeader: false, + }, + }, + }) + return n +} + +func NewContent(nodes ...BlockContent) *FabricContentNode { + var children []*Node + for _, node := range nodes { + children = node.ExtendNodes(children) + } + return &FabricContentNode{ + Root: &BaseNode{ + Children: children, + BlankPreviousLines: true, + }, + } +} diff --git a/plugin/ast/v1/testdata/fuzz/FuzzEncoder/6b5e47038908d6af b/plugin/ast/v1/testdata/fuzz/FuzzEncoder/6b5e47038908d6af new file mode 100644 index 00000000..918fda37 --- /dev/null +++ b/plugin/ast/v1/testdata/fuzz/FuzzEncoder/6b5e47038908d6af @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte(" ```\n\t") diff --git a/plugin/content.go b/plugin/content.go index 2317db16..2e53e261 100644 --- a/plugin/content.go +++ b/plugin/content.go @@ -1,10 +1,21 @@ package plugin import ( + "bytes" "fmt" + "log/slog" "slices" "sync" + markdown "github.com/blackstork-io/goldmark-markdown" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/text" + "google.golang.org/protobuf/proto" + + "github.com/blackstork-io/fabric/plugin/ast/astsrc" + "github.com/blackstork-io/fabric/plugin/ast/nodes" + astv1 "github.com/blackstork-io/fabric/plugin/ast/v1" "github.com/blackstork-io/fabric/plugin/plugindata" ) @@ -36,27 +47,28 @@ type Location struct { type ContentResult struct { Location *Location Content Content + // Content *FabricContentNode // switch to this } type Content interface { setID(id uint32) - setMeta(meta *ContentMeta) + setMeta(meta *nodes.ContentMeta) AsData() plugindata.Data ID() uint32 AsPluginData() plugindata.Data - Meta() *ContentMeta + Meta() *nodes.ContentMeta } type ContentEmpty struct { id uint32 - meta *ContentMeta + meta *nodes.ContentMeta } func (n *ContentEmpty) setID(id uint32) { n.id = id } -func (n *ContentEmpty) setMeta(meta *ContentMeta) { +func (n *ContentEmpty) setMeta(meta *nodes.ContentMeta) { n.meta = meta } @@ -72,7 +84,7 @@ func (n *ContentEmpty) ID() uint32 { return n.id } -func (n *ContentEmpty) Meta() *ContentMeta { +func (n *ContentEmpty) Meta() *nodes.ContentMeta { return n.meta } @@ -84,7 +96,7 @@ type ContentSection struct { *idStore id uint32 Children []Content - meta *ContentMeta + meta *nodes.ContentMeta } func NewSection(contentID uint32) *ContentSection { @@ -151,7 +163,7 @@ func (c *ContentSection) setID(id uint32) { c.id = id } -func (c *ContentSection) setMeta(meta *ContentMeta) { +func (c *ContentSection) setMeta(meta *nodes.ContentMeta) { c.meta = meta for _, child := range c.Children { child.setMeta(meta) @@ -162,7 +174,7 @@ func (c *ContentSection) ID() uint32 { return c.id } -func (c *ContentSection) Meta() *ContentMeta { +func (c *ContentSection) Meta() *nodes.ContentMeta { return c.meta } @@ -201,9 +213,129 @@ func (c *ContentSection) AsData() plugindata.Data { } type ContentElement struct { - id uint32 - Markdown string - meta *ContentMeta + // Type transitions: + // mdString <-> source&node <-> serializedNode + + meta *nodes.ContentMeta + id uint32 + + // do not access directly + // legacy markdown string representation + mdString []byte + // serialized node representation + serializedNode *astv1.FabricContentNode + source astsrc.ASTSource + node *nodes.FabricContentNode +} + +// NewElement is the preferred way to create a new content element. +// It accepts a list of AST nodes to build the content element. +func NewElement(content ...astv1.BlockContent) *ContentElement { + var children []*astv1.Node + for _, node := range content { + children = node.ExtendNodes(children) + } + return &ContentElement{ + serializedNode: &astv1.FabricContentNode{ + Root: &astv1.BaseNode{ + Children: children, + }, + }, + } +} + +// NewElementFromMarkdown creates a new content element from a markdown string. +// +// Deprecated: opt in to working with the new AST by using [NewElement] instead. +func NewElementFromMarkdown(source string) *ContentElement { + return &ContentElement{ + mdString: []byte(source), + } +} + +// NewElementFromMarkdownAndAST creates a new content element from a markdown string and an AST. +// This is a temporary method to allow for a smooth transition to the new AST. +// Should only be used for deserialization purposes during the transition. +func NewElementFromMarkdownAndAST(source []byte, ast *astv1.FabricContentNode) *ContentElement { + return &ContentElement{ + mdString: source, + serializedNode: ast, + } +} + +var BaseMarkdownOptions = goldmark.WithExtensions( + extension.Table, + extension.Strikethrough, + extension.TaskList, +) + +// AsMarkdownSrc returns the markdown source of the content element. +// +// Deprecated: opt in to working with the new AST by using .AsNode() +func (c *ContentElement) AsMarkdownSrc() []byte { + if c.mdString != nil { + return c.mdString + } + + source, node := c.AsNode() + var buf bytes.Buffer + err := goldmark.New( + BaseMarkdownOptions, + goldmark.WithExtensions( + markdown.NewRenderer( + markdown.WithIgnoredNodes( + nodes.ContentNodeKind, + nodes.CustomBlockKind, + nodes.CustomInlineKind, + ), + ), + ), + ).Renderer().Render(&buf, source.AsBytes(), node) + if err != nil { + slog.Error("failed to render markdown", "error", err) + } + c.mdString = buf.Bytes() + return c.mdString +} + +func (c *ContentElement) AsSerializedNode() *astv1.FabricContentNode { + if c.serializedNode != nil { + return c.serializedNode + } + src, node := c.AsNode() + serNode, err := astv1.Encode(node, src.AsBytes()) + if err != nil { + slog.Error("failed to encode AST", "error", err) + } + c.serializedNode = serNode.GetContentNode() + return c.serializedNode +} + +func (c *ContentElement) AsNode() (*astsrc.ASTSource, *nodes.FabricContentNode) { + if c.node != nil { + return &c.source, c.node + } + if c.serializedNode != nil { + node, source, err := astv1.Decode(&astv1.Node{ + Kind: &astv1.Node_ContentNode{ + ContentNode: c.serializedNode, + }, + }) + if err != nil { + slog.Error("failed to decode AST", "error", err) + } else { + c.node = nodes.ToFabricContentNode(node) + c.node.Meta = c.meta + c.source = source + } + } else { + node := goldmark.New(BaseMarkdownOptions). + Parser().Parse(text.NewReader(c.mdString)) + c.node = nodes.ToFabricContentNode(node) + c.node.Meta = c.meta + } + + return &c.source, c.node } func (c *ContentElement) ID() uint32 { @@ -214,11 +346,11 @@ func (c *ContentElement) setID(id uint32) { c.id = id } -func (c *ContentElement) Meta() *ContentMeta { +func (c *ContentElement) Meta() *nodes.ContentMeta { return c.meta } -func (c *ContentElement) setMeta(meta *ContentMeta) { +func (c *ContentElement) setMeta(meta *nodes.ContentMeta) { c.meta = meta } @@ -226,33 +358,30 @@ func (c *ContentElement) AsPluginData() plugindata.Data { return c.AsData() } +func (c *ContentElement) IsAst() bool { + return c.node != nil || c.serializedNode != nil +} + func (c *ContentElement) AsData() plugindata.Data { if c == nil { return nil } - return plugindata.Map{ + data := plugindata.Map{ "type": plugindata.String("element"), "id": plugindata.Number(c.id), - "markdown": plugindata.String(c.Markdown), + "markdown": plugindata.String(c.AsMarkdownSrc()), "meta": c.meta.AsData(), } -} - -type ContentMeta struct { - Provider string - Plugin string - Version string -} - -func (meta *ContentMeta) AsData() plugindata.Data { - if meta == nil { - return nil - } - return plugindata.Map{ - "provider": plugindata.String(meta.Provider), - "plugin": plugindata.String(meta.Plugin), - "version": plugindata.String(meta.Version), + // we have some AST data, include it + if c.IsAst() { + ser, err := proto.Marshal(c.AsSerializedNode()) + if err != nil { + slog.Warn("failed to preserve AST in element", "error", err) + } else { + data["__ast"] = plugindata.String(ser) + } } + return data } func ParseContentData(data plugindata.Map) (Content, error) { @@ -312,7 +441,7 @@ func parseContentElement(data plugindata.Map) (*ContentElement, error) { if !ok { return nil, fmt.Errorf("missing markdown") } - elem.Markdown = string(markdown) + elem.mdString = []byte(markdown) id, ok := data["id"].(plugindata.Number) if ok { elem.id = uint32(id) @@ -321,6 +450,16 @@ func parseContentElement(data plugindata.Map) (*ContentElement, error) { if ok { elem.meta = ParseContentMeta(meta) } + if astData, ok := data["__ast"].(plugindata.String); ok { + // we have some AST data, include it + serNode := &astv1.FabricContentNode{} + err := proto.Unmarshal([]byte(astData), serNode) + if err != nil { + slog.Warn("failed to decode AST in element", "error", err) + } else { + elem.serializedNode = serNode + } + } return elem, nil } @@ -341,7 +480,7 @@ func parseContentEmpty(data plugindata.Map) (*ContentEmpty, error) { return empty, nil } -func ParseContentMeta(data plugindata.Data) *ContentMeta { +func ParseContentMeta(data plugindata.Data) *nodes.ContentMeta { if data == nil { return nil } @@ -349,7 +488,7 @@ func ParseContentMeta(data plugindata.Data) *ContentMeta { provider, _ := meta["provider"].(plugindata.String) plugin, _ := meta["plugin"].(plugindata.String) version, _ := meta["version"].(plugindata.String) - return &ContentMeta{ + return &nodes.ContentMeta{ Provider: string(provider), Plugin: string(plugin), Version: string(version), diff --git a/plugin/pluginapi/v1/content.pb.go b/plugin/pluginapi/v1/content.pb.go index d130847b..55a7ddd1 100644 --- a/plugin/pluginapi/v1/content.pb.go +++ b/plugin/pluginapi/v1/content.pb.go @@ -7,6 +7,7 @@ package pluginapiv1 import ( + v1 "github.com/blackstork-io/fabric/plugin/ast/v1" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -326,7 +327,8 @@ type ContentElement struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Markdown string `protobuf:"bytes,1,opt,name=markdown,proto3" json:"markdown,omitempty"` + Markdown []byte `protobuf:"bytes,1,opt,name=markdown,proto3" json:"markdown,omitempty"` + Ast *v1.FabricContentNode `protobuf:"bytes,2,opt,name=ast,proto3,oneof" json:"ast,omitempty"` } func (x *ContentElement) Reset() { @@ -361,11 +363,18 @@ func (*ContentElement) Descriptor() ([]byte, []int) { return file_pluginapi_v1_content_proto_rawDescGZIP(), []int{4} } -func (x *ContentElement) GetMarkdown() string { +func (x *ContentElement) GetMarkdown() []byte { if x != nil { return x.Markdown } - return "" + return nil +} + +func (x *ContentElement) GetAst() *v1.FabricContentNode { + if x != nil { + return x.Ast + } + return nil } type ContentEmpty struct { @@ -411,58 +420,63 @@ var File_pluginapi_v1_content_proto protoreflect.FileDescriptor var file_pluginapi_v1_content_proto_rawDesc = []byte{ 0x0a, 0x1a, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x70, 0x6c, - 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x22, 0x56, 0x0a, 0x08, 0x4c, 0x6f, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x34, 0x0a, 0x06, - 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, - 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x52, 0x06, 0x65, 0x66, 0x66, 0x65, - 0x63, 0x74, 0x22, 0x74, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, - 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xba, 0x01, 0x0a, 0x07, 0x43, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x07, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x45, 0x6c, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x07, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x38, - 0x0a, 0x07, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, - 0x07, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x65, 0x6d, 0x70, 0x74, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, - 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x48, 0x00, 0x52, 0x05, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x07, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x43, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, - 0x72, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x6c, 0x75, 0x67, + 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x1a, 0x10, 0x61, 0x73, 0x74, 0x2f, + 0x76, 0x31, 0x2f, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x56, 0x0a, 0x08, + 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x34, + 0x0a, 0x06, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, + 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x52, 0x06, 0x65, 0x66, + 0x66, 0x65, 0x63, 0x74, 0x22, 0x74, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x07, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xba, 0x01, 0x0a, 0x07, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x07, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x45, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x07, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x12, 0x38, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, + 0x00, 0x52, 0x07, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x65, 0x6d, + 0x70, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x0e, 0x43, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, - 0x6d, 0x61, 0x72, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x6d, 0x61, 0x72, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x22, 0x0e, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x2a, 0x68, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x1b, 0x4c, 0x4f, - 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x46, 0x46, 0x45, 0x43, 0x54, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x4c, - 0x4f, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x46, 0x46, 0x45, 0x43, 0x54, 0x5f, 0x42, - 0x45, 0x46, 0x4f, 0x52, 0x45, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x4c, 0x4f, 0x43, 0x41, 0x54, - 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x46, 0x46, 0x45, 0x43, 0x54, 0x5f, 0x41, 0x46, 0x54, 0x45, 0x52, - 0x10, 0x02, 0x42, 0xb2, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, - 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x73, 0x74, 0x6f, 0x72, 0x6b, 0x2d, 0x69, - 0x6f, 0x2f, 0x66, 0x61, 0x62, 0x72, 0x69, 0x63, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, - 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x3b, 0x70, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x50, 0x58, 0x58, 0xaa, 0x02, - 0x0c, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0c, - 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, 0x50, - 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, - 0x61, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x48, 0x00, 0x52, 0x05, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x07, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x43, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x08, 0x63, 0x68, 0x69, + 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x22, 0x66, 0x0a, 0x0e, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x6d, 0x61, 0x72, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x6d, 0x61, 0x72, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x30, 0x0a, 0x03, 0x61, 0x73, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x46, 0x61, 0x62, 0x72, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4e, 0x6f, + 0x64, 0x65, 0x48, 0x00, 0x52, 0x03, 0x61, 0x73, 0x74, 0x88, 0x01, 0x01, 0x42, 0x06, 0x0a, 0x04, + 0x5f, 0x61, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x2a, 0x68, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x12, 0x1f, 0x0a, 0x1b, 0x4c, 0x4f, 0x43, 0x41, 0x54, 0x49, + 0x4f, 0x4e, 0x5f, 0x45, 0x46, 0x46, 0x45, 0x43, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x4c, 0x4f, 0x43, 0x41, 0x54, + 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x46, 0x46, 0x45, 0x43, 0x54, 0x5f, 0x42, 0x45, 0x46, 0x4f, 0x52, + 0x45, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x4c, 0x4f, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x45, 0x46, 0x46, 0x45, 0x43, 0x54, 0x5f, 0x41, 0x46, 0x54, 0x45, 0x52, 0x10, 0x02, 0x42, 0xb2, + 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x73, 0x74, 0x6f, 0x72, 0x6b, 0x2d, 0x69, 0x6f, 0x2f, 0x66, 0x61, + 0x62, 0x72, 0x69, 0x63, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x3b, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, + 0x70, 0x69, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x50, 0x58, 0x58, 0xaa, 0x02, 0x0c, 0x50, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0c, 0x50, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x61, 0x70, 0x69, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, 0x50, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x61, 0x70, 0x69, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x61, 0x70, 0x69, 0x3a, + 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -480,13 +494,14 @@ func file_pluginapi_v1_content_proto_rawDescGZIP() []byte { var file_pluginapi_v1_content_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_pluginapi_v1_content_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_pluginapi_v1_content_proto_goTypes = []any{ - (LocationEffect)(0), // 0: pluginapi.v1.LocationEffect - (*Location)(nil), // 1: pluginapi.v1.Location - (*ContentResult)(nil), // 2: pluginapi.v1.ContentResult - (*Content)(nil), // 3: pluginapi.v1.Content - (*ContentSection)(nil), // 4: pluginapi.v1.ContentSection - (*ContentElement)(nil), // 5: pluginapi.v1.ContentElement - (*ContentEmpty)(nil), // 6: pluginapi.v1.ContentEmpty + (LocationEffect)(0), // 0: pluginapi.v1.LocationEffect + (*Location)(nil), // 1: pluginapi.v1.Location + (*ContentResult)(nil), // 2: pluginapi.v1.ContentResult + (*Content)(nil), // 3: pluginapi.v1.Content + (*ContentSection)(nil), // 4: pluginapi.v1.ContentSection + (*ContentElement)(nil), // 5: pluginapi.v1.ContentElement + (*ContentEmpty)(nil), // 6: pluginapi.v1.ContentEmpty + (*v1.FabricContentNode)(nil), // 7: ast.v1.FabricContentNode } var file_pluginapi_v1_content_proto_depIdxs = []int32{ 0, // 0: pluginapi.v1.Location.effect:type_name -> pluginapi.v1.LocationEffect @@ -496,11 +511,12 @@ var file_pluginapi_v1_content_proto_depIdxs = []int32{ 4, // 4: pluginapi.v1.Content.section:type_name -> pluginapi.v1.ContentSection 6, // 5: pluginapi.v1.Content.empty:type_name -> pluginapi.v1.ContentEmpty 3, // 6: pluginapi.v1.ContentSection.children:type_name -> pluginapi.v1.Content - 7, // [7:7] is the sub-list for method output_type - 7, // [7:7] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 7, // 7: pluginapi.v1.ContentElement.ast:type_name -> ast.v1.FabricContentNode + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_pluginapi_v1_content_proto_init() } @@ -587,6 +603,7 @@ func file_pluginapi_v1_content_proto_init() { (*Content_Section)(nil), (*Content_Empty)(nil), } + file_pluginapi_v1_content_proto_msgTypes[4].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/plugin/pluginapi/v1/content_decoder.go b/plugin/pluginapi/v1/content_decoder.go index 627fee96..4b271d8a 100644 --- a/plugin/pluginapi/v1/content_decoder.go +++ b/plugin/pluginapi/v1/content_decoder.go @@ -1,6 +1,12 @@ package pluginapiv1 -import "github.com/blackstork-io/fabric/plugin" +import ( + "fmt" + "log/slog" + + "github.com/blackstork-io/fabric/pkg/utils" + "github.com/blackstork-io/fabric/plugin" +) func decodeContentResult(src *ContentResult) *plugin.ContentResult { if src == nil { @@ -13,48 +19,21 @@ func decodeContentResult(src *ContentResult) *plugin.ContentResult { } func decodeContent(src *Content) plugin.Content { - if src == nil { - return nil - } - switch val := src.Value.(type) { + switch val := src.GetValue().(type) { case *Content_Element: - return decodeContentElement(val.Element) + return plugin.NewElementFromMarkdownAndAST(val.Element.GetMarkdown(), val.Element.GetAst()) case *Content_Section: - return decodeContentSection(val.Section) + return &plugin.ContentSection{ + Children: utils.FnMap(val.Section.GetChildren(), decodeContent), + } case *Content_Empty: - return decodeContentEmpty(val.Empty) - default: - return nil - } -} - -func decodeContentElement(src *ContentElement) *plugin.ContentElement { - if src == nil { + return &plugin.ContentEmpty{} + case nil: return nil - } - return &plugin.ContentElement{ - Markdown: src.GetMarkdown(), - } -} - -func decodeContentEmpty(src *ContentEmpty) *plugin.ContentEmpty { - if src == nil { - return nil - } - return &plugin.ContentEmpty{} -} - -func decodeContentSection(src *ContentSection) *plugin.ContentSection { - if src == nil { + default: + slog.Error("unknown content type", "type", fmt.Sprintf("%T", src)) return nil } - children := make([]plugin.Content, len(src.GetChildren())) - for i, child := range src.GetChildren() { - children[i] = decodeContent(child) - } - return &plugin.ContentSection{ - Children: children, - } } func decodeLocation(src *Location) *plugin.Location { diff --git a/plugin/pluginapi/v1/content_encoder.go b/plugin/pluginapi/v1/content_encoder.go index 70a67c1e..0d64f175 100644 --- a/plugin/pluginapi/v1/content_encoder.go +++ b/plugin/pluginapi/v1/content_encoder.go @@ -1,6 +1,12 @@ package pluginapiv1 -import "github.com/blackstork-io/fabric/plugin" +import ( + "fmt" + "log/slog" + + "github.com/blackstork-io/fabric/pkg/utils" + "github.com/blackstork-io/fabric/plugin" +) func encodeContentResult(src *plugin.ContentResult) *ContentResult { if src == nil { @@ -13,60 +19,43 @@ func encodeContentResult(src *plugin.ContentResult) *ContentResult { } func encodeContent(src plugin.Content) *Content { - if src == nil { - return nil - } + var variant isContent_Value switch val := src.(type) { + case nil: + return nil case *plugin.ContentElement: - return &Content{ - Value: &Content_Element{ - Element: encodeContentElement(val), - }, + if val == nil { + break + } + el := &ContentElement{ + Markdown: val.AsMarkdownSrc(), + } + if val.IsAst() { + el.Ast = val.AsSerializedNode() + } + variant = &Content_Element{ + Element: el, } case *plugin.ContentSection: - return &Content{ - Value: &Content_Section{ - Section: encodeContentSection(val), + if val == nil { + break + } + variant = &Content_Section{ + Section: &ContentSection{ + Children: utils.FnMap(val.Children, encodeContent), }, } case *plugin.ContentEmpty: - return &Content{ - Value: &Content_Empty{ - Empty: encodeContentEmpty(val), - }, + variant = &Content_Empty{ + Empty: &ContentEmpty{}, } default: - return nil - } -} - -func encodeContentSection(src *plugin.ContentSection) *ContentSection { - if src == nil { - return nil - } - children := make([]*Content, len(src.Children)) - for i, child := range src.Children { - children[i] = encodeContent(child) - } - return &ContentSection{ - Children: children, - } -} - -func encodeContentElement(src *plugin.ContentElement) *ContentElement { - if src == nil { - return nil - } - return &ContentElement{ - Markdown: src.Markdown, + slog.Error("unknown content type", "type", fmt.Sprintf("%T", src)) } -} -func encodeContentEmpty(src *plugin.ContentEmpty) *ContentEmpty { - if src == nil { - return nil + return &Content{ + Value: variant, } - return &ContentEmpty{} } func encodeLocation(src *plugin.Location) *Location { @@ -74,7 +63,7 @@ func encodeLocation(src *plugin.Location) *Location { return nil } return &Location{ - Index: uint32(src.Index), + Index: src.Index, Effect: encodeLocationEffect(src.Effect), } } diff --git a/plugin/schema.go b/plugin/schema.go index 4ce815a0..61f2fcca 100644 --- a/plugin/schema.go +++ b/plugin/schema.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/blackstork-io/fabric/pkg/diagnostics" + "github.com/blackstork-io/fabric/plugin/ast/nodes" "github.com/blackstork-io/fabric/plugin/plugindata" ) @@ -102,7 +103,8 @@ func (p *Schema) ProvideContent(ctx context.Context, name string, params *Provid if diags.HasErrors() { return nil, diags } - result.Content.setMeta(&ContentMeta{ + // TODO: set metadata in content provider + result.Content.setMeta(&nodes.ContentMeta{ Provider: name, Plugin: p.Name, Version: p.Version, diff --git a/print/htmlprint/printer.go b/print/htmlprint/printer.go index e9562dc6..8d1a7c43 100644 --- a/print/htmlprint/printer.go +++ b/print/htmlprint/printer.go @@ -8,11 +8,9 @@ import ( "fmt" "html/template" "io" - "strings" "github.com/pelletier/go-toml/v2" "github.com/yuin/goldmark" - "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/renderer/html" "gopkg.in/yaml.v3" @@ -86,7 +84,7 @@ func (p Printer) Print(ctx context.Context, w io.Writer, el plugin.Content) (err return err } md := goldmark.New( - goldmark.WithExtensions(extension.GFM), + plugin.BaseMarkdownOptions, goldmark.WithParserOptions( parser.WithAutoHeadingID(), ), @@ -151,9 +149,9 @@ func (p Printer) firstTitle(el plugin.Content) (string, bool) { case *plugin.ContentElement: meta := el.Meta() if meta.Plugin == "blackstork/builtin" && meta.Provider == "title" { - return strings.TrimSpace( - strings.TrimPrefix(el.Markdown, "#"), - ), true + return string(bytes.TrimSpace( + bytes.TrimPrefix(el.AsMarkdownSrc(), []byte("#")), + )), true } } return "", false @@ -179,16 +177,16 @@ func (p Printer) extractFrontmatter(el plugin.Content) (*plugin.ContentElement, } func (p Printer) parseFrontmatter(fm *plugin.ContentElement) (result map[string]any, err error) { - str := fm.Markdown + str := fm.AsMarkdownSrc() switch { - case strings.HasPrefix(str, "{"): - err = json.Unmarshal([]byte(str), &result) - case strings.HasPrefix(str, "---"): - str = strings.Trim(str, "-") - err = yaml.Unmarshal([]byte(str), &result) - case strings.HasPrefix(str, "+++"): - str = strings.Trim(str, "+") - err = toml.Unmarshal([]byte(str), &result) + case bytes.HasPrefix(str, []byte("{")): + err = json.Unmarshal(str, &result) + case bytes.HasPrefix(str, []byte("---")): + str = bytes.Trim(str, "-") + err = yaml.Unmarshal(str, &result) + case bytes.HasPrefix(str, []byte("+++")): + str = bytes.Trim(str, "+") + err = toml.Unmarshal(str, &result) default: err = fmt.Errorf("invalid frontmatter format") } diff --git a/print/mdprint/printer.go b/print/mdprint/printer.go index 0888eaca..c1cf2329 100644 --- a/print/mdprint/printer.go +++ b/print/mdprint/printer.go @@ -3,9 +3,18 @@ package mdprint import ( "bytes" "context" + "fmt" "io" + "log/slog" + + markdown "github.com/blackstork-io/goldmark-markdown" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/ast" "github.com/blackstork-io/fabric/plugin" + "github.com/blackstork-io/fabric/plugin/ast/astsrc" + "github.com/blackstork-io/fabric/plugin/ast/nodes" + "github.com/blackstork-io/fabric/print" ) // Printer is the interface for printing markdown content. @@ -38,19 +47,51 @@ func (p Printer) Print(ctx context.Context, w io.Writer, el plugin.Content) (err func (p Printer) printContent(w io.Writer, content plugin.Content) (err error) { switch content := content.(type) { case *plugin.ContentElement: - if err = p.printContentElement(w, content); err != nil { - return + if content.IsAst() { + src, node := content.AsNode() + err = p.printContentElement(w, src, node) + } else { + _, err = w.Write(content.AsMarkdownSrc()) } case *plugin.ContentSection: - if err = p.printContentSection(w, content); err != nil { - return - } + err = p.printContentSection(w, content) } - return nil + return } -func (p Printer) printContentElement(w io.Writer, elem *plugin.ContentElement) error { - _, err := w.Write([]byte(elem.Markdown)) +func (p Printer) printContentElement(w io.Writer, source *astsrc.ASTSource, node *nodes.FabricContentNode) error { + n := print.ReplaceNodes(node, func(n ast.Node) (repl ast.Node, skipChildren bool) { + switch nT := n.(type) { + case *nodes.CustomBlock: + slog.Info("OtherBlock found in AST, replacing with message segment") + p := ast.NewCodeBlock() + p.AppendChild(p, ast.NewRawTextSegment( + source.Appendf("", nT.Data.GetTypeUrl()), + )) + return p, false + case *nodes.CustomInline: + slog.Info("OtherInline found in AST, replacing with message segment") + p := ast.NewCodeSpan() + p.AppendChild(p, ast.NewRawTextSegment( + source.Appendf("", nT.Data.GetTypeUrl()), + )) + return p, false + } + return n, false + }) + err := goldmark.New( + plugin.BaseMarkdownOptions, + goldmark.WithExtensions( + markdown.NewRenderer( + markdown.WithIgnoredNodes( + nodes.ContentNodeKind, + ), + ), + ), + ).Renderer().Render(w, source.AsBytes(), n) + if err != nil { + return fmt.Errorf("failed to render markdown from AST: %w", err) + } return err } diff --git a/print/pdfprint/printer.go b/print/pdfprint/printer.go index 6a49c586..813f3ed8 100644 --- a/print/pdfprint/printer.go +++ b/print/pdfprint/printer.go @@ -5,13 +5,16 @@ import ( "context" "image/color" "io" + "log/slog" pdf "github.com/stephenafamo/goldmark-pdf" "github.com/yuin/goldmark" - "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/parser" "github.com/blackstork-io/fabric/plugin" + "github.com/blackstork-io/fabric/plugin/ast/astsrc" + "github.com/blackstork-io/fabric/print" "github.com/blackstork-io/fabric/print/mdprint" ) @@ -32,13 +35,34 @@ func Print(w io.Writer, el plugin.Content) error { } func (p Printer) Print(ctx context.Context, w io.Writer, el plugin.Content) (err error) { - p.removeFrontatter(el) - buf := bytes.NewBuffer(nil) + p.removeFrontmatter(el) + print.ReplaceNodesInContent(el, func(src *astsrc.ASTSource, n ast.Node) (repl ast.Node, skipChildren bool) { + switch n := n.(type) { + case *ast.HTMLBlock: + slog.Info("HTML block found in AST, replacing with message segment") + p := ast.NewCodeBlock() + p.AppendChild(p, ast.NewRawTextSegment( + src.Appendf("", n.Kind()), + )) + return p, false + case *ast.RawHTML: + slog.Info("Raw HTML found in AST, replacing with message segment") + p := ast.NewCodeSpan() + p.AppendChild(p, ast.NewRawTextSegment( + src.Appendf("", n.Kind()), + )) + return p, false + } + return n, false + }) + + buf := &bytes.Buffer{} if err := p.md.Print(ctx, buf, el); err != nil { return err } + md := goldmark.New( - goldmark.WithExtensions(extension.GFM), + plugin.BaseMarkdownOptions, goldmark.WithParserOptions( parser.WithAutoHeadingID(), ), @@ -59,7 +83,7 @@ func (p Printer) Print(ctx context.Context, w io.Writer, el plugin.Content) (err return md.Convert(buf.Bytes(), w) } -func (p Printer) removeFrontatter(el plugin.Content) bool { +func (p Printer) removeFrontmatter(el plugin.Content) bool { section, ok := el.(*plugin.ContentSection) if !ok { return false diff --git a/print/printer.go b/print/printer.go index 3488f1e6..5cebf0c6 100644 --- a/print/printer.go +++ b/print/printer.go @@ -4,10 +4,60 @@ import ( "context" "io" + "github.com/yuin/goldmark/ast" + "github.com/blackstork-io/fabric/plugin" + "github.com/blackstork-io/fabric/plugin/ast/astsrc" ) // Printer is the interface for printing content. type Printer interface { Print(ctx context.Context, w io.Writer, el plugin.Content) error } + +// ReplaceNodes walks the AST starting from the given node and replaces nodes in it. +// If replacer returns nil - the node is deleted +func ReplaceNodes(n ast.Node, replacer func(n ast.Node) (repl ast.Node, skipChildren bool)) ast.Node { + if n == nil { + return nil + } + n, skipChildren := replacer(n) + if n == nil || skipChildren { + return n + } + c := n.FirstChild() + for c != nil { + repl := ReplaceNodes(c, replacer) + switch repl { + case nil: + next := c.NextSibling() + n.RemoveChild(n, c) + c = next + case c: + c = c.NextSibling() + default: + n.ReplaceChild(n, c, repl) + c = repl + } + } + return n +} + +// ReplaceNodesInContent runs ReplaceNodes on plugin.ContentAST nodes +func ReplaceNodesInContent(el plugin.Content, replacer func(src *astsrc.ASTSource, n ast.Node) (repl ast.Node, skipChildren bool)) { + switch el := el.(type) { + case *plugin.ContentSection: + for _, child := range el.Children { + ReplaceNodesInContent(child, replacer) + } + case *plugin.ContentElement: + if !el.IsAst() { + return + } + src, node := el.AsNode() + ReplaceNodes(node, func(n ast.Node) (repl ast.Node, skipChildren bool) { + return replacer(src, n) + }) + node.Dump(src.AsBytes(), 0) + } +} diff --git a/proto/ast/v1/ast.proto b/proto/ast/v1/ast.proto new file mode 100644 index 00000000..5d89481a --- /dev/null +++ b/proto/ast/v1/ast.proto @@ -0,0 +1,232 @@ +syntax = "proto3"; + +package ast.v1; + +import "google/protobuf/any.proto"; + +message Attribute { + bytes name = 1; + oneof value { + bytes bytes = 2; + string str = 3; + } +} + +message BaseNode { + repeated Node children = 1; + repeated Attribute attributes = 2; + // value meaningful only for blocks + bool blank_previous_lines = 3; +} + +message Node { + oneof kind { + // Blocks + Document document = 1; + TextBlock text_block = 5; + Paragraph paragraph = 6; + Heading heading = 7; + ThematicBreak thematic_break = 8; + CodeBlock code_block = 9; + FencedCodeBlock fenced_code_block = 10; + Blockquote blockquote = 11; + List list = 12; + ListItem list_item = 13; + HTMLBlock html_block = 14; + + // inlines + Text text = 15; + String string = 16; + CodeSpan code_span = 17; + Emphasis emphasis = 18; + LinkOrImage link_or_image = 19; + AutoLink auto_link = 20; + RawHTML raw_html = 21; + + // Github Flavored Markdown + // blocks + Table table = 22; + TableRow table_row = 23; + TableCell table_cell = 24; + // inline + TaskCheckbox task_checkbox = 25; + Strikethrough strikethrough = 26; + + + // Root of the plugin-rendered data + FabricContentNode content_node = 254; + // Custom node types can be serialized using this + CustomNode custom = 255; + } +} + +// Node kinds + +message Document { + BaseNode base = 1; +} + +message TextBlock { + BaseNode base = 1; +} + +message Paragraph { + BaseNode base = 1; +} + +message Heading { + BaseNode base = 1; + uint32 level = 2; +} + +message ThematicBreak { + BaseNode base = 1; +} + +message CodeBlock { + BaseNode base = 1; + repeated bytes lines = 2; +} + +message FencedCodeBlock { + BaseNode base = 1; + Text info = 2; + repeated bytes lines = 3; +} + +message Blockquote { + BaseNode base = 1; +} + +message List { + BaseNode base = 1; + uint32 marker = 2; + bool is_tight = 3; + uint32 start = 4; +} + +message ListItem { + BaseNode base = 1; + int64 offset = 2; +} + +enum HTMLBlockType { + HTML_BLOCK_TYPE_UNSPECIFIED = 0; + HTML_BLOCK_TYPE_1 = 1; + HTML_BLOCK_TYPE_2 = 2; + HTML_BLOCK_TYPE_3 = 3; + HTML_BLOCK_TYPE_4 = 4; + HTML_BLOCK_TYPE_5 = 5; + HTML_BLOCK_TYPE_6 = 6; + HTML_BLOCK_TYPE_7 = 7; +} + +message HTMLBlock { + BaseNode base = 1; + HTMLBlockType type = 2; + repeated bytes lines = 3; + bytes closure_line = 4; +} + +message Text { + BaseNode base = 1; + bytes segment = 2; + bool soft_line_break = 3; + bool hard_line_break = 4; + bool raw = 5; +} + +message String { + BaseNode base = 1; + bytes value = 2; + bool raw = 3; + bool code = 4; +} + +message CodeSpan { + BaseNode base = 1; +} + +message Emphasis { + BaseNode base = 1; + int64 level = 2; +} + +message LinkOrImage { + BaseNode base = 1; + bytes destination = 2; + bytes title = 3; + bool is_image = 4; +} + +enum AutoLinkType { + AUTO_LINK_TYPE_UNSPECIFIED = 0; + AUTO_LINK_TYPE_EMAIL = 1; + AUTO_LINK_TYPE_URL = 2; +} + +message AutoLink { + BaseNode base = 1; + AutoLinkType type = 2; + bytes protocol = 3; + bytes value = 4; +} + +message RawHTML { + BaseNode base = 1; + repeated bytes segments = 2; +} + +enum CellAlignment { + CELL_ALIGNMENT_UNSPECIFIED = 0; + CELL_ALIGNMENT_LEFT = 1; + CELL_ALIGNMENT_RIGHT = 2; + CELL_ALIGNMENT_CENTER = 3; + CELL_ALIGNMENT_NONE = 4; +} + +message Table { + BaseNode base = 1; + repeated CellAlignment alignments = 2; +} + +message TableRow { + BaseNode base = 1; + repeated CellAlignment alignments = 2; + bool is_header = 4; +} + +message TableCell { + BaseNode base = 1; + CellAlignment alignment = 2; +} + +message TaskCheckbox { + BaseNode base = 1; + bool is_checked = 2; +} + +message Strikethrough { + BaseNode base = 1; +} + +message CustomNode { + // Indicates that this block is an inline element + bool is_inline = 1; + google.protobuf.Any data = 2; + bool blank_previous_lines = 3; +} + +message Metadata { + // ie "blackstork/builtin" + string provider = 1; + // ie "title" + string plugin = 2; + string version = 3; +} + +// Root of the plugin-rendered data +message FabricContentNode { + Metadata metadata = 1; + BaseNode root = 2; // direct content, no document node +} diff --git a/proto/pluginapi/v1/content.proto b/proto/pluginapi/v1/content.proto index 0038bfe0..a3f84592 100644 --- a/proto/pluginapi/v1/content.proto +++ b/proto/pluginapi/v1/content.proto @@ -1,6 +1,7 @@ syntax = "proto3"; package pluginapi.v1; +import "ast/v1/ast.proto"; enum LocationEffect { @@ -21,9 +22,9 @@ message ContentResult { message Content { oneof value { - ContentElement element = 1; - ContentSection section = 2; - ContentEmpty empty = 3; + ContentElement element = 1; + ContentSection section = 2; + ContentEmpty empty = 3; }; } @@ -32,7 +33,8 @@ message ContentSection { } message ContentElement { - string markdown = 1; + bytes markdown = 1; + optional ast.v1.FabricContentNode ast = 2; } message ContentEmpty {} \ No newline at end of file From 20d1cb9f780dcd4bd743cc8a5e1488b3cebd44e8 Mon Sep 17 00:00:00 2001 From: Andrew Morozko Date: Fri, 6 Sep 2024 12:54:16 +0400 Subject: [PATCH 06/12] Trivial: typos and formatting --- justfile | 2 +- pkg/utils/golang.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/justfile b/justfile index bee56388..347f12fa 100644 --- a/justfile +++ b/justfile @@ -8,7 +8,7 @@ test-run: format: go mod tidy - ./gen_code.sh + ./codegen/format.sh format-extra: format gofumpt -w -extra . diff --git a/pkg/utils/golang.go b/pkg/utils/golang.go index 2651b100..4222c535 100644 --- a/pkg/utils/golang.go +++ b/pkg/utils/golang.go @@ -1,10 +1,12 @@ package utils -import "reflect" +import ( + "reflect" +) // Correct version of nil check, works on nil interfaces as well as any other value. func IsNil(val any) bool { - // Checking for nil on interface objects is terrble + // Checking for nil on interface objects is terrible // Thanks to: https://stackoverflow.com/a/76595928/4632951 if val == nil { return true From 1fdf597f954bd5755049e70019777a90f2b93bc2 Mon Sep 17 00:00:00 2001 From: Andrew Morozko Date: Fri, 6 Sep 2024 12:54:16 +0400 Subject: [PATCH 07/12] Trivial: update plugins to new interface --- examples/plugins/basic/content_greeting.go | 4 +-- internal/builtin/content_blockquote.go | 4 +-- internal/builtin/content_blockquote_test.go | 27 +++++++++---------- internal/builtin/content_code.go | 4 +-- internal/builtin/content_code_test.go | 18 ++++++------- internal/builtin/content_frontmatter.go | 4 +-- internal/builtin/content_image.go | 4 +-- internal/builtin/content_list.go | 4 +-- internal/builtin/content_table.go | 4 +-- internal/builtin/content_text.go | 4 +-- internal/builtin/content_title.go | 4 +-- internal/builtin/content_toc.go | 6 ++--- .../microsoft/content_azure_openai_text.go | 4 +-- internal/openai/content_openai_text.go | 4 +-- 14 files changed, 33 insertions(+), 62 deletions(-) diff --git a/examples/plugins/basic/content_greeting.go b/examples/plugins/basic/content_greeting.go index e5937008..5f312f7e 100644 --- a/examples/plugins/basic/content_greeting.go +++ b/examples/plugins/basic/content_greeting.go @@ -36,8 +36,6 @@ func renderGreetingMessage(ctx context.Context, params *plugin.ProvideContentPar // that it exists, non-null and non-empty, with whitespace trimmed name := params.Args.GetAttrVal("name").AsString() return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: fmt.Sprintf("Hello, %s!", name), - }, + Content: plugin.NewElementFromMarkdown(fmt.Sprintf("Hello, %s!", name)), }, nil } diff --git a/internal/builtin/content_blockquote.go b/internal/builtin/content_blockquote.go index e7988927..9af61e81 100644 --- a/internal/builtin/content_blockquote.go +++ b/internal/builtin/content_blockquote.go @@ -40,8 +40,6 @@ func genBlockQuoteContent(ctx context.Context, params *plugin.ProvideContentPara } text = "> " + strings.ReplaceAll(text, "\n", "\n> ") return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: text, - }, + Content: plugin.NewElementFromMarkdown(text), }, nil } diff --git a/internal/builtin/content_blockquote_test.go b/internal/builtin/content_blockquote_test.go index f80976df..2837d9b3 100644 --- a/internal/builtin/content_blockquote_test.go +++ b/internal/builtin/content_blockquote_test.go @@ -61,11 +61,10 @@ func (s *BlockQuoteTestSuite) TestCallBlockquote() { }, }) s.Empty(diags) - s.Equal(&plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: "> Hello World!", - }, - }, content) + s.Equal( + plugindata.String("> Hello World!"), + content.Content.AsData().(plugindata.Map)["markdown"], + ) } func (s *BlockQuoteTestSuite) TestCallBlockquoteMultiline() { @@ -80,11 +79,10 @@ func (s *BlockQuoteTestSuite) TestCallBlockquoteMultiline() { }, }) s.Empty(diags) - s.Equal(&plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: "> Hello\n> World\n> for you!", - }, - }, content) + s.Equal( + plugindata.String("> Hello\n> World\n> for you!"), + content.Content.AsData().(plugindata.Map)["markdown"], + ) } func (s *BlockQuoteTestSuite) TestCallBlockquoteMultilineDoubleNewline() { @@ -99,9 +97,8 @@ func (s *BlockQuoteTestSuite) TestCallBlockquoteMultilineDoubleNewline() { }, }) s.Empty(diags) - s.Equal(&plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: "> Hello\n> World\n> \n> for you!", - }, - }, content) + s.Equal( + plugindata.String("> Hello\n> World\n> \n> for you!"), + content.Content.AsData().(plugindata.Map)["markdown"], + ) } diff --git a/internal/builtin/content_code.go b/internal/builtin/content_code.go index abcab048..f142c6bb 100644 --- a/internal/builtin/content_code.go +++ b/internal/builtin/content_code.go @@ -50,8 +50,6 @@ func genCodeContent(ctx context.Context, params *plugin.ProvideContentParams) (* } text = fmt.Sprintf("```%s\n%s\n```", lang.AsString(), text) return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: text, - }, + Content: plugin.NewElementFromMarkdown(text), }, nil } diff --git a/internal/builtin/content_code_test.go b/internal/builtin/content_code_test.go index dcf3daa3..5c5bdbf6 100644 --- a/internal/builtin/content_code_test.go +++ b/internal/builtin/content_code_test.go @@ -56,11 +56,10 @@ func (s *CodeTestSuite) TestCallCodeDefault() { }, }) s.Empty(diags) - s.Equal(&plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: "```\nHello World!\n```", - }, - }, content) + s.Equal( + plugindata.String("```\nHello World!\n```"), + content.Content.AsData().(plugindata.Map)["markdown"], + ) } func (s *CodeTestSuite) TestCallCodeWithLanguage() { @@ -78,9 +77,8 @@ func (s *CodeTestSuite) TestCallCodeWithLanguage() { }, }) s.Empty(diags) - s.Equal(&plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: "```json\n{\"hello\": \"world\"}\n```", - }, - }, content) + s.Equal( + plugindata.String("```json\n{\"hello\": \"world\"}\n```"), + content.Content.AsData().(plugindata.Map)["markdown"], + ) } diff --git a/internal/builtin/content_frontmatter.go b/internal/builtin/content_frontmatter.go index f2b73f3d..a64db3cd 100644 --- a/internal/builtin/content_frontmatter.go +++ b/internal/builtin/content_frontmatter.go @@ -105,9 +105,7 @@ func genFrontMatterContent(ctx context.Context, params *plugin.ProvideContentPar }} } return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: result, - }, + Content: plugin.NewElementFromMarkdown(result), Location: &plugin.Location{ Index: 1, Effect: plugin.LocationEffectBefore, diff --git a/internal/builtin/content_image.go b/internal/builtin/content_image.go index 95e361a5..16261877 100644 --- a/internal/builtin/content_image.go +++ b/internal/builtin/content_image.go @@ -70,9 +70,7 @@ func genImageContent(ctx context.Context, params *plugin.ProvideContentParams) ( srcStr = strings.TrimSpace(strings.ReplaceAll(srcStr, "\n", "")) altStr = strings.TrimSpace(strings.ReplaceAll(altStr, "\n", "")) return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: fmt.Sprintf("![%s](%s)", altStr, srcStr), - }, + Content: plugin.NewElementFromMarkdown(fmt.Sprintf("![%s](%s)", altStr, srcStr)), }, nil } diff --git a/internal/builtin/content_list.go b/internal/builtin/content_list.go index 245a8e77..2164c05c 100644 --- a/internal/builtin/content_list.go +++ b/internal/builtin/content_list.go @@ -97,9 +97,7 @@ func genListContent(ctx context.Context, params *plugin.ProvideContentParams) (* }} } return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: result, - }, + Content: plugin.NewElementFromMarkdown(result), }, nil } diff --git a/internal/builtin/content_table.go b/internal/builtin/content_table.go index 37893ceb..6acbf8b4 100644 --- a/internal/builtin/content_table.go +++ b/internal/builtin/content_table.go @@ -112,9 +112,7 @@ func genTableContent(ctx context.Context, params *plugin.ProvideContentParams) ( }} } return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: result, - }, + Content: plugin.NewElementFromMarkdown(result), }, nil } diff --git a/internal/builtin/content_text.go b/internal/builtin/content_text.go index 6c677da5..ac9f0759 100644 --- a/internal/builtin/content_text.go +++ b/internal/builtin/content_text.go @@ -55,9 +55,7 @@ func genTextContent(ctx context.Context, params *plugin.ProvideContentParams) (* }} } return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: text, - }, + Content: plugin.NewElementFromMarkdown(text), }, nil } diff --git a/internal/builtin/content_title.go b/internal/builtin/content_title.go index 035551de..3560718d 100644 --- a/internal/builtin/content_title.go +++ b/internal/builtin/content_title.go @@ -105,9 +105,7 @@ func genTitleContent(ctx context.Context, params *plugin.ProvideContentParams) ( text = strings.ReplaceAll(text, "\n", " ") text = strings.Repeat("#", int(titleSize)+1) + " " + text return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: text, - }, + Content: plugin.NewElementFromMarkdown(text), }, nil } diff --git a/internal/builtin/content_toc.go b/internal/builtin/content_toc.go index 3d925c63..d2c687df 100644 --- a/internal/builtin/content_toc.go +++ b/internal/builtin/content_toc.go @@ -113,9 +113,7 @@ func genTOC(ctx context.Context, params *plugin.ProvideContentParams) (*plugin.C } return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: titles.render(0, args.ordered), - }, + Content: plugin.NewElementFromMarkdown(titles.render(0, args.ordered)), }, nil } @@ -177,7 +175,7 @@ func extractTitles(section *plugin.ContentSection) []string { if meta == nil || meta.Plugin != Name || meta.Provider != "title" { continue } - titles = append(titles, content.Markdown) + titles = append(titles, string(content.AsMarkdownSrc())) } } return titles diff --git a/internal/microsoft/content_azure_openai_text.go b/internal/microsoft/content_azure_openai_text.go index d06e3b8c..20c83630 100644 --- a/internal/microsoft/content_azure_openai_text.go +++ b/internal/microsoft/content_azure_openai_text.go @@ -101,9 +101,7 @@ func genOpenAIText(loader AzureOpenaiClientLoadFn) plugin.ProvideContentFunc { }} } return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: result, - }, + Content: plugin.NewElementFromMarkdown(result), }, nil } } diff --git a/internal/openai/content_openai_text.go b/internal/openai/content_openai_text.go index 14d09506..56d30bd8 100644 --- a/internal/openai/content_openai_text.go +++ b/internal/openai/content_openai_text.go @@ -79,9 +79,7 @@ func genOpenAIText(loader ClientLoadFn) plugin.ProvideContentFunc { }} } return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: result, - }, + Content: plugin.NewElementFromMarkdown(result), }, nil } } From e1ae96a9fa4aef930c7fbfb421078c851bf3c2e3 Mon Sep 17 00:00:00 2001 From: Andrew Morozko Date: Fri, 6 Sep 2024 12:54:16 +0400 Subject: [PATCH 08/12] Update stixview to use AST --- internal/stixview/content_stixview.go | 9 +++++---- internal/stixview/content_stixview_test.go | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/stixview/content_stixview.go b/internal/stixview/content_stixview.go index 8b99b881..006c60db 100644 --- a/internal/stixview/content_stixview.go +++ b/internal/stixview/content_stixview.go @@ -14,6 +14,7 @@ import ( "github.com/blackstork-io/fabric/pkg/diagnostics" "github.com/blackstork-io/fabric/plugin" + "github.com/blackstork-io/fabric/plugin/ast" "github.com/blackstork-io/fabric/plugin/dataspec" "github.com/blackstork-io/fabric/plugin/plugindata" ) @@ -130,7 +131,7 @@ func renderStixView(ctx context.Context, params *plugin.ProvideContentParams) (* if rctx.Objects == nil && args.StixURL == nil && args.GistID == nil { return nil, diagnostics.Diag{{ Severity: hcl.DiagError, - Summary: "Missing arugments", + Summary: "Missing arguments", Detail: "Must provide either stix_url or gist_id or objects", }} } @@ -145,9 +146,9 @@ func renderStixView(ctx context.Context, params *plugin.ProvideContentParams) (* } return &plugin.ContentResult{ - Content: &plugin.ContentElement{ - Markdown: buf.String(), - }, + Content: plugin.NewElement( + ast.HTMLBlock(buf.Bytes()), + ), }, nil } diff --git a/internal/stixview/content_stixview_test.go b/internal/stixview/content_stixview_test.go index 59f8e93e..52deaafd 100644 --- a/internal/stixview/content_stixview_test.go +++ b/internal/stixview/content_stixview_test.go @@ -49,6 +49,7 @@ func (s *StixViewTestSuite) TestGistID() { ``, `
`, `
`, + ``, }, "\n"), mdprint.PrintString(res.Content)) } @@ -68,6 +69,7 @@ func (s *StixViewTestSuite) TestStixURL() { ``, `
`, `
`, + ``, }, "\n"), mdprint.PrintString(res.Content)) } @@ -97,6 +99,7 @@ func (s *StixViewTestSuite) TestAllArgs() { ``, `
`, `
`, + ``, }, "\n"), mdprint.PrintString(res.Content)) } From 2625b016b6a2eb95ae3c1d8fc6490d9230fecfbd Mon Sep 17 00:00:00 2001 From: Andrew Morozko Date: Fri, 4 Oct 2024 02:04:10 +0400 Subject: [PATCH 09/12] Making linter happy --- plugin/ast/v1/ast_encoder.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/ast/v1/ast_encoder.go b/plugin/ast/v1/ast_encoder.go index 714f9c53..e14cf02e 100644 --- a/plugin/ast/v1/ast_encoder.go +++ b/plugin/ast/v1/ast_encoder.go @@ -129,7 +129,7 @@ func (e *encoder) encodeNode(node ast.Node) *Node { kind = &Node_Heading{ Heading: &Heading{ Base: e.encodeBaseBlock(&n.BaseBlock), - Level: uint32(n.Level), + Level: uint32(n.Level), //nolint:gosec // Level is bounded by 1-6. }, } case *ast.ThematicBreak: @@ -172,7 +172,7 @@ func (e *encoder) encodeNode(node ast.Node) *Node { Base: e.encodeBaseBlock(&n.BaseBlock), Marker: uint32(n.Marker), IsTight: n.IsTight, - Start: uint32(n.Start), + Start: uint32(n.Start), //nolint:gosec // Start numbers must be nine digits or less (https://spec.commonmark.org/0.31.2/#example-265) }, } case *ast.ListItem: From f4d170cff7beb3fed695dd8f59624cf75bbc21e3 Mon Sep 17 00:00:00 2001 From: Andrew Morozko Date: Sat, 5 Oct 2024 18:33:03 +0400 Subject: [PATCH 10/12] Improve ReplaceNodes --- print/printer.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/print/printer.go b/print/printer.go index 5cebf0c6..9c15a078 100644 --- a/print/printer.go +++ b/print/printer.go @@ -2,6 +2,7 @@ package print import ( "context" + "fmt" "io" "github.com/yuin/goldmark/ast" @@ -18,6 +19,7 @@ type Printer interface { // ReplaceNodes walks the AST starting from the given node and replaces nodes in it. // If replacer returns nil - the node is deleted func ReplaceNodes(n ast.Node, replacer func(n ast.Node) (repl ast.Node, skipChildren bool)) ast.Node { + const maxReplacementsWithoutAdvance = 100 if n == nil { return nil } @@ -26,6 +28,7 @@ func ReplaceNodes(n ast.Node, replacer func(n ast.Node) (repl ast.Node, skipChil return n } c := n.FirstChild() + replacementsWithoutAdvance := 0 for c != nil { repl := ReplaceNodes(c, replacer) switch repl { @@ -33,10 +36,19 @@ func ReplaceNodes(n ast.Node, replacer func(n ast.Node) (repl ast.Node, skipChil next := c.NextSibling() n.RemoveChild(n, c) c = next + replacementsWithoutAdvance = 0 case c: c = c.NextSibling() + replacementsWithoutAdvance = 0 default: + if replacementsWithoutAdvance >= maxReplacementsWithoutAdvance { + panic(fmt.Sprintf("Replacer stuck at node %s", repl.Kind())) + } + replacementsWithoutAdvance++ n.ReplaceChild(n, c, repl) + // Intentionally trying to replace the replacement result + // This allows replacer to not care whether the replacement node + // or its children need to be further replaced c = repl } } From 9a7018e8d7db03d6154cb5a04cfb4f2fe9ec5402 Mon Sep 17 00:00:00 2001 From: Andrew Morozko Date: Sat, 5 Oct 2024 19:25:21 +0400 Subject: [PATCH 11/12] Now replacers can produce errors --- print/mdprint/printer.go | 13 ++++++++----- print/pdfprint/printer.go | 12 ++++++++---- print/printer.go | 40 +++++++++++++++++++++++++++------------ 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/print/mdprint/printer.go b/print/mdprint/printer.go index c1cf2329..ea019e12 100644 --- a/print/mdprint/printer.go +++ b/print/mdprint/printer.go @@ -60,7 +60,7 @@ func (p Printer) printContent(w io.Writer, content plugin.Content) (err error) { } func (p Printer) printContentElement(w io.Writer, source *astsrc.ASTSource, node *nodes.FabricContentNode) error { - n := print.ReplaceNodes(node, func(n ast.Node) (repl ast.Node, skipChildren bool) { + n, err := print.ReplaceNodes(node, func(n ast.Node) (repl ast.Node, err error) { switch nT := n.(type) { case *nodes.CustomBlock: slog.Info("OtherBlock found in AST, replacing with message segment") @@ -68,18 +68,21 @@ func (p Printer) printContentElement(w io.Writer, source *astsrc.ASTSource, node p.AppendChild(p, ast.NewRawTextSegment( source.Appendf("", nT.Data.GetTypeUrl()), )) - return p, false + return p, nil case *nodes.CustomInline: slog.Info("OtherInline found in AST, replacing with message segment") p := ast.NewCodeSpan() p.AppendChild(p, ast.NewRawTextSegment( source.Appendf("", nT.Data.GetTypeUrl()), )) - return p, false + return p, nil } - return n, false + return n, nil }) - err := goldmark.New( + if err != nil { + return fmt.Errorf("replacement failed: %w", err) + } + err = goldmark.New( plugin.BaseMarkdownOptions, goldmark.WithExtensions( markdown.NewRenderer( diff --git a/print/pdfprint/printer.go b/print/pdfprint/printer.go index 813f3ed8..98ac6989 100644 --- a/print/pdfprint/printer.go +++ b/print/pdfprint/printer.go @@ -3,6 +3,7 @@ package pdfprint import ( "bytes" "context" + "fmt" "image/color" "io" "log/slog" @@ -36,7 +37,7 @@ func Print(w io.Writer, el plugin.Content) error { func (p Printer) Print(ctx context.Context, w io.Writer, el plugin.Content) (err error) { p.removeFrontmatter(el) - print.ReplaceNodesInContent(el, func(src *astsrc.ASTSource, n ast.Node) (repl ast.Node, skipChildren bool) { + err = print.ReplaceNodesInContent(el, func(src *astsrc.ASTSource, n ast.Node) (repl ast.Node, err error) { switch n := n.(type) { case *ast.HTMLBlock: slog.Info("HTML block found in AST, replacing with message segment") @@ -44,17 +45,20 @@ func (p Printer) Print(ctx context.Context, w io.Writer, el plugin.Content) (err p.AppendChild(p, ast.NewRawTextSegment( src.Appendf("", n.Kind()), )) - return p, false + return p, nil case *ast.RawHTML: slog.Info("Raw HTML found in AST, replacing with message segment") p := ast.NewCodeSpan() p.AppendChild(p, ast.NewRawTextSegment( src.Appendf("", n.Kind()), )) - return p, false + return p, nil } - return n, false + return n, nil }) + if err != nil { + return fmt.Errorf("replacement failed: %w", err) + } buf := &bytes.Buffer{} if err := p.md.Print(ctx, buf, el); err != nil { diff --git a/print/printer.go b/print/printer.go index 9c15a078..ef24b898 100644 --- a/print/printer.go +++ b/print/printer.go @@ -2,6 +2,7 @@ package print import ( "context" + "errors" "fmt" "io" @@ -16,21 +17,31 @@ type Printer interface { Print(ctx context.Context, w io.Writer, el plugin.Content) error } +// ErrReplacerSkipChildren could be returned from the ReplaceNodes replacer func +// to skip children. Will never be returned by ReplaceNodes itself. +var ErrReplacerSkipChildren = errors.New("Skip children") + // ReplaceNodes walks the AST starting from the given node and replaces nodes in it. // If replacer returns nil - the node is deleted -func ReplaceNodes(n ast.Node, replacer func(n ast.Node) (repl ast.Node, skipChildren bool)) ast.Node { +func ReplaceNodes(n ast.Node, replacer func(n ast.Node) (repl ast.Node, err error)) (ast.Node, error) { const maxReplacementsWithoutAdvance = 100 if n == nil { - return nil + return nil, nil } - n, skipChildren := replacer(n) - if n == nil || skipChildren { - return n + n, err := replacer(n) + if n == nil || err != nil { + if err == ErrReplacerSkipChildren { + err = nil + } + return n, err } c := n.FirstChild() replacementsWithoutAdvance := 0 for c != nil { - repl := ReplaceNodes(c, replacer) + repl, err := ReplaceNodes(c, replacer) + if err != nil { + return n, err + } switch repl { case nil: next := c.NextSibling() @@ -52,24 +63,29 @@ func ReplaceNodes(n ast.Node, replacer func(n ast.Node) (repl ast.Node, skipChil c = repl } } - return n + return n, nil } // ReplaceNodesInContent runs ReplaceNodes on plugin.ContentAST nodes -func ReplaceNodesInContent(el plugin.Content, replacer func(src *astsrc.ASTSource, n ast.Node) (repl ast.Node, skipChildren bool)) { +// Replacer is not expected to replace the top-level plugin node (ContentNode) +func ReplaceNodesInContent(el plugin.Content, replacer func(src *astsrc.ASTSource, n ast.Node) (repl ast.Node, err error)) error { switch el := el.(type) { case *plugin.ContentSection: for _, child := range el.Children { - ReplaceNodesInContent(child, replacer) + err := ReplaceNodesInContent(child, replacer) + if err != nil { + return err + } } case *plugin.ContentElement: if !el.IsAst() { - return + return nil } src, node := el.AsNode() - ReplaceNodes(node, func(n ast.Node) (repl ast.Node, skipChildren bool) { + _, err := ReplaceNodes(node, func(n ast.Node) (repl ast.Node, err error) { return replacer(src, n) }) - node.Dump(src.AsBytes(), 0) + return err } + return nil } From 65a6eaade29f6e4c6d3a65880ccf2d73f2c38502 Mon Sep 17 00:00:00 2001 From: Andrew Morozko Date: Sat, 5 Oct 2024 19:29:27 +0400 Subject: [PATCH 12/12] Replaced panic with an error --- print/printer.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/print/printer.go b/print/printer.go index ef24b898..aace1513 100644 --- a/print/printer.go +++ b/print/printer.go @@ -17,9 +17,13 @@ type Printer interface { Print(ctx context.Context, w io.Writer, el plugin.Content) error } -// ErrReplacerSkipChildren could be returned from the ReplaceNodes replacer func -// to skip children. Will never be returned by ReplaceNodes itself. -var ErrReplacerSkipChildren = errors.New("Skip children") +var ( + // ErrReplacerSkipChildren could be returned from the ReplaceNodes replacer func + // to skip children. Will never be returned by ReplaceNodes itself. + ErrReplacerSkipChildren = errors.New("skip children") + // ErrReplacerStuck means that replacer failed to make progress + ErrReplacerStuck = errors.New("replacer stuck") +) // ReplaceNodes walks the AST starting from the given node and replaces nodes in it. // If replacer returns nil - the node is deleted @@ -53,7 +57,7 @@ func ReplaceNodes(n ast.Node, replacer func(n ast.Node) (repl ast.Node, err erro replacementsWithoutAdvance = 0 default: if replacementsWithoutAdvance >= maxReplacementsWithoutAdvance { - panic(fmt.Sprintf("Replacer stuck at node %s", repl.Kind())) + return n, fmt.Errorf("%w: node %q", ErrReplacerStuck, repl.Kind()) } replacementsWithoutAdvance++ n.ReplaceChild(n, c, repl)