diff --git a/ast/node.go b/ast/node.go index fff3a4c..c406908 100644 --- a/ast/node.go +++ b/ast/node.go @@ -366,6 +366,15 @@ type Callout struct { ID []byte // number of this callout } +// Index is a node that contains an Index item and an optional, subitem. +type Index struct { + Leaf + + Primary bool + Item []byte + Subitem []byte +} + func removeNodeFromArray(a []Node, node Node) []Node { n := len(a) for i := 0; i < n; i++ { diff --git a/html/renderer.go b/html/renderer.go index 97d4c92..06654c5 100644 --- a/html/renderer.go +++ b/html/renderer.go @@ -869,6 +869,10 @@ func (r *Renderer) callout(w io.Writer, node *ast.Callout) { r.outs(w, "") } +func (r *Renderer) index(w io.Writer, node *ast.Index) { + // there is no in-text representation. +} + // RenderNode renders a markdown node to HTML func (r *Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.WalkStatus { if r.opts.RenderNodeHook != nil { @@ -957,6 +961,8 @@ func (r *Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.Wal r.matter(w, node, entering) case *ast.Callout: r.callout(w, node) + case *ast.Index: + r.index(w, node) default: panic(fmt.Sprintf("Unknown node %T", node)) } diff --git a/parser/ref.go b/parser/ref.go index 68e2c91..501c1d1 100644 --- a/parser/ref.go +++ b/parser/ref.go @@ -1,8 +1,12 @@ package parser -import "github.com/gomarkdown/markdown/ast" +import ( + "bytes" -// parse '(#r)', where r does not contain spaces and is an existing label. Or. + "github.com/gomarkdown/markdown/ast" +) + +// parse '(#r)', where r does not contain spaces. Or. // (!item) (!item, subitem), for an index, (!!item) signals primary. func maybeShortRefOrIndex(p *Parser, data []byte, offset int) (int, ast.Node) { if len(data[offset:]) < 4 { @@ -11,34 +15,67 @@ func maybeShortRefOrIndex(p *Parser, data []byte, offset int) (int, ast.Node) { // short ref first data = data[offset:] i := 1 - if data[i] != '#' { - return 0, nil - } - i++ -Loop: - for i < len(data) { - c := data[i] - switch { - case c == ')': - break Loop - case !isAlnum(c): - if c == '_' || c == '-' || c == ':' { - i++ - continue - } - i = 0 - break Loop - } + switch data[i] { + case '#': // cross ref i++ - } - // end not found or no valid syntax - if i == 0 || data[i-1] != ')' { - return 0, nil + Loop: + for i < len(data) { + c := data[i] + switch { + case c == ')': + break Loop + case !isAlnum(c): + if c == '_' || c == '-' || c == ':' { + i++ + continue + } + i = 0 + break Loop + } + i++ + } + // end not found or no valid syntax + if i == 0 || data[i-1] != ')' { + return 0, nil + } + + id := data[2:i] + node := &ast.CrossReference{} + node.Destination = id + + return i, node + + case '!': // index + i++ + start := i + i = skipUntilChar(data, start, ')') + + // did we reach the end of the buffer without a closing marker? + if i >= len(data) { + return 0, nil + } + + if len(data[start:i]) < 1 { + return 0, nil + } + idx := &ast.Index{} + idx.Primary = data[start] == '!' + buf := data[start:i] + + if idx.Primary { + buf = buf[1:] + } + items := bytes.Split(buf, []byte(",")) + switch len(items) { + case 1: + idx.Item = bytes.TrimSpace(items[0]) + return i + 1, idx + case 2: + idx.Item = bytes.TrimSpace(items[0]) + idx.Subitem = bytes.TrimSpace(items[1]) + return i + 1, idx + } } - id := data[2:i] - node := &ast.CrossReference{} - node.Destination = id - - return i, node + return 0, nil } diff --git a/parser/ref_test.go b/parser/ref_test.go index 0177fb3..4738f1c 100644 --- a/parser/ref_test.go +++ b/parser/ref_test.go @@ -36,3 +36,58 @@ func TestCrossReference(t *testing.T) { } } } + +func TestIndex(t *testing.T) { + p := New() + + tests := []struct { + data []byte + i *ast.Index + fail bool + }{ + // ok + { + data: []byte("(!yes)"), + i: &ast.Index{Item: []byte("yes")}, + }, + { + data: []byte("(!y:es)"), + i: &ast.Index{Item: []byte("y:es")}, + }, + { + data: []byte("(!yes, no)"), + i: &ast.Index{Item: []byte("yes"), Subitem: []byte("no")}, + }, + { + data: []byte("(! yes , no )"), + i: &ast.Index{Item: []byte("yes"), Subitem: []byte("no")}, + }, + // fails + {data: []byte("(!yes"), fail: true}, + {data: []byte("(_yes"), fail: true}, + } + + for i, test := range tests { + _, n := maybeShortRefOrIndex(p, test.data, 0) + if test.fail && n != nil { + t.Errorf("test %d, should have failed to parse %s", i, test.data) + continue + } + if test.fail && n == nil { + // ok + continue + } + + idx := n.(*ast.Index) + + if string(test.i.Item) != string(idx.Item) { + t.Errorf("test %d, got item %s, wanted %s", i, idx.Item, test.i.Item) + } + if string(test.i.Subitem) != string(idx.Subitem) { + t.Errorf("test %d, got item %s, wanted %s", i, idx.Subitem, test.i.Subitem) + } + if test.i.Primary != idx.Primary { + t.Errorf("test %d, got item %t, wanted %t", i, idx.Primary, test.i.Primary) + } + } +} diff --git a/testdata/mmark.test b/testdata/mmark.test index ff6b6bc..59bbe7c 100644 --- a/testdata/mmark.test +++ b/testdata/mmark.test @@ -79,3 +79,14 @@ code ---
code
+---
+# Index
+(!myitem, subitem)
+(!!item)
+(!!item, subtitem )
+---
++ +