2022-09-10 19:09:36 +03:00
|
|
|
package markdown
|
|
|
|
|
|
|
|
import (
|
|
|
|
"embed"
|
|
|
|
"fmt"
|
2022-09-10 19:28:54 +03:00
|
|
|
"io"
|
2022-09-10 19:09:36 +03:00
|
|
|
|
|
|
|
"github.com/gomarkdown/markdown"
|
|
|
|
"github.com/gomarkdown/markdown/ast"
|
|
|
|
"github.com/wader/fq/format"
|
|
|
|
"github.com/wader/fq/pkg/bitio"
|
|
|
|
"github.com/wader/fq/pkg/decode"
|
|
|
|
"github.com/wader/fq/pkg/interp"
|
|
|
|
"github.com/wader/fq/pkg/scalar"
|
|
|
|
)
|
|
|
|
|
|
|
|
//go:embed markdown.jq
|
2022-10-09 20:05:30 +03:00
|
|
|
//go:embed markdown.md
|
2022-09-10 19:09:36 +03:00
|
|
|
var markdownFS embed.FS
|
|
|
|
|
|
|
|
func init() {
|
2023-03-29 01:36:55 +03:00
|
|
|
interp.RegisterFormat(
|
|
|
|
format.Markdown,
|
|
|
|
&decode.Format{
|
|
|
|
Description: "Markdown",
|
|
|
|
DecodeFn: decodeMarkdown,
|
|
|
|
Functions: []string{"_todisplay"},
|
|
|
|
})
|
2022-09-10 19:09:36 +03:00
|
|
|
interp.RegisterFS(markdownFS)
|
|
|
|
}
|
|
|
|
|
2023-02-18 23:10:48 +03:00
|
|
|
func decodeMarkdown(d *decode.D) any {
|
2022-09-10 19:28:54 +03:00
|
|
|
b, err := io.ReadAll(bitio.NewIOReader(d.RawLen(d.Len())))
|
2022-09-10 19:09:36 +03:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2022-09-30 14:58:23 +03:00
|
|
|
var s scalar.Any
|
2022-09-10 19:09:36 +03:00
|
|
|
s.Actual = node(markdown.Parse(b, nil))
|
|
|
|
d.Value.V = &s
|
|
|
|
d.Value.Range.Len = d.Len()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringSlice[T string | []byte](ss []T) []any {
|
|
|
|
var vs []any
|
|
|
|
for _, e := range ss {
|
|
|
|
vs = append(vs, string(e))
|
|
|
|
}
|
|
|
|
return vs
|
|
|
|
}
|
|
|
|
|
|
|
|
func sliceMap[F, T any](vs []F, fn func(F) T) []T {
|
|
|
|
ts := make([]T, len(vs))
|
|
|
|
for i, v := range vs {
|
|
|
|
ts[i] = fn(v)
|
|
|
|
}
|
|
|
|
return ts
|
|
|
|
}
|
|
|
|
|
|
|
|
func intSlice[T ~int](ss []T) []any {
|
|
|
|
var vs []any
|
|
|
|
for _, e := range ss {
|
|
|
|
vs = append(vs, e)
|
|
|
|
}
|
|
|
|
return vs
|
|
|
|
}
|
|
|
|
|
|
|
|
func attr(v map[string]any, attr *ast.Attribute) {
|
|
|
|
if attr == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
v["id"] = string(attr.ID)
|
|
|
|
|
|
|
|
var as []any
|
|
|
|
for _, a := range attr.Attrs {
|
|
|
|
as = append(as, string(a))
|
|
|
|
}
|
|
|
|
v["attrs"] = as
|
|
|
|
|
|
|
|
var cs []any
|
|
|
|
for _, a := range attr.Classes {
|
|
|
|
cs = append(cs, string(a))
|
|
|
|
}
|
|
|
|
v["classes"] = cs
|
|
|
|
}
|
|
|
|
|
|
|
|
func leaf(v map[string]any, typ string, l ast.Leaf) {
|
|
|
|
v["type"] = typ
|
|
|
|
v["literal"] = string(l.Literal)
|
|
|
|
|
|
|
|
attr(v, l.Attribute)
|
|
|
|
}
|
|
|
|
|
|
|
|
func container(v map[string]any, typ string, c ast.Container) {
|
|
|
|
v["type"] = typ
|
|
|
|
v["literal"] = string(c.Literal)
|
|
|
|
|
|
|
|
var cs []any
|
|
|
|
children := c.GetChildren()
|
|
|
|
for _, n := range children {
|
|
|
|
cv := node(n)
|
|
|
|
if cv != nil {
|
|
|
|
cs = append(cs, node(n))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
v["children"] = cs
|
|
|
|
|
|
|
|
attr(v, c.Attribute)
|
|
|
|
}
|
|
|
|
|
|
|
|
func listType(t ast.ListType) []any {
|
|
|
|
var vs []any
|
|
|
|
|
|
|
|
if t&ast.ListTypeOrdered == ast.ListTypeOrdered {
|
|
|
|
vs = append(vs, "ordered")
|
|
|
|
}
|
|
|
|
if t%ast.ListTypeOrdered == ast.ListTypeOrdered {
|
|
|
|
vs = append(vs, "ordered")
|
|
|
|
}
|
|
|
|
if t%ast.ListTypeDefinition == ast.ListTypeDefinition {
|
|
|
|
vs = append(vs, "definition")
|
|
|
|
}
|
|
|
|
if t%ast.ListTypeTerm == ast.ListTypeTerm {
|
|
|
|
vs = append(vs, "term")
|
|
|
|
}
|
|
|
|
if t%ast.ListItemContainsBlock == ast.ListItemContainsBlock {
|
|
|
|
vs = append(vs, "contains_block")
|
|
|
|
}
|
|
|
|
if t%ast.ListItemBeginningOfList == ast.ListItemBeginningOfList {
|
|
|
|
vs = append(vs, "beginning_of_list")
|
|
|
|
}
|
|
|
|
if t%ast.ListItemEndOfList == ast.ListItemEndOfList {
|
|
|
|
vs = append(vs, "end_of_list")
|
|
|
|
}
|
|
|
|
|
|
|
|
return vs
|
|
|
|
}
|
|
|
|
|
|
|
|
func node(n ast.Node) any {
|
|
|
|
v := map[string]any{}
|
|
|
|
|
|
|
|
switch n := n.(type) {
|
|
|
|
case *ast.Text:
|
|
|
|
if n.Leaf.Attribute == nil {
|
|
|
|
if len(n.Leaf.Literal) > 0 {
|
|
|
|
return string(n.Leaf.Literal)
|
|
|
|
}
|
|
|
|
// skip
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
case *ast.Softbreak:
|
|
|
|
leaf(v, "softbreak", n.Leaf)
|
|
|
|
case *ast.Hardbreak:
|
|
|
|
leaf(v, "hardbreak", n.Leaf)
|
|
|
|
case *ast.NonBlockingSpace:
|
|
|
|
leaf(v, "nbsp", n.Leaf)
|
|
|
|
case *ast.Emph:
|
|
|
|
container(v, "em", n.Container)
|
|
|
|
case *ast.Strong:
|
|
|
|
container(v, "strong", n.Container)
|
|
|
|
case *ast.Del:
|
|
|
|
container(v, "del", n.Container)
|
|
|
|
case *ast.BlockQuote:
|
|
|
|
container(v, "blockquote", n.Container)
|
|
|
|
case *ast.Aside:
|
|
|
|
container(v, "aside", n.Container)
|
|
|
|
case *ast.Link:
|
|
|
|
container(v, "link", n.Container)
|
|
|
|
v["destination"] = string(n.Destination)
|
|
|
|
v["title"] = string(n.Title)
|
|
|
|
v["note_id"] = n.NoteID
|
|
|
|
v["deferred_id"] = string(n.DeferredID)
|
|
|
|
v["additional_attributes"] = stringSlice(n.AdditionalAttributes)
|
|
|
|
case *ast.CrossReference:
|
|
|
|
container(v, "cross_reference", n.Container)
|
|
|
|
v["destination"] = string(n.Destination)
|
|
|
|
case *ast.Citation:
|
|
|
|
leaf(v, "citation", n.Leaf)
|
|
|
|
v["destination"] = stringSlice(n.Destination)
|
|
|
|
v["type"] = sliceMap(n.Type, func(v ast.CitationTypes) string {
|
|
|
|
switch v {
|
|
|
|
case ast.CitationTypeNone:
|
|
|
|
return "none"
|
|
|
|
case ast.CitationTypeSuppressed:
|
|
|
|
return "suppressed"
|
|
|
|
case ast.CitationTypeInformative:
|
|
|
|
return "informative"
|
|
|
|
case ast.CitationTypeNormative:
|
|
|
|
return "normative"
|
|
|
|
default:
|
|
|
|
return "unknown"
|
|
|
|
}
|
|
|
|
})
|
|
|
|
v["type"] = intSlice(n.Type)
|
|
|
|
v["suffix"] = stringSlice(n.Suffix)
|
|
|
|
case *ast.Image:
|
|
|
|
container(v, "image", n.Container)
|
|
|
|
v["destination"] = string(n.Destination)
|
|
|
|
v["title"] = string(n.Title)
|
|
|
|
case *ast.Code:
|
|
|
|
leaf(v, "code", n.Leaf)
|
|
|
|
case *ast.CodeBlock:
|
|
|
|
leaf(v, "code_block", n.Leaf)
|
|
|
|
v["is_fenced"] = n.IsFenced
|
|
|
|
v["info"] = string(n.Info)
|
|
|
|
if n.FenceChar != 0 {
|
|
|
|
v["fence_char"] = string(n.FenceChar)
|
|
|
|
}
|
|
|
|
v["fence_length"] = n.FenceLength
|
|
|
|
v["fence_offset"] = n.FenceOffset
|
|
|
|
case *ast.Caption:
|
|
|
|
container(v, "caption", n.Container)
|
|
|
|
case *ast.CaptionFigure:
|
|
|
|
container(v, "caption_figure", n.Container)
|
|
|
|
v["heading_id"] = n.HeadingID
|
|
|
|
case *ast.Document:
|
|
|
|
container(v, "document", n.Container)
|
|
|
|
case *ast.Paragraph:
|
|
|
|
container(v, "paragraph", n.Container)
|
|
|
|
case *ast.HTMLSpan:
|
|
|
|
leaf(v, "html_span", n.Leaf)
|
|
|
|
case *ast.HTMLBlock:
|
|
|
|
leaf(v, "html_block", n.Leaf)
|
|
|
|
case *ast.Heading:
|
|
|
|
container(v, "heading", n.Container)
|
|
|
|
v["level"] = n.Level
|
|
|
|
v["heading_id"] = n.HeadingID
|
|
|
|
v["is_titleblock"] = n.IsTitleblock
|
|
|
|
v["is_special"] = n.IsSpecial
|
|
|
|
case *ast.HorizontalRule:
|
|
|
|
leaf(v, "hr", n.Leaf)
|
|
|
|
case *ast.List:
|
|
|
|
container(v, "list", n.Container)
|
|
|
|
v["list_flags"] = listType(n.ListFlags)
|
|
|
|
v["tight"] = n.Tight
|
|
|
|
if n.BulletChar != 0 {
|
|
|
|
v["bullet_char"] = string(n.BulletChar)
|
|
|
|
}
|
|
|
|
if n.Delimiter != 0 {
|
|
|
|
v["delimiter"] = string(n.Delimiter)
|
|
|
|
}
|
|
|
|
v["start"] = n.Start
|
|
|
|
v["ref_link"] = string(n.RefLink)
|
|
|
|
v["is_footnotes_list"] = n.IsFootnotesList
|
|
|
|
case *ast.ListItem:
|
|
|
|
container(v, "list_item", n.Container)
|
|
|
|
v["list_flags"] = listType(n.ListFlags)
|
|
|
|
v["tight"] = n.Tight
|
|
|
|
if n.BulletChar != 0 {
|
|
|
|
v["bullet_char"] = string(n.BulletChar)
|
|
|
|
}
|
|
|
|
if n.Delimiter != 0 {
|
|
|
|
v["delimiter"] = string(n.Delimiter)
|
|
|
|
}
|
|
|
|
v["ref_link"] = string(n.RefLink)
|
|
|
|
v["is_footnotes_list"] = n.IsFootnotesList
|
|
|
|
case *ast.Table:
|
|
|
|
container(v, "table", n.Container)
|
|
|
|
case *ast.TableCell:
|
|
|
|
container(v, "table_cell", n.Container)
|
|
|
|
v["is_header"] = n.IsHeader
|
|
|
|
v["align"] = n.Align.String()
|
|
|
|
v["col_span"] = n.ColSpan
|
|
|
|
case *ast.TableHeader:
|
|
|
|
container(v, "table_header", n.Container)
|
|
|
|
case *ast.TableBody:
|
|
|
|
container(v, "table_body", n.Container)
|
|
|
|
case *ast.TableRow:
|
|
|
|
container(v, "table_row", n.Container)
|
|
|
|
case *ast.TableFooter:
|
|
|
|
container(v, "table_footer", n.Container)
|
|
|
|
case *ast.Math:
|
|
|
|
leaf(v, "math", n.Leaf)
|
|
|
|
case *ast.MathBlock:
|
|
|
|
container(v, "math_block", n.Container)
|
|
|
|
case *ast.DocumentMatter:
|
|
|
|
container(v, "document_matter", n.Container)
|
|
|
|
v["matter"] = func(v ast.DocumentMatters) string {
|
|
|
|
switch v {
|
|
|
|
case ast.DocumentMatterNone:
|
|
|
|
return "none"
|
|
|
|
case ast.DocumentMatterFront:
|
|
|
|
return "front"
|
|
|
|
case ast.DocumentMatterMain:
|
|
|
|
return "main"
|
|
|
|
case ast.DocumentMatterBack:
|
|
|
|
return "back"
|
|
|
|
default:
|
|
|
|
return "unknown"
|
|
|
|
}
|
|
|
|
}(n.Matter)
|
|
|
|
case *ast.Callout:
|
|
|
|
leaf(v, "callout", n.Leaf)
|
|
|
|
v["id"] = string(n.ID)
|
|
|
|
case *ast.Index:
|
|
|
|
leaf(v, "index", n.Leaf)
|
|
|
|
v["primary"] = n.Primary
|
|
|
|
v["item"] = string(n.Item)
|
|
|
|
v["subitem"] = string(n.Subitem)
|
|
|
|
v["id"] = n.ID
|
|
|
|
case *ast.Subscript:
|
|
|
|
leaf(v, "subscript", n.Leaf)
|
|
|
|
case *ast.Superscript:
|
|
|
|
leaf(v, "superscript", n.Leaf)
|
|
|
|
case *ast.Footnotes:
|
|
|
|
container(v, "footnotes", n.Container)
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unknown node %T", node))
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, e := range v {
|
|
|
|
if s, ok := e.(string); ok && s == "" {
|
|
|
|
delete(v, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return v
|
|
|
|
}
|