1
1
mirror of https://github.com/wader/fq.git synced 2024-12-24 13:52:02 +03:00
fq/format/markdown/markdown.go
2022-09-10 18:26:45 +02:00

320 lines
7.3 KiB
Go

package markdown
import (
"embed"
"fmt"
"io/ioutil"
"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
var markdownFS embed.FS
func init() {
interp.RegisterFormat(decode.Format{
Name: format.MARKDOWN,
Description: "Markdown",
DecodeFn: decodeMarkdown,
Functions: []string{"_todisplay"},
})
interp.RegisterFS(markdownFS)
}
func decodeMarkdown(d *decode.D, _ any) any {
b, err := ioutil.ReadAll(bitio.NewIOReader(d.RawLen(d.Len())))
if err != nil {
panic(err)
}
var s scalar.S
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
}