commit 1b1cb0929bf4c7c39aaa9b88fc3764c7e08f9ea2 Author: Pierre-Olivier Mercier Date: Thu Aug 17 13:01:36 2023 +0200 Initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d17bda6 --- /dev/null +++ b/LICENSE @@ -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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7dd8b7d --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# goldmark-inline-attributes + +[GoldMark](https://github.com/yuin/goldmark/) inline attributes extension. + +```markdown +[Attention]{.underline} some text +``` + +```html +

Attention some text

+``` + +```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) +} +``` diff --git a/ast.go b/ast.go new file mode 100644 index 0000000..ba141e4 --- /dev/null +++ b/ast.go @@ -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) +} diff --git a/extend.go b/extend.go new file mode 100644 index 0000000..cffb7fa --- /dev/null +++ b/extend.go @@ -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) diff --git a/extend_test.go b/extend_test.go new file mode 100644 index 0000000..7a797b5 --- /dev/null +++ b/extend_test.go @@ -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) + } + +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..026db45 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/nemunaire/goldmark-inline-attributes + +go 1.21.0 + +require github.com/yuin/goldmark v1.5.6 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3a4e29a --- /dev/null +++ b/go.sum @@ -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= diff --git a/parser.go b/parser.go new file mode 100644 index 0000000..2cebb8c --- /dev/null +++ b/parser.go @@ -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 +} diff --git a/render.go b/render.go new file mode 100644 index 0000000..a2bad67 --- /dev/null +++ b/render.go @@ -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(``) + return nil +} + +func (r *attributesRenderer) exit(w util.BufWriter, n *Node) { + w.WriteString("") +}