From 5c7bfc04dc3d2cd60e84c80229804fdcd615709e Mon Sep 17 00:00:00 2001 From: Shulhan Date: Mon, 21 Nov 2022 01:20:03 +0700 Subject: all: implement inline macro for passthrough ("pass:") The inline passthrough "pass:" can be used to control the substitutions applied to a run of text. Ref: https://docs.asciidoctor.org/asciidoc/latest/pass/pass-macro/ --- _doc/SPECS.adoc | 34 ++ const.go | 14 + element.go | 6 + html_backend.go | 521 ++++++++++++++++++++++++ inline_parser.go | 12 +- inline_parser_test.go | 48 +++ macro.go | 71 +++- parser.go | 62 +++ parser_test.go | 45 ++ testdata/inline_parser/macro_pass_a_test.txt | 13 + testdata/inline_parser/macro_pass_c_test.txt | 16 + testdata/inline_parser/macro_pass_m_test.txt | 32 ++ testdata/inline_parser/macro_pass_none_test.txt | 29 ++ testdata/inline_parser/macro_pass_q_test.txt | 15 + testdata/inline_parser/macro_pass_r_test.txt | 18 + 15 files changed, 932 insertions(+), 4 deletions(-) create mode 100644 const.go create mode 100644 testdata/inline_parser/macro_pass_a_test.txt create mode 100644 testdata/inline_parser/macro_pass_c_test.txt create mode 100644 testdata/inline_parser/macro_pass_m_test.txt create mode 100644 testdata/inline_parser/macro_pass_none_test.txt create mode 100644 testdata/inline_parser/macro_pass_q_test.txt create mode 100644 testdata/inline_parser/macro_pass_r_test.txt diff --git a/_doc/SPECS.adoc b/_doc/SPECS.adoc index 14e971f..6a5244f 100644 --- a/_doc/SPECS.adoc +++ b/_doc/SPECS.adoc @@ -476,6 +476,8 @@ exist, otherwise it would be considered as normal text. == Passthrough +{url_ref}/pass/[Reference^] + ---- PASSTHROUGH_SINGLE = FORMAT_BEGIN "+" TEXT "+" FORMAT_END @@ -484,8 +486,40 @@ PASSTHROUGH_DOUBLE = "++" TEXT "++" PASSTHROUGH_TRIPLE = "+++" TEXT "+++" PASSTHROUGH_BLOCK = "++++" LF 1*LINE "++++" LF + +PASSTHROUGH_MACRO = "pass:" *(PASSMACRO_SUB) "[" TEXT "]" + +PASSMACRO_SUB = PASSMACRO_CHAR *("," PASSMACRO_CHAR) + +PASSMACRO_CHAR = "c" / "q" / "a" / "r" / "m" / "p" + / PASSMACRO_GROUP_NORMAL + / PASSMACRO_GROUP_VERBATIM + +PASSMACRO_GROUP_NORMAL = "n" ; equal to "c,q,r,m,p" + +PASSMACRO_GROUP_VERBATIM = "v" ; equal to "c" ---- +The "c" allow +{url_ref}/subs/special-characters/[special character substitutions]. + +The "q" allow +{url_ref}/subs/quotes/[quotes substitutions]. + +The "a" allow +{url_ref}/subs/attributes/[attributes references substitutions]. + +The "r" allow +{url_ref}/subs/replacements/[character replacement substitutions]. + +The "m" allow +{url_ref}/subs/macros/[macro substitutions]. + +The "p" allow +{url_ref}/subs/post-replacements/[post-replacement substitutions]. + +The substitutions are applied in above order. + == URLs diff --git a/const.go b/const.go new file mode 100644 index 0000000..f60090c --- /dev/null +++ b/const.go @@ -0,0 +1,14 @@ +package asciidoctor + +// List of passthrough substitutions. +const ( + passSubNone int = 0 + passSubChar = 1 // 'c' + passSubQuote = 2 // 'q' + passSubAttr = 4 // 'a' + passSubRepl = 8 // 'r' + passSubMacro = 16 // 'm' + passSubPostRepl = 32 // 'p' + passSubNormal = passSubChar | passSubQuote | passSubAttr | passSubRepl | passSubMacro | passSubPostRepl + passSubVerbatim = passSubChar +) diff --git a/element.go b/element.go index 1abba9e..8f6348d 100644 --- a/element.go +++ b/element.go @@ -45,6 +45,9 @@ type element struct { rawLabel bytes.Buffer level int // The number of dot for ordered list, or '*' for unordered list. kind int + + // List of substitutions to be applied on raw. + applySubs int } func (el *element) getListOrderedClass() string { @@ -870,6 +873,9 @@ func (el *element) toHTML(doc *Document, w io.Writer) { case elKindInlineImage: htmlWriteInlineImage(el, w) + case elKindInlinePass: + htmlWriteInlinePass(doc, el, w) + case elKindListDescription: htmlWriteListDescription(el, w) case elKindListOrdered: diff --git a/html_backend.go b/html_backend.go index 4c2e467..1bbefcb 100644 --- a/html_backend.go +++ b/html_backend.go @@ -9,6 +9,7 @@ import ( "io" "strings" + libascii "github.com/shuLhan/share/lib/ascii" libstrings "github.com/shuLhan/share/lib/strings" ) @@ -60,6 +61,519 @@ const ( htmlSymbolZeroWidthSpace = `​` ) +// htmlSubs apply the text substitutions to element.raw based on applySubs in +// the following order: c, q, a, r, m, p. +// If applySubs is 0, it will return element.raw as is. +func htmlSubs(doc *Document, el *element) []byte { + var ( + input = el.raw + ) + if el.applySubs == 0 { + return input + } + if el.applySubs&passSubChar != 0 { + input = htmlSubsChar(input) + } + if el.applySubs&passSubQuote != 0 { + input = htmlSubsQuote(input) + } + if el.applySubs&passSubAttr != 0 { + input = htmlSubsAttr(doc, input) + } + if el.applySubs&passSubRepl != 0 { + input = htmlSubsRepl(input) + } + if el.applySubs&passSubMacro != 0 { + input = htmlSubsMacro(doc, input, el.kind == elKindInlinePass) + } + return input +} + +// htmlSubsChar replace character '<', '>', and '&' with "<", ">", and +// "&". +// +// Ref: https://docs.asciidoctor.org/asciidoc/latest/subs/special-characters/ +func htmlSubsChar(input []byte) []byte { + var ( + bb bytes.Buffer + c byte + ) + for _, c = range input { + if c == '<' { + bb.WriteString(`<`) + continue + } + if c == '>' { + bb.WriteString(`>`) + continue + } + if c == '&' { + bb.WriteString(`&`) + continue + } + bb.WriteByte(c) + } + return bb.Bytes() +} + +// htmlSubsQuote replace inline markup with its HTML markup. +// The following inline markup ara parsed and substitutes, +// +// - emphasis: _word_ with "word". +// - strong: *word* with "word". +// - monospace: `word` with "word". +// - superscript: ^word^ with "word". +// - subscript: ~word~ with "word". +// - double curved quotes: "`word`" with "“word”" +// - single curved quotes: '`word`' with "‘word’" +// +// Ref: https://docs.asciidoctor.org/asciidoc/latest/subs/quotes/ +func htmlSubsQuote(input []byte) []byte { + var ( + bb bytes.Buffer + x int + idx int + text []byte + c1 byte + nextc byte + ) + for x < len(input) { + c1 = input[x] + + x++ + if x == len(input) { + // Nothing left to parsed. + bb.WriteByte(c1) + break + } + nextc = input[x] + + if c1 == '_' { + text, idx = indexByteUnescape(input[x:], c1) + if text == nil { + bb.WriteByte(c1) + continue + } + bb.WriteString(``) + bb.Write(text) + bb.WriteString(``) + x = x + idx + 1 + continue + } + if c1 == '*' { + text, idx = indexByteUnescape(input[x:], c1) + if text == nil { + bb.WriteByte(c1) + continue + } + bb.WriteString(``) + bb.Write(text) + bb.WriteString(``) + x = x + idx + 1 + continue + } + if c1 == '`' { + text, idx = indexByteUnescape(input[x:], c1) + if text == nil { + bb.WriteByte(c1) + continue + } + bb.WriteString(``) + bb.Write(text) + bb.WriteString(``) + x = x + idx + 1 + continue + } + if c1 == '^' { + text, idx = indexByteUnescape(input[x:], c1) + if text == nil { + bb.WriteByte(c1) + continue + } + bb.WriteString(``) + bb.Write(text) + bb.WriteString(``) + x = x + idx + 1 + continue + } + if c1 == '~' { + text, idx = indexByteUnescape(input[x:], c1) + if text == nil { + bb.WriteByte(c1) + continue + } + bb.WriteString(``) + bb.Write(text) + bb.WriteString(``) + x = x + idx + 1 + continue + } + if c1 == '"' { + if nextc != '`' { + bb.WriteByte(c1) + continue + } + if x+1 == len(input) { + bb.WriteByte(c1) + continue + } + + text, idx = indexUnescape(input[x+1:], []byte("`\"")) + if text == nil { + bb.WriteByte(c1) + continue + } + bb.WriteString(htmlSymbolLeftDoubleQuote) + bb.Write(text) + bb.WriteString(htmlSymbolRightDoubleQuote) + x = x + idx + 3 + continue + } + if c1 == '\'' { + if nextc != '`' { + bb.WriteByte(c1) + continue + } + if x+1 == len(input) { + bb.WriteByte(c1) + continue + } + + text, idx = indexUnescape(input[x+1:], []byte("`'")) + if text == nil { + bb.WriteByte(c1) + continue + } + bb.WriteString(htmlSymbolLeftSingleQuote) + bb.Write(text) + bb.WriteString(htmlSymbolRightSingleQuote) + x = x + idx + 3 + continue + } + bb.WriteByte(c1) + } + return bb.Bytes() +} + +// htmlSubsAttr replace attribute (the `{...}`) with its values. +// +// Ref: https://docs.asciidoctor.org/asciidoc/latest/subs/attributes/ +func htmlSubsAttr(doc *Document, input []byte) []byte { + var ( + bb bytes.Buffer + key string + val string + vbytes []byte + idx int + x int + c byte + ok bool + ) + + for x < len(input) { + c = input[x] + x++ + if c != '{' { + bb.WriteByte(c) + continue + } + + vbytes, idx = indexByteUnescape(input[x:], '}') + if vbytes == nil { + bb.WriteByte(c) + continue + } + vbytes = bytes.TrimSpace(vbytes) + vbytes = bytes.ToLower(vbytes) + + key = string(vbytes) + val, ok = _attrRef[key] + if ok { + bb.WriteString(val) + x = x + idx + 1 + continue + } + + val, ok = doc.Attributes[key] + if !ok { + bb.WriteByte(c) + continue + } + + // Add prefix "mailto:" if the ref name start with email, so + // it can be parsed by caller as macro link. + if key == `email` || strings.HasPrefix(key, `email_`) { + val = `mailto:` + val + `[` + val + `]` + } + + bb.WriteString(val) + x = x + idx + 1 + } + + return bb.Bytes() +} + +// htmlSubsRepl substitutes special characters with HTML unicode. +// +// The special characters are, +// +// - (C) replaced with © +// - (R) : ® +// - (TM) : ™ +// - -- : — Only replaced if between two word characters, between a +// word character and a line boundary, or flanked by spaces. +// When flanked by space characters (e.g., a -- b), the normal spaces are +// replaced by thin spaces ( ). +// - ... : … +// - -> : → +// - => : ⇒ +// - <- : ← +// - <= : ⇐ +// - ' : ’ +// +// According to [the documentation], this substitution step also recognizes +// HTML and XML character references as well as decimal and hexadecimal +// Unicode code points, but we only cover the above right now. +// +// [the documentation]: https://docs.asciidoctor.org/asciidoc/latest/subs/replacements/ +func htmlSubsRepl(input []byte) (out []byte) { + var ( + text []byte + x int + idx int + c1 byte + nextc byte + prevc byte + ) + + out = make([]byte, 0, len(input)) + + for x < len(input) { + prevc = c1 + c1 = input[x] + + x++ + if x == len(input) { + out = append(out, c1) + break + } + nextc = input[x] + + if c1 == '(' { + text, idx = indexByteUnescape(input[x:], ')') + if len(text) == 1 { + if text[0] == 'C' { + out = append(out, []byte(htmlSymbolCopyright)...) + x = x + idx + 1 + c1 = ')' + continue + } + if text[0] == 'R' { + out = append(out, []byte(htmlSymbolRegistered)...) + x = x + idx + 1 + c1 = ')' + continue + } + } else if len(text) == 2 { + if text[0] == 'T' && text[1] == 'M' { + out = append(out, []byte(htmlSymbolTrademark)...) + x = x + idx + 1 + c1 = ')' + continue + } + } + + out = append(out, c1) + continue + } + if c1 == '-' { + if nextc == '>' { + out = append(out, []byte(htmlSymbolSingleRightArrow)...) + x++ + c1 = nextc + continue + } + if nextc == '-' { + if x+1 >= len(input) { + out = append(out, c1) + continue + } + // set c1 to the third character after '--'. + c1 = input[x+1] + if libascii.IsSpace(prevc) && libascii.IsSpace(c1) { + out = out[:len(out)-1] + out = append(out, []byte(htmlSymbolThinSpace)...) + out = append(out, []byte(htmlSymbolEmdash)...) + out = append(out, []byte(htmlSymbolThinSpace)...) + x += 2 + continue + } + if libascii.IsAlpha(prevc) && libascii.IsAlpha(c1) { + out = append(out, []byte(htmlSymbolEmdash)...) + x++ + continue + } + } + out = append(out, c1) + continue + } + if c1 == '=' { + if nextc == '>' { + out = append(out, []byte(htmlSymbolDoubleRightArrow)...) + x++ + c1 = nextc + continue + } + out = append(out, c1) + continue + } + if c1 == '<' { + if nextc == '-' { + out = append(out, []byte(htmlSymbolSingleLeftArrow)...) + x++ + continue + } + if nextc == '=' { + out = append(out, []byte(htmlSymbolDoubleLeftArrow)...) + x++ + continue + } + out = append(out, c1) + continue + } + if c1 == '.' { + if nextc != '.' { + out = append(out, c1) + continue + } + if x+1 >= len(input) { + out = append(out, c1) + continue + } + // Set c1 to the third character. + c1 = input[x+1] + if c1 == '.' { + out = append(out, []byte(htmlSymbolEllipsis)...) + x += 2 + continue + } + out = append(out, c1) + continue + } + if c1 == '\'' { + if libascii.IsAlpha(prevc) { + out = append(out, []byte(htmlSymbolApostrophe)...) + continue + } + out = append(out, c1) + continue + } + out = append(out, c1) + } + return out +} + +// htmlSubsMacro substitutes macro with its HTML markup. +func htmlSubsMacro(doc *Document, input []byte, isInlinePass bool) (out []byte) { + var ( + el *element + bb bytes.Buffer + macroName string + x int + n int + c byte + ) + + for x < len(input) { + c = input[x] + if c != ':' { + out = append(out, c) + x++ + continue + } + + macroName = parseMacroName(input[:x]) + if len(macroName) == 0 { + out = append(out, c) + x++ + continue + } + + switch macroName { + case macroFootnote: + el, n = parseMacroFootnote(doc, input[x+1:]) + if el == nil { + out = append(out, c) + x++ + continue + } + x += n + n = len(out) + out = out[:n-len(macroName)] // Undo the macro name + bb.Reset() + htmlWriteFootnote(el, &bb) + out = append(out, bb.Bytes()...) + + case macroFTP, macroHTTPS, macroHTTP, macroIRC, macroLink, macroMailto: + el, n = parseURL(doc, macroName, input[x+1:]) + if el == nil { + out = append(out, c) + x++ + continue + } + x += n + n = len(out) + out = out[:n-len(macroName)] + bb.Reset() + htmlWriteURLBegin(el, &bb) + if el.child != nil { + el.child.toHTML(doc, &bb) + } + htmlWriteURLEnd(&bb) + out = append(out, bb.Bytes()...) + + case macroImage: + el, n = parseInlineImage(doc, input[x+1:]) + if el == nil { + out = append(out, c) + x++ + continue + } + x += n + n = len(out) + out = out[:n-len(macroName)] + bb.Reset() + htmlWriteInlineImage(el, &bb) + out = append(out, bb.Bytes()...) + + case macroPass: + if isInlinePass { + // Prevent recursive substitutions. + out = append(out, c) + x++ + continue + } + el, n = parseMacroPass(input[x+1:]) + if el == nil { + out = append(out, c) + x++ + continue + } + x += n + n = len(out) + out = out[:n-len(macroName)] + bb.Reset() + htmlWriteInlinePass(doc, el, &bb) + out = append(out, bb.Bytes()...) + + default: + out = append(out, c) + x++ + } + } + return out +} + func htmlWriteBlockBegin(el *element, out io.Writer, addClass string) { fmt.Fprint(out, "\n`) } +func htmlWriteInlinePass(doc *Document, el *element, out io.Writer) { + var ( + text []byte = htmlSubs(doc, el) + ) + fmt.Fprint(out, string(text)) +} + func htmlWriteListDescription(el *element, out io.Writer) { var openTag string if el.isStyleQandA() { diff --git a/inline_parser.go b/inline_parser.go index f7a9e8e..621c21e 100644 --- a/inline_parser.go +++ b/inline_parser.go @@ -743,6 +743,14 @@ func (pi *inlineParser) parseMacro() bool { } pi.x += n pi.prev = 0 + + case macroPass: + el, n = parseMacroPass(pi.content[pi.x+1:]) + if el == nil { + return false + } + pi.x += n + pi.prev = 0 } pi.current.raw = pi.current.raw[:len(pi.current.raw)-len(name)] @@ -934,8 +942,6 @@ func (pi *inlineParser) parseSuperscript() bool { // parseURL parser the URL, an optional text, optional attribute for target, // and optional role. -// -// The current state of p.x is equal to ":". func parseURL(doc *Document, scheme string, content []byte) (el *element, n int) { var ( x int @@ -1066,7 +1072,7 @@ func (pi *inlineParser) terminate(kind int, style int64) { // indexByteUnescape find the index of the first unescaped byte `c` on // slice of byte `in`. -// It will return nil and -1 if no unescape byte `c` found. +// It will return nil and -1 if no unescaped byte `c` found. func indexByteUnescape(in []byte, c byte) (out []byte, idx int) { var ( x int diff --git a/inline_parser_test.go b/inline_parser_test.go index 0fd62bf..3670d7f 100644 --- a/inline_parser_test.go +++ b/inline_parser_test.go @@ -6,6 +6,7 @@ package asciidoctor import ( "bytes" "fmt" + "strings" "testing" "github.com/shuLhan/share/lib/test" @@ -132,3 +133,50 @@ func TestInlineParser_macro_footnote(t *testing.T) { got.Reset() } } + +func TestInlineParser_macro_pass(t *testing.T) { + var ( + testFiles = []string{ + `testdata/inline_parser/macro_pass_none_test.txt`, + `testdata/inline_parser/macro_pass_c_test.txt`, + `testdata/inline_parser/macro_pass_q_test.txt`, + `testdata/inline_parser/macro_pass_a_test.txt`, + `testdata/inline_parser/macro_pass_r_test.txt`, + `testdata/inline_parser/macro_pass_m_test.txt`, + } + + testFile string + inputName string + outputName string + got bytes.Buffer + tdata *test.Data + doc *Document + exp []byte + err error + ) + + for _, testFile = range testFiles { + tdata, err = test.LoadData(testFile) + if err != nil { + t.Fatalf(`%s: %s`, testFile, err) + } + + for inputName = range tdata.Input { + t.Logf(`%s: %s`, testFile, inputName) + + outputName = strings.Replace(inputName, `.adoc`, `.html`, 1) + + doc = Parse(tdata.Input[inputName]) + + got.Reset() + err = doc.ToHTMLEmbedded(&got) + if err != nil { + t.Fatalf(`%s: %s`, inputName, err) + } + + exp = tdata.Output[outputName] + + test.Assert(t, inputName, string(exp), got.String()) + } + } +} diff --git a/macro.go b/macro.go index 6aa5523..a57f4b3 100644 --- a/macro.go +++ b/macro.go @@ -9,6 +9,7 @@ import ( "github.com/shuLhan/share/lib/ascii" ) +// List of macro names. const ( macroFTP = `ftp` macroFootnote = `footnote` @@ -18,6 +19,7 @@ const ( macroImage = `image` macroLink = `link` macroMailto = `mailto` + macroPass = `pass` ) var ( @@ -30,6 +32,7 @@ var ( macroImage: elKindInlineImage, macroLink: elKindURL, macroMailto: elKindURL, + macroPass: elKindText, } ) @@ -44,7 +47,7 @@ type macro struct { // val represent the text for URL or image and footnote. rawContent []byte - // level represent footnoted index number. + // level represent footnote index number. level int } @@ -158,3 +161,69 @@ func parseMacroFootnote(doc *Document, text []byte) (el *element, n int) { return el, n } + +// parseMacroPass parse the macro for passthrough. +// +// "pass:" *(SUB) "[" TEXT "]" +// +// SUB = SUB_KIND *("," SUB_KIND) +// +// SUB_KIND = "c" / "q" / "a" / "r" / "m" / "p" / "n" / "v" +func parseMacroPass(text []byte) (el *element, n int) { + var ( + x int + c byte + ) + + el = &element{ + kind: elKindInlinePass, + } + + // Consume the substitutions until "[" or spaces. + // Spaces automatically stop the process. + // Other characters except the sub kinds are ignored. + for ; x < len(text); x++ { + c = text[x] + if c == '[' { + break + } + if c == ',' { + continue + } + if ascii.IsSpace(c) { + return nil, 0 + } + switch c { + case 'c': + el.applySubs |= passSubChar + case 'q': + el.applySubs |= passSubQuote + case 'a': + el.applySubs |= passSubAttr + case 'r': + el.applySubs |= passSubRepl + case 'm': + el.applySubs |= passSubMacro + case 'p': + el.applySubs |= passSubPostRepl + case 'n': + el.applySubs |= passSubNormal + case 'v': + el.applySubs |= passSubChar + } + } + if c != '[' { + return nil, 0 + } + x++ + n = x + + el.raw, x = parseClosedBracket(text[x:], '[', ']') + if x < 0 { + return nil, 0 + } + + n += x + 2 + + return el, n +} diff --git a/parser.go b/parser.go index 033604c..bc7da9e 100644 --- a/parser.go +++ b/parser.go @@ -40,6 +40,7 @@ const ( elKindInlineID // "[[" REF_ID "]]" TEXT elKindInlineIDShort // "[#" REF_ID "]#" TEXT "#" elKindInlineImage // Inline macro for "image:" + elKindInlinePass // Inline macro for passthrough "pass:" elKindInlineParagraph // elKindListOrdered // Wrapper. elKindListOrderedItem // 30: Line start with ". " @@ -637,6 +638,67 @@ func parseAttrRef(doc *Document, content []byte, x int) (newContent []byte, ok b return newContent, true } +// parseClosedBracket parse the text in input until we found the last close +// bracket. +// It will skip any open-close brackets inside input. +// For example, parsing ("test:[]]", '[', ']') will return ("test:[]", 7). +// +// If no closed bracket found it will return (nil, -1). +func parseClosedBracket(input []byte, openb, closedb byte) (out []byte, idx int) { + var ( + openCount int + c byte + isEsc bool + ) + + out = make([]byte, 0, len(input)) + + for idx, c = range input { + if c == '\\' { + if isEsc { + out = append(out, '\\') + isEsc = false + } else { + isEsc = true + } + continue + } + + if c == closedb { + if isEsc { + out = append(out, c) + isEsc = false + continue + } + if openCount == 0 { + return out, idx + } + openCount-- + out = append(out, c) + continue + } + + if c == openb { + out = append(out, c) + if isEsc { + isEsc = false + } else { + openCount++ + } + continue + } + + if isEsc { + out = append(out, '\\') + isEsc = false + } + out = append(out, c) + } + + // No closed bracket found. + return nil, -1 +} + // parseIDLabel parse the string "ID (,LABEL)" into ID and label. // It will return empty id and label if ID is not valid. func parseIDLabel(s []byte) (id, label []byte) { diff --git a/parser_test.go b/parser_test.go index ace7581..5769a42 100644 --- a/parser_test.go +++ b/parser_test.go @@ -87,6 +87,51 @@ func TestGenerateID(t *testing.T) { } } +func TestParseClosedBracket(t *testing.T) { + type testCase struct { + input string + expOut string + expIdx int + } + + var cases = []testCase{{ + input: `test:[]] input`, + expOut: `test:[]`, + expIdx: 7, + }, { + input: `[test:[]]] input`, + expOut: `[test:[]]`, + expIdx: 9, + }, { + input: `[test:[]] input`, + expOut: ``, + expIdx: -1, + }, { + input: `test:\[\]] input`, + expOut: `test:[]`, + expIdx: 9, + }, { + input: `test:\x\]] input`, + expOut: `test:\x]`, + expIdx: 9, + }} + + var ( + c testCase + got []byte + gotIdx int + ) + + for _, c = range cases { + t.Logf(`input: %s`, c.input) + + got, gotIdx = parseClosedBracket([]byte(c.input), '[', ']') + + test.Assert(t, `got`, c.expOut, string(got)) + test.Assert(t, `got index`, c.expIdx, gotIdx) + } +} + func TestIsValidID(t *testing.T) { type testCase struct { id string diff --git a/testdata/inline_parser/macro_pass_a_test.txt b/testdata/inline_parser/macro_pass_a_test.txt new file mode 100644 index 0000000..3c4dd8c --- /dev/null +++ b/testdata/inline_parser/macro_pass_a_test.txt @@ -0,0 +1,13 @@ +Test macro pass with attribute substitutions only. + +>>> pass_a.adoc +:meta-a: meta A +:meta-b: meta B + +pass:a[attributes: {meta-A}, {meta-b}, and {meta-not_exist}]. + +<<< pass_a.html + +
+

attributes: meta A, meta B, and {meta-not_exist}.

+
diff --git a/testdata/inline_parser/macro_pass_c_test.txt b/testdata/inline_parser/macro_pass_c_test.txt new file mode 100644 index 0000000..c5682fe --- /dev/null +++ b/testdata/inline_parser/macro_pass_c_test.txt @@ -0,0 +1,16 @@ +Test macro pass with special character substitutions only. + +>>> pass_c.adoc + +pass:c[char: < > &]. + +pass:c[replacement: (C) (R) (TM) -- ... -> => <- <= user's input]. + +<<< pass_c.html + +
+

char: < > &.

+
+
+

replacement: (C) (R) (TM) -- ... -> => <- <= user's input.

+
diff --git a/testdata/inline_parser/macro_pass_m_test.txt b/testdata/inline_parser/macro_pass_m_test.txt new file mode 100644 index 0000000..248680e --- /dev/null +++ b/testdata/inline_parser/macro_pass_m_test.txt @@ -0,0 +1,32 @@ +Test macro pass with macro only. + +>>> pass_m.adoc + +pass:m[Text with footnote:id[footnote]]. + +pass:m[Text with http://127.0.0.1[HTTP URL]]. + +pass:m[Text with image:test.jpg[image]]. + +pass:m[Text with pass:[_none_] and pass:c[<_char_>]]. + +<<< pass_m.html + +
+

Text with [1].

+
+
+

Text with HTTP URL.

+
+
+

Text with image.

+
+
+

Text with pass:[_none_] and pass:c[<_char_>].

+
+
+
+
+1. footnote +
+
diff --git a/testdata/inline_parser/macro_pass_none_test.txt b/testdata/inline_parser/macro_pass_none_test.txt new file mode 100644 index 0000000..e4c1013 --- /dev/null +++ b/testdata/inline_parser/macro_pass_none_test.txt @@ -0,0 +1,29 @@ + +>>> pass_none.adoc + +pass:[char: < > &]. + +pass:[quote: _emphasis_, *strong*], +pass:[`monospace`, ^superscript^, ~subscript~], +pass:["`double curved quotes`", and '`single curved quotes`']. + +pass:[attributes: {meta-A}, {meta-b}, and {meta-not_exist}]. + +pass:[replacement: (C) (R) (TM) -- ... -> => <- <= user's input]. + +<<< pass_none.html + +
+

char: < > &.

+
+
+

quote: _emphasis_, *strong*, +`monospace`, ^superscript^, ~subscript~, +"`double curved quotes`", and '`single curved quotes`'.

+
+
+

attributes: {meta-A}, {meta-b}, and {meta-not_exist}.

+
+
+

replacement: (C) (R) (TM) -- ... -> => <- <= user's input.

+
diff --git a/testdata/inline_parser/macro_pass_q_test.txt b/testdata/inline_parser/macro_pass_q_test.txt new file mode 100644 index 0000000..067ce43 --- /dev/null +++ b/testdata/inline_parser/macro_pass_q_test.txt @@ -0,0 +1,15 @@ +Test macro pass with inline markup substitutions only. + +>>> pass_q.adoc + +pass:q[quote: _emphasis_, *strong*], +pass:q[`monospace`, ^superscript^, ~subscript~], +pass:q["`double curved quotes`", and '`single curved quotes`']. + +<<< pass_q.html + +
+

quote: emphasis, strong, +monospace, superscript, subscript, +“double curved quotes”, and ‘single curved quotes’.

+
diff --git a/testdata/inline_parser/macro_pass_r_test.txt b/testdata/inline_parser/macro_pass_r_test.txt new file mode 100644 index 0000000..e777b73 --- /dev/null +++ b/testdata/inline_parser/macro_pass_r_test.txt @@ -0,0 +1,18 @@ +Test macro pass with special replacements only. + +>>> pass_r.adoc + +pass:r[char: < > &]. + +pass:r[replacement: (C) (R) (TM) -- ...] +pass:r[-> => <- <= user's input]. + +<<< pass_r.html + +
+

char: < > &.

+
+
+

replacement: © ® ™ — … +→ ⇒ ← ⇐ user’s input.

+
-- cgit v1.3