Initial commit
This commit is contained in:
commit
1b1cb0929b
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Pierre-Olivier Mercier
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
20
README.md
Normal file
20
README.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# goldmark-inline-attributes
|
||||||
|
|
||||||
|
[GoldMark](https://github.com/yuin/goldmark/) inline attributes extension.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
[Attention]{.underline} some text
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p><span class="underline">Attention</span> some text</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
var md = goldmark.New(attributes.Enable)
|
||||||
|
var source = []byte("[Text]{#id .class1}\nother text")
|
||||||
|
err := md.Convert(source, os.Stdout)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
```
|
28
ast.go
Normal file
28
ast.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package attributes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yuin/goldmark/ast"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Kind is the kind of hashtag AST nodes.
|
||||||
|
var Kind = ast.NewNodeKind("InlineAttributes")
|
||||||
|
|
||||||
|
// Node is a parsed attributes node.
|
||||||
|
type Node struct {
|
||||||
|
ast.BaseInline
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Node) Kind() ast.NodeKind { return Kind }
|
||||||
|
|
||||||
|
func (n *Node) Dump(src []byte, level int) {
|
||||||
|
attrs := n.Attributes()
|
||||||
|
list := make(map[string]string, len(attrs))
|
||||||
|
for _, attr := range attrs {
|
||||||
|
name := util.BytesToReadOnlyString(attr.Name)
|
||||||
|
value := util.BytesToReadOnlyString(util.EscapeHTML(attr.Value.([]byte)))
|
||||||
|
list[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
ast.DumpHelper(n, src, level, list, nil)
|
||||||
|
}
|
34
extend.go
Normal file
34
extend.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package attributes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/renderer"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Extender struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultParser = new(attributesParser)
|
||||||
|
defaultRenderer = new(attributesRenderer)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *Extender) Extend(m goldmark.Markdown) {
|
||||||
|
m.Parser().AddOptions(
|
||||||
|
parser.WithInlineParsers(
|
||||||
|
util.Prioritized(defaultParser, 100),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
m.Renderer().AddOptions(
|
||||||
|
renderer.WithNodeRenderers(
|
||||||
|
util.Prioritized(defaultRenderer, 100),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extension is a goldmark.Extender with markdown inline attributes support.
|
||||||
|
var Extension goldmark.Extender = new(Extender)
|
||||||
|
|
||||||
|
// Enable is a goldmark.Option with inline attributes support.
|
||||||
|
var Enable = goldmark.WithExtensions(Extension)
|
22
extend_test.go
Normal file
22
extend_test.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package attributes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAttributes(t *testing.T) {
|
||||||
|
source := []byte(`
|
||||||
|
[Text underlined]{.underline}
|
||||||
|
`)
|
||||||
|
|
||||||
|
var md = goldmark.New(Enable)
|
||||||
|
err := md.Convert(source, os.Stdout)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module github.com/nemunaire/goldmark-inline-attributes
|
||||||
|
|
||||||
|
go 1.21.0
|
||||||
|
|
||||||
|
require github.com/yuin/goldmark v1.5.6
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github.com/yuin/goldmark v1.5.6 h1:COmQAWTCcGetChm3Ig7G/t8AFAN00t+o8Mt4cf7JpwA=
|
||||||
|
github.com/yuin/goldmark v1.5.6/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
54
parser.go
Normal file
54
parser.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package attributes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/yuin/goldmark/ast"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
type attributesParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultAttributesParser = &attributesParser{}
|
||||||
|
|
||||||
|
// NewAttributesParser return a new InlineParser that parses inline attributes
|
||||||
|
// like '[txt]{.underline}' .
|
||||||
|
func NewAttributesParser() parser.InlineParser {
|
||||||
|
return defaultAttributesParser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *attributesParser) Trigger() []byte {
|
||||||
|
return []byte{'['}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *attributesParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||||
|
savedLine, savedPosition := block.Position()
|
||||||
|
|
||||||
|
line, seg := block.PeekLine()
|
||||||
|
|
||||||
|
endText := bytes.Index(line, []byte{']'})
|
||||||
|
if endText < 0 {
|
||||||
|
return nil // must close on the same line
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(line) <= endText || line[endText+1] != '{' {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
block.Advance(endText + 1)
|
||||||
|
|
||||||
|
attrs, ok := parser.ParseAttributes(block)
|
||||||
|
if !ok {
|
||||||
|
block.SetPosition(savedLine, savedPosition)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n := &Node{}
|
||||||
|
for _, attr := range attrs {
|
||||||
|
n.SetAttribute(attr.Name, attr.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
n.AppendChild(n, ast.NewTextSegment(text.NewSegment(seg.Start+1, seg.Start+endText)))
|
||||||
|
return n
|
||||||
|
}
|
44
render.go
Normal file
44
render.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package attributes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/yuin/goldmark/ast"
|
||||||
|
"github.com/yuin/goldmark/renderer"
|
||||||
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type attributesRenderer struct{}
|
||||||
|
|
||||||
|
func (r *attributesRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||||
|
reg.Register(Kind, r.Render)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *attributesRenderer) Render(w util.BufWriter, _ []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
|
n, ok := node.(*Node)
|
||||||
|
if !ok {
|
||||||
|
return ast.WalkStop, fmt.Errorf("unexpected node %T, expected *Node", node)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entering {
|
||||||
|
if err := r.enter(w, n); err != nil {
|
||||||
|
return ast.WalkStop, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.exit(w, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *attributesRenderer) enter(w util.BufWriter, n *Node) error {
|
||||||
|
w.WriteString(`<span`)
|
||||||
|
html.RenderAttributes(w, n, nil)
|
||||||
|
w.WriteString(`>`)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *attributesRenderer) exit(w util.BufWriter, n *Node) {
|
||||||
|
w.WriteString("</span>")
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user