From 868e95a5c493408bf1118a5f69c39aebf694748b Mon Sep 17 00:00:00 2001 From: Xavier Coulon Date: Tue, 8 Aug 2017 22:40:30 +0200 Subject: [PATCH] feat(parser/renderer): parse and render unordered list items (#12) including multiple levels also, refactor/organize tests Signed-off-by: Xavier Coulon --- parser/asciidoc-grammar.peg | 43 +- parser/asciidoc_parser_test.go | 424 +------------- parser/block_image_test.go | 10 +- parser/delimited_block_test.go | 8 +- parser/external_link_test.go | 74 +++ parser/heading_test.go | 147 +++++ parser/list_test.go | 521 ++++++++++++++++++ parser/meta_elements_test.go | 120 ++++ parser/paragraph_test.go | 45 ++ ...quotedtext_test.go => quoted_text_test.go} | 84 ++- renderer/html5/delimited_block_test.go | 4 +- renderer/html5/heading_test.go | 2 +- renderer/html5/image_test.go | 2 +- renderer/html5/list_item.go | 105 ++++ renderer/html5/list_item_test.go | 92 ++++ renderer/html5/paragraph_test.go | 15 + renderer/html5/quoted_text_test.go | 2 +- renderer/html5/renderer.go | 2 + renderer/html5/renderer_test.go | 8 +- test/log_init.go | 32 +- types/type_utils_test.go | 3 +- types/types.go | 252 +++++++-- 22 files changed, 1440 insertions(+), 555 deletions(-) create mode 100644 parser/external_link_test.go create mode 100644 parser/heading_test.go create mode 100644 parser/list_test.go create mode 100644 parser/meta_elements_test.go create mode 100644 parser/paragraph_test.go rename parser/{quotedtext_test.go => quoted_text_test.go} (90%) create mode 100644 renderer/html5/list_item.go create mode 100644 renderer/html5/list_item_test.go create mode 100644 renderer/html5/paragraph_test.go diff --git a/parser/asciidoc-grammar.peg b/parser/asciidoc-grammar.peg index 486ba13a..53098815 100644 --- a/parser/asciidoc-grammar.peg +++ b/parser/asciidoc-grammar.peg @@ -13,7 +13,7 @@ Document <- lines:Line* EOF { return types.NewDocument(lines.([]interface{})) } -Line <- Heading / ListItem / BlockImage / DelimitedBlock / MetadataElement / Paragraph / BlankLine +Line <- Heading / List / BlockImage / DelimitedBlock / MetadataElement / Paragraph / BlankLine // ------------------------------------------ // Headings @@ -23,16 +23,21 @@ Heading <- metadata:(MetadataElement)* level:("="+) WS+ content:InlineContent { } // ------------------------------------------ -// List Type +// List Items // ------------------------------------------ -//TODO: Blank lines are required before and after a list -//TODO: Additionally, blank lines are permitted, but not required, between list items. -ListItem <- WS* ('*' / '-') WS+ content:(InlineContent) { - return types.NewListItem(content.(*types.InlineContent)) +List <- metadata:(MetadataElement)* elements:(ListItem BlankLine?)+ (BlankLine / EOF) { + return types.NewList(elements.([]interface{}), metadata.([]interface{})) } +ListItem <- WS* level:('*'+ / '-') WS+ content:(ListItemContent) { + return types.NewListItem(level, content.(*types.ListItemContent), nil) +} + +ListItemContent <- lines:(!(WS* ('*'+ / '-') WS+) InlineContent)+ { + return types.NewListItemContent(c.text, lines.([]interface{})) +} // ------------------------------------------ -// Paragraph Type +// Paragraphs // ------------------------------------------ // a paragraph is a group of line ending with a blank line (or end of file) Paragraph <- lines:(InlineContent)+ (BlankLine/EOF) { @@ -44,7 +49,7 @@ InlineContent <- !NEWLINE elements:(QuotedText / ExternalLink / Word / WS)+ EOL } // ------------------------------------------ -// Quote Types (bold, italic and monospace) +// Quote Texts (bold, italic and monospace) // ------------------------------------------ QuotedText <- BoldText / ItalicText / MonospaceText @@ -68,7 +73,7 @@ QuotedTextContentWord <- (!NEWLINE !WS !'*' !'_' !'`' .)+ // cannot have '*', '_ InvalidQuotedTextContentWord <- (!NEWLINE !WS .)+ // can have '*', '_' or '`' within, maybe because the user made an error (extra or missing space, for example) // ------------------------------------------ -// Link Type +// Links // ------------------------------------------ ExternalLink <- url:(URL_SCHEME URL) text:('[' (URL_TEXT)* ']')? { if text != nil { @@ -78,7 +83,7 @@ ExternalLink <- url:(URL_SCHEME URL) text:('[' (URL_TEXT)* ']')? { } // ------------------------------------------ -// Image Type +// Images // ------------------------------------------ BlockImage <- metadata:(MetadataElement)* image:BlockImageMacro { // here we can ignore the blank line in the returned element @@ -94,7 +99,7 @@ BlockImageMacro <- "image::" path:(URL) '[' attributes:(URL_TEXT?) ']' EOL { } // ------------------------------------------ -// Delimited Block Types +// Delimited Blocks // ------------------------------------------ DelimitedBlock <- SourceBlock @@ -108,7 +113,7 @@ SourceBlockDelimiter <- "```" SourceBlockLine <- (!EOL .)* NEWLINE // ------------------------------------------ -// Meta Element Types +// Meta Elements // ------------------------------------------ MetadataElement <- meta:(ElementLink / ElementID / ElementTitle) @@ -134,6 +139,10 @@ Word <- (!NEWLINE !WS .)+ { return string(c.text), nil } +BlankLine <- NEWLINE { + return types.NewBlankLine() +} + URL <- (!NEWLINE !WS !'[' !']' .)+ { return string(c.text), nil } @@ -146,16 +155,16 @@ URL_TEXT <- (!NEWLINE !'[' !']' .)+ { return string(c.text), nil } - -BlankLine <- NEWLINE { - return types.NewBlankLine() -} - URL_SCHEME <- "http://" / "https://" / "ftp://" / "irc://" / "mailto:" + DIGIT <- [0-9] + NEWLINE <- "\r\n" / '\r' / '\n' + WS <- ' ' / '\t' { return string(c.text), nil } + EOF <- !. + EOL <- NEWLINE / EOF \ No newline at end of file diff --git a/parser/asciidoc_parser_test.go b/parser/asciidoc_parser_test.go index 7024bc4e..0f0f1d53 100644 --- a/parser/asciidoc_parser_test.go +++ b/parser/asciidoc_parser_test.go @@ -1,7 +1,6 @@ package parser_test import ( - "fmt" "strings" . "github.com/bytesparadise/libasciidoc/parser" @@ -12,181 +11,8 @@ import ( "github.com/stretchr/testify/require" ) -var _ = Describe("Testing with Ginkgo", func() { - It("heading only", func() { +var _ = Describe("Parsing content", func() { - actualContent := "= a heading" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Heading{ - Level: 1, - Content: &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "a heading"}, - }, - }, - ID: &types.ElementID{ - Value: "_a_heading", - }, - }, - }} - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("heading invalid1", func() { - - actualContent := "=a heading" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "=a heading"}, - }, - }, - }, - }, - }} - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("heading invalid2", func() { - - actualContent := " = a heading" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: " = a heading"}, - }, - }, - }, - }, - }} - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("section2", func() { - - actualContent := `== section 1` - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Heading{ - Level: 2, - Content: &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "section 1"}, - }, - }, - ID: &types.ElementID{ - Value: "_section_1", - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("heading with section2", func() { - - actualContent := "= a heading\n" + - "\n" + - "== section 1" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Heading{ - Level: 1, - Content: &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "a heading"}, - }, - }, - ID: &types.ElementID{ - Value: "_a_heading", - }, - }, - &types.BlankLine{}, - &types.Heading{ - Level: 2, - Content: &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "section 1"}, - }, - }, - ID: &types.ElementID{ - Value: "_section_1", - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("heading with invalid section2", func() { - - actualContent := "= a heading\n" + - "\n" + - " == section 1" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Heading{ - Level: 1, Content: &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "a heading"}, - }, - }, - ID: &types.ElementID{ - Value: "_a_heading", - }, - }, - &types.BlankLine{}, - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: " == section 1"}, - }, - }, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("inline1 word", func() { - - actualContent := "hello" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "hello"}, - }, - }, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("inline simple", func() { - - actualContent := "a paragraph with some content" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "a paragraph with some content"}, - }, - }, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) It("heading section inline with bold quote", func() { actualContent := "= a heading\n" + @@ -238,257 +64,19 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("single list item", func() { - - actualContent := "* a list item" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.ListItem{ - Content: &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "a list item"}, - }, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("invalid list item", func() { - - actualContent := "*an invalid list item" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "*an invalid list item"}, - }, - }, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("list items", func() { - - actualContent := "* a first item\n" + - "* a second item with *bold content*" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.ListItem{ - Content: &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "a first item"}, - }, - }, - }, - &types.ListItem{ - Content: &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "a second item with "}, - &types.QuotedText{Kind: types.Bold, - Elements: []types.DocElement{ - &types.StringElement{Content: "bold content"}, - }, - }, - }, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("external link", func() { - - actualContent := "a link to https://foo.bar" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "a link to "}, - &types.ExternalLink{ - URL: "https://foo.bar", - }, - }, - }, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("external link with empty text", func() { - - actualContent := "a link to https://foo.bar[]" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "a link to "}, - &types.ExternalLink{ - URL: "https://foo.bar", - Text: "", - }, - }, - }, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("external link with text", func() { - - actualContent := "a link to mailto:foo@bar[the foo@bar email]" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "a link to "}, - &types.ExternalLink{ - URL: "mailto:foo@bar", - Text: "the foo@bar email", - }, - }, - }, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("element link", func() { - - actualContent := "[link=http://foo.bar]" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.ElementLink{Path: "http://foo.bar"}, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("element link with spaces", func() { - - actualContent := "[ link = http://foo.bar ]" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.ElementLink{Path: "http://foo.bar"}, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("element link invalid", func() { - - actualContent := "[ link = http://foo.bar" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "[ link = "}, - &types.ExternalLink{URL: "http://foo.bar"}, - }, - }, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("element i d", func() { - - actualContent := "[#img-foobar]" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.ElementID{Value: "img-foobar"}, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("element i d with spaces", func() { - actualContent := "[ #img-foobar ]" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.ElementID{Value: "img-foobar"}, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("element i d invalid", func() { - - actualContent := "[#img-foobar" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{Elements: []types.DocElement{&types.StringElement{Content: "[#img-foobar"}}}, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("element title", func() { - - actualContent := ".a title" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.ElementTitle{Content: "a title"}, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("element title invalid1", func() { - - actualContent := ". a title" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{Elements: []types.DocElement{&types.StringElement{Content: ". a title"}}}, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) - It("element title invalid2", func() { - - actualContent := "!a title" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{Elements: []types.DocElement{&types.StringElement{Content: "!a title"}}}, - }, - }, - }, - } - compare(GinkgoT(), expectedDocument, actualContent) - }) }) func compare(t GinkgoTInterface, expectedDocument *types.Document, content string) { - t.Log(fmt.Sprintf("processing:\n%s", content)) + log.Debugf("processing:\n%s", content) reader := strings.NewReader(content) result, err := ParseReader("", reader) if err != nil { - log.Errorf("Error found while parsing the document: %v", err.Error()) + log.WithError(err).Error("Error found while parsing the document") } require.Nil(t, err) actualDocument := result.(*types.Document) - t.Log(fmt.Sprintf("actual document: %s", actualDocument.String())) - t.Log(fmt.Sprintf("expected document: %s", expectedDocument.String())) - assert.EqualValues(t, expectedDocument, actualDocument) + t.Logf("actual document: %+v", actualDocument) + t.Logf("expected document: %+v", expectedDocument) + assert.EqualValues(t, *expectedDocument, *actualDocument) } diff --git a/parser/block_image_test.go b/parser/block_image_test.go index 7ab85878..65951371 100644 --- a/parser/block_image_test.go +++ b/parser/block_image_test.go @@ -5,9 +5,9 @@ import ( . "github.com/onsi/ginkgo" ) -var _ = Describe("Testing with Ginkgo", func() { - It("block image with empty alt", func() { +var _ = Describe("Parsing Block Images", func() { + It("block image with empty alt", func() { actualContent := "image::images/foo.png[]" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -21,8 +21,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("block image with alt", func() { + It("block image with alt", func() { actualContent := "image::images/foo.png[the foo.png image]" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -36,8 +36,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("block image with dimensions and i d link title meta", func() { + It("block image with dimensions and i d link title meta", func() { actualContent := "[#img-foobar]\n" + ".A title to foobar\n" + "[link=http://foo.bar]\n" + @@ -61,8 +61,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("block image appending inline content", func() { + It("block image appending inline content", func() { actualContent := "a paragraph\nimage::images/foo.png[]" expectedDocument := &types.Document{ Elements: []types.DocElement{ diff --git a/parser/delimited_block_test.go b/parser/delimited_block_test.go index b6162992..886a20ea 100644 --- a/parser/delimited_block_test.go +++ b/parser/delimited_block_test.go @@ -5,9 +5,9 @@ import ( . "github.com/onsi/ginkgo" ) -var _ = Describe("Testing with Ginkgo", func() { - It("delimited source block with single line", func() { +var _ = Describe("Parsing Delimited Blocks", func() { + It("delimited source block with single line", func() { content := "some source code" actualContent := "```\n" + content + "\n```" expectedDocument := &types.Document{ @@ -20,8 +20,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("delimited source block with multiple lines", func() { + It("delimited source block with multiple lines", func() { content := "some source code\nwith an empty line\n\nin the middle" actualContent := "```\n" + content + "\n```" expectedDocument := &types.Document{ @@ -34,8 +34,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("delimited source block with no line", func() { + It("delimited source block with no line", func() { content := "" actualContent := "```\n" + content + "```" expectedDocument := &types.Document{ diff --git a/parser/external_link_test.go b/parser/external_link_test.go new file mode 100644 index 00000000..4be81b7b --- /dev/null +++ b/parser/external_link_test.go @@ -0,0 +1,74 @@ +package parser_test + +import ( + "github.com/bytesparadise/libasciidoc/types" + . "github.com/onsi/ginkgo" +) + +var _ = Describe("Parsing External Links", func() { + + It("external link", func() { + actualContent := "a link to https://foo.bar" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a link to "}, + &types.ExternalLink{ + URL: "https://foo.bar", + }, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("external link with empty text", func() { + actualContent := "a link to https://foo.bar[]" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a link to "}, + &types.ExternalLink{ + URL: "https://foo.bar", + Text: "", + }, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("external link with text", func() { + actualContent := "a link to mailto:foo@bar[the foo@bar email]" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a link to "}, + &types.ExternalLink{ + URL: "mailto:foo@bar", + Text: "the foo@bar email", + }, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) +}) diff --git a/parser/heading_test.go b/parser/heading_test.go new file mode 100644 index 00000000..3099aeb2 --- /dev/null +++ b/parser/heading_test.go @@ -0,0 +1,147 @@ +package parser_test + +import ( + "github.com/bytesparadise/libasciidoc/types" + . "github.com/onsi/ginkgo" +) + +var _ = Describe("Parsing Headings", func() { + + It("heading only", func() { + actualContent := "= a heading" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Heading{ + Level: 1, + Content: &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a heading"}, + }, + }, + ID: &types.ElementID{ + Value: "_a_heading", + }, + }, + }} + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("heading invalid1", func() { + actualContent := "=a heading" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "=a heading"}, + }, + }, + }, + }, + }} + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("heading invalid2", func() { + actualContent := " = a heading" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: " = a heading"}, + }, + }, + }, + }, + }} + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("section2", func() { + actualContent := `== section 1` + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Heading{ + Level: 2, + Content: &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "section 1"}, + }, + }, + ID: &types.ElementID{ + Value: "_section_1", + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("heading with section2", func() { + actualContent := "= a heading\n" + + "\n" + + "== section 1" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Heading{ + Level: 1, + Content: &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a heading"}, + }, + }, + ID: &types.ElementID{ + Value: "_a_heading", + }, + }, + &types.BlankLine{}, + &types.Heading{ + Level: 2, + Content: &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "section 1"}, + }, + }, + ID: &types.ElementID{ + Value: "_section_1", + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("heading with invalid section2", func() { + actualContent := "= a heading\n" + + "\n" + + " == section 1" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Heading{ + Level: 1, Content: &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a heading"}, + }, + }, + ID: &types.ElementID{ + Value: "_a_heading", + }, + }, + &types.BlankLine{}, + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: " == section 1"}, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) +}) diff --git a/parser/list_test.go b/parser/list_test.go new file mode 100644 index 00000000..c20a3722 --- /dev/null +++ b/parser/list_test.go @@ -0,0 +1,521 @@ +package parser_test + +import ( + "github.com/bytesparadise/libasciidoc/types" + . "github.com/onsi/ginkgo" +) + +var _ = Describe("Parsing Unordered List Items", func() { + + Context("Valid content", func() { + It("1 list with a single item", func() { + actualContent := "* a list item" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a list item"}, + }, + }, + }, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + It("1 list with an ID and a single item", func() { + actualContent := "[#listID]\n" + + "* a list item" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.List{ + ID: &types.ElementID{Value: "listID"}, + Items: []*types.ListItem{ + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a list item"}, + }, + }, + }, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("1 list with 2 items with stars", func() { + actualContent := "* a first item\n" + + "* a second item with *bold content*" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a first item"}, + }, + }, + }, + }, + }, + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a second item with "}, + &types.QuotedText{Kind: types.Bold, + Elements: []types.DocElement{ + &types.StringElement{Content: "bold content"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + It("1 list with 2 items with carets", func() { + actualContent := "- a first item\n" + + "- a second item with *bold content*" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a first item"}, + }, + }, + }, + }, + }, + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a second item with "}, + &types.QuotedText{Kind: types.Bold, + Elements: []types.DocElement{ + &types.StringElement{Content: "bold content"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + It("1 list with 2 items with empty line in-between", func() { + actualContent := "* a first item\n" + + "\n" + + "* a second item with *bold content*" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a first item"}, + }, + }, + }, + }, + }, + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a second item with "}, + &types.QuotedText{Kind: types.Bold, + Elements: []types.DocElement{ + &types.StringElement{Content: "bold content"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + It("1 list with 2 items on multiple lines", func() { + actualContent := "* item 1\n" + + " on 2 lines.\n" + + "* item 2\n" + + "on 2 lines, too." + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 1"}, + }, + }, + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: " on 2 lines."}, + }, + }, + }, + }, + }, + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 2"}, + }, + }, + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "on 2 lines, too."}, + }, + }, + }, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + It("2 lists with 2 empty line in-between", func() { + actualContent := "* an item in the first list\n" + + "\n" + + "\n" + + "* an item in the second list" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "an item in the first list"}, + }, + }, + }, + }, + }, + }, + }, + &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "an item in the second list"}, + }, + }, + }, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + }) + + Context("List of multiple levels", func() { + It("a list with items on 3 levels", func() { + actualContent := "* item 1\n" + + "** item 1.1\n" + + "** item 1.2\n" + + "*** item 1.2.1\n" + + "** item 1.3\n" + + "** item 1.4\n" + + "* item 2\n" + + "** item 2.1\n" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 1"}, + }, + }, + }, + }, + Children: &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 2, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + + Elements: []types.DocElement{ + &types.StringElement{Content: "item 1.1"}, + }, + }, + }, + }, + }, + &types.ListItem{ + Level: 2, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 1.2"}, + }, + }, + }, + }, + Children: &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 3, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 1.2.1"}, + }, + }, + }, + }, + }, + }, + }, + }, + &types.ListItem{ + Level: 2, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 1.3"}, + }, + }, + }, + }, + }, + &types.ListItem{ + Level: 2, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 1.4"}, + }, + }, + }, + }, + }, + }, + }, + }, + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 2"}, + }, + }, + }, + }, + Children: &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 2, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 2.1"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + }) + + Context("Invalid content", func() { + It("a list with items on 2 levels - bad numbering", func() { + actualContent := "* item 1\n" + + "*** item 1.1\n" + + "*** item 1.1.1\n" + + "** item 1.2\n" + + "* item 2" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 1"}, + }, + }, + }, + }, + Children: &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 2, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 1.1"}, + }, + }, + }, + }, + Children: &types.List{ + Items: []*types.ListItem{ + &types.ListItem{ + Level: 3, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 1.1.1"}, + }, + }, + }, + }, + }, + }, + }, + }, + &types.ListItem{ + Level: 2, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 1.2"}, + }, + }, + }, + }, + }, + }, + }, + }, + &types.ListItem{ + Level: 1, + Content: &types.ListItemContent{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "item 2"}, + }, + }, + }, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("invalid list item", func() { + actualContent := "*an invalid list item" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "*an invalid list item"}, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + }) + +}) diff --git a/parser/meta_elements_test.go b/parser/meta_elements_test.go new file mode 100644 index 00000000..da964a7b --- /dev/null +++ b/parser/meta_elements_test.go @@ -0,0 +1,120 @@ +package parser_test + +import ( + "github.com/bytesparadise/libasciidoc/types" + . "github.com/onsi/ginkgo" +) + +var _ = Describe("Parsing Meta Elements", func() { + + It("element link", func() { + actualContent := "[link=http://foo.bar]" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.ElementLink{Path: "http://foo.bar"}, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("element link with spaces", func() { + actualContent := "[ link = http://foo.bar ]" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.ElementLink{Path: "http://foo.bar"}, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("element link invalid", func() { + actualContent := "[ link = http://foo.bar" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "[ link = "}, + &types.ExternalLink{URL: "http://foo.bar"}, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("element id", func() { + actualContent := "[#img-foobar]" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.ElementID{Value: "img-foobar"}, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("element id with spaces", func() { + actualContent := "[ #img-foobar ]" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.ElementID{Value: "img-foobar"}, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("element id invalid", func() { + actualContent := "[#img-foobar" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{Elements: []types.DocElement{&types.StringElement{Content: "[#img-foobar"}}}, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("element title", func() { + actualContent := ".a title" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.ElementTitle{Content: "a title"}, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("element title invalid1", func() { + actualContent := ". a title" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{Elements: []types.DocElement{&types.StringElement{Content: ". a title"}}}, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("element title invalid2", func() { + actualContent := "!a title" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{Elements: []types.DocElement{&types.StringElement{Content: "!a title"}}}, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) +}) diff --git a/parser/paragraph_test.go b/parser/paragraph_test.go new file mode 100644 index 00000000..24d957d2 --- /dev/null +++ b/parser/paragraph_test.go @@ -0,0 +1,45 @@ +package parser_test + +import ( + "github.com/bytesparadise/libasciidoc/types" + . "github.com/onsi/ginkgo" +) + +var _ = Describe("Parsing Paragraphs", func() { + + It("inline1 word", func() { + actualContent := "hello" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "hello"}, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) + + It("inline simple", func() { + actualContent := "a paragraph with some content" + expectedDocument := &types.Document{ + Elements: []types.DocElement{ + &types.Paragraph{ + Lines: []*types.InlineContent{ + &types.InlineContent{ + Elements: []types.DocElement{ + &types.StringElement{Content: "a paragraph with some content"}, + }, + }, + }, + }, + }, + } + compare(GinkgoT(), expectedDocument, actualContent) + }) +}) diff --git a/parser/quotedtext_test.go b/parser/quoted_text_test.go similarity index 90% rename from parser/quotedtext_test.go rename to parser/quoted_text_test.go index 25f0f54f..e962bec5 100644 --- a/parser/quotedtext_test.go +++ b/parser/quoted_text_test.go @@ -5,9 +5,9 @@ import ( . "github.com/onsi/ginkgo" ) -var _ = Describe("Testing with Ginkgo", func() { - It("bold text of1 word", func() { +var _ = Describe("Parsing Quoted Texts", func() { + It("bold text of 1 word", func() { actualContent := "*hello*" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -29,8 +29,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("bold text of2 words", func() { + It("bold text of 2 words", func() { actualContent := "*bold content*" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -52,8 +52,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("bold text of3 words", func() { + It("bold text of 3 words", func() { actualContent := "*some bold content*" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -75,8 +75,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("inline with bold text", func() { + It("inline with bold text", func() { actualContent := "a paragraph with *some bold content*" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -99,8 +99,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("inline with invalid bold text1", func() { + It("inline with invalid bold text1", func() { actualContent := "a paragraph with *some bold content" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -117,8 +117,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("inline with invalid bold text2", func() { + It("inline with invalid bold text2", func() { actualContent := "a paragraph with *some bold content *" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -135,8 +135,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("inline with invalid bold text3", func() { + It("inline with invalid bold text3", func() { actualContent := "a paragraph with * some bold content*" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -153,8 +153,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("italic text with3 words", func() { + It("italic text with3 words", func() { actualContent := "_some italic content_" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -176,8 +176,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("monospace text with3 words", func() { + It("monospace text with3 words", func() { actualContent := "`some monospace content`" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -199,8 +199,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("italic text within bold text", func() { + It("italic text within bold text", func() { actualContent := "some *bold and _italic content_ together*." expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -231,8 +231,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("invalid italic text within bold text", func() { + It("invalid italic text within bold text", func() { actualContent := "some *bold and _italic content _ together*." expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -256,8 +256,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("italic text within invalid bold text", func() { + It("italic text within invalid bold text", func() { actualContent := "some *bold and _italic content_ together *." expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -281,8 +281,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("bold text within italic text", func() { + It("bold text within italic text", func() { actualContent := "_some *bold* content_" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -311,8 +311,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("monospace text within bold text within italic quote", func() { + It("monospace text within bold text within italic quote", func() { actualContent := "*some _italic and `monospaced content`_*" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -346,8 +346,8 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) - It("italic text within italic text", func() { + It("italic text within italic text", func() { actualContent := "_some _very italic_ content_" expectedDocument := &types.Document{ Elements: []types.DocElement{ @@ -376,31 +376,29 @@ var _ = Describe("Testing with Ginkgo", func() { } compare(GinkgoT(), expectedDocument, actualContent) }) -}) - -func xTestAllQuotes(t GinkgoTInterface) { - - actualContent := "*bold phrase* & **char**acter**s**\n" + - "_italic phrase_ & __char__acter__s__\n" + - "*_bold italic phrase_* & **__char__**acter**__s__**\n" + - "`monospace phrase` & ``char``acter``s``\n" + - "`*monospace bold phrase*` & ``**char**``acter``**s**``\n" + - "`_monospace italic phrase_` & ``__char__``acter``__s__``\n" + - "`*_monospace bold italic phrase_*` & \n" + - "``**__char__**``acter``**__s__**``" - expectedDocument := &types.Document{ - Elements: []types.DocElement{ - &types.Paragraph{ - Lines: []*types.InlineContent{ - &types.InlineContent{ - Elements: []types.DocElement{ - &types.StringElement{Content: "a paragraph with * some bold content*"}, - }, - }, - }, - }, - }, - } - compare(t, expectedDocument, actualContent) -} + // It("all supported quotes", func() { + // actualContent := "*bold phrase* & **char**acter**s**\n" + + // "_italic phrase_ & __char__acter__s__\n" + + // "*_bold italic phrase_* & **__char__**acter**__s__**\n" + + // "`monospace phrase` & ``char``acter``s``\n" + + // "`*monospace bold phrase*` & ``**char**``acter``**s**``\n" + + // "`_monospace italic phrase_` & ``__char__``acter``__s__``\n" + + // "`*_monospace bold italic phrase_*` & \n" + + // "``**__char__**``acter``**__s__**``" + // expectedDocument := &types.Document{ + // Elements: []types.DocElement{ + // &types.Paragraph{ + // Lines: []*types.InlineContent{ + // &types.InlineContent{ + // Elements: []types.DocElement{ + // &types.StringElement{Content: "a paragraph with * some bold content*"}, + // }, + // }, + // }, + // }, + // }, + // } + // compare(GinkgoT(), expectedDocument, actualContent) + // }) +}) diff --git a/renderer/html5/delimited_block_test.go b/renderer/html5/delimited_block_test.go index 5a21014b..bf946c96 100644 --- a/renderer/html5/delimited_block_test.go +++ b/renderer/html5/delimited_block_test.go @@ -2,8 +2,8 @@ package html5_test import . "github.com/onsi/ginkgo" -var _ = Describe("render delimited source blocks", func() { - It("source with multiple lines", func() { +var _ = Describe("Rendering Delimited Blocks", func() { + It("source block with multiple lines", func() { content := "```\nsome source code\n\nhere\n```" expected := `
diff --git a/renderer/html5/heading_test.go b/renderer/html5/heading_test.go index 6c0ae20e..487e2fa4 100644 --- a/renderer/html5/heading_test.go +++ b/renderer/html5/heading_test.go @@ -2,7 +2,7 @@ package html5_test import . "github.com/onsi/ginkgo" -var _ = Describe("render headings", func() { +var _ = Describe("Rendering Headings", func() { It("heading level 1", func() { content := "= a title" expected := ``) + listItemTmpl = newTemplate("list item", `
  • +{{.Content}}{{ if .Children }} +{{.Children}}{{ end }} +
  • `) + listItemContentTmpl = newTemplate("list item content", `

    {{.}}

    `) +} + +func renderList(ctx context.Context, list types.List) ([]byte, error) { + renderedElementsBuff := bytes.NewBuffer(make([]byte, 0)) + for i, item := range list.Items { + renderedListItem, err := renderListItem(ctx, *item) + if err != nil { + return nil, errors.Wrapf(err, "unable to render list of items") + } + renderedElementsBuff.Write(renderedListItem) + if i < len(list.Items)-1 { + renderedElementsBuff.WriteString("\n") + } + } + + result := bytes.NewBuffer(make([]byte, 0)) + // here we must preserve the HTML tags + err := unorderedListTmpl.Execute(result, struct { + ID *types.ElementID + Items template.HTML + }{ + ID: list.ID, + Items: template.HTML(renderedElementsBuff.String()), + }) + if err != nil { + return nil, errors.Wrapf(err, "unable to render list of items") + } + log.Debugf("rendered list of items: %s", result.Bytes()) + return result.Bytes(), nil +} + +func renderListItem(ctx context.Context, item types.ListItem) ([]byte, error) { + renderedItemContent, err := renderListItemContent(ctx, *item.Content) + if err != nil { + return nil, errors.Wrapf(err, "unable to render list item") + } + result := bytes.NewBuffer(make([]byte, 0)) + var renderedChildrenOutput *template.HTML + if item.Children != nil { + childrenOutput, err := renderList(ctx, *item.Children) + if err != nil { + return nil, errors.Wrapf(err, "unable to render list item") + } + htmlChildrenOutput := template.HTML(string(childrenOutput)) + renderedChildrenOutput = &htmlChildrenOutput + } + err = listItemTmpl.Execute(result, struct { + Content template.HTML + Children *template.HTML + }{ + Content: template.HTML(string(renderedItemContent)), + Children: renderedChildrenOutput, + }) + if err != nil { + return nil, errors.Wrapf(err, "unable to render list item") + } + log.Debugf("rendered item: %s", result.Bytes()) + return result.Bytes(), nil +} + +func renderListItemContent(ctx context.Context, content types.ListItemContent) ([]byte, error) { + renderedLinesBuff := bytes.NewBuffer(make([]byte, 0)) + for _, line := range content.Lines { + renderedLine, err := renderInlineContent(ctx, *line) + if err != nil { + return nil, errors.Wrapf(err, "failed to render list item content") + } + renderedLinesBuff.Write(renderedLine) + } + result := bytes.NewBuffer(make([]byte, 0)) + err := listItemContentTmpl.Execute(result, renderedLinesBuff.String()) + if err != nil { + return nil, errors.Wrapf(err, "unable to render list item") + } + log.Debugf("rendered item content: %s", result.Bytes()) + return result.Bytes(), nil +} diff --git a/renderer/html5/list_item_test.go b/renderer/html5/list_item_test.go new file mode 100644 index 00000000..95b80c15 --- /dev/null +++ b/renderer/html5/list_item_test.go @@ -0,0 +1,92 @@ +package html5_test + +import . "github.com/onsi/ginkgo" + +var _ = Describe("Rendering List of Items", func() { + It("simple list", func() { + content := `* item 1 +* item 2` + expected := `
    +
      +
    • +

      item 1

      +
    • +
    • +

      item 2

      +
    • +
    +
    ` + verify(GinkgoT(), expected, content) + }) + It("simple list with a title", func() { + content := `[#foo] + * item 1 + * item 2` + expected := `
    +
      +
    • +

      item 1

      +
    • +
    • +

      item 2

      +
    • +
    +
    ` + verify(GinkgoT(), expected, content) + }) + It("nested lists", func() { + content := `* item 1 +** item 1.1 +** item 1.2 +* item 2` + expected := `
    +
      +
    • +

      item 1

      +
      +
        +
      • +

        item 1.1

        +
      • +
      • +

        item 1.2

        +
      • +
      +
      +
    • +
    • +

      item 2

      +
    • +
    +
    ` + verify(GinkgoT(), expected, content) + }) + It("nested lists with a title", func() { + content := `[#foo] +* item 1 +** item 1.1 +** item 1.2 +* item 2` + expected := `
    +
      +
    • +

      item 1

      +
      +
        +
      • +

        item 1.1

        +
      • +
      • +

        item 1.2

        +
      • +
      +
      +
    • +
    • +

      item 2

      +
    • +
    +
    ` + verify(GinkgoT(), expected, content) + }) +}) diff --git a/renderer/html5/paragraph_test.go b/renderer/html5/paragraph_test.go new file mode 100644 index 00000000..607ff205 --- /dev/null +++ b/renderer/html5/paragraph_test.go @@ -0,0 +1,15 @@ +package html5_test + +import . "github.com/onsi/ginkgo" + +var _ = Describe("Rendering Paragraph", func() { + It("some paragraph", func() { + + content := "*bold content* \n" + + "with more content afterwards..." + expected := `
    +

    bold content with more content afterwards...

    +
    ` + verify(GinkgoT(), expected, content) + }) +}) diff --git a/renderer/html5/quoted_text_test.go b/renderer/html5/quoted_text_test.go index 4577c940..6dde3f55 100644 --- a/renderer/html5/quoted_text_test.go +++ b/renderer/html5/quoted_text_test.go @@ -2,7 +2,7 @@ package html5_test import . "github.com/onsi/ginkgo" -var _ = Describe("render quotes", func() { +var _ = Describe("Rendering Quoted Texts", func() { It("bold content alone", func() { content := "*bold content*" diff --git a/renderer/html5/renderer.go b/renderer/html5/renderer.go index ab87f0c5..8ab4fcf0 100644 --- a/renderer/html5/renderer.go +++ b/renderer/html5/renderer.go @@ -26,6 +26,8 @@ func renderElement(ctx context.Context, element types.DocElement) ([]byte, error switch element.(type) { case *types.Heading: return renderHeading(ctx, *element.(*types.Heading)) + case *types.List: + return renderList(ctx, *element.(*types.List)) case *types.Paragraph: return renderParagraph(ctx, *element.(*types.Paragraph)) case *types.QuotedText: diff --git a/renderer/html5/renderer_test.go b/renderer/html5/renderer_test.go index 9dc5575b..fb09a18a 100644 --- a/renderer/html5/renderer_test.go +++ b/renderer/html5/renderer_test.go @@ -3,7 +3,6 @@ package html5_test import ( "bytes" "context" - "fmt" "strings" "github.com/bytesparadise/libasciidoc/parser" @@ -15,8 +14,7 @@ import ( ) func verify(t GinkgoTInterface, expected, content string) { - - t.Log(fmt.Sprintf("processing '%s'", content)) + t.Logf("processing '%s'", content) reader := strings.NewReader(content) doc, err := parser.ParseReader("", reader) if err != nil { @@ -24,14 +22,14 @@ func verify(t GinkgoTInterface, expected, content string) { } require.Nil(t, err) actualDocument := doc.(*types.Document) - buff := bytes.NewBuffer(make([]byte, 0)) err = Render(context.Background(), *actualDocument, buff) t.Log("Done processing document") - require.Nil(t, err) require.Empty(t, err) result := string(buff.Bytes()) + t.Logf("** Actual output:\n%s\n", result) + t.Logf("** Expected output:\n%s\n", expected) assert.Equal(t, expected, result) } diff --git a/test/log_init.go b/test/log_init.go index ec4b1d37..9f6795e2 100644 --- a/test/log_init.go +++ b/test/log_init.go @@ -2,7 +2,7 @@ package test import ( "flag" - "fmt" + "os" log "github.com/sirupsen/logrus" ) @@ -11,13 +11,33 @@ import ( // Other tests must import this 'test' package even if unused, using: // import _ "github.com/bytesparadise/libasciidoc/test" func init() { - debugMode := false - flag.BoolVar(&debugMode, "debug", false, "when set, enables debug log messages") - // flag.Parse() - fmt.Printf("Args: %v\n", flag.Args()) - if debugMode { + customFormatter := new(log.TextFormatter) + customFormatter.TimestampFormat = "2006-01-02 15:04:05" + log.SetFormatter(customFormatter) + if debugMode() { log.SetLevel(log.DebugLevel) log.Warn("Running test with logs in debug-level") } log.SetFormatter(&log.TextFormatter{FullTimestamp: false}) } + +func debugMode() bool { + debugMode := false + flag.BoolVar(&debugMode, "debug", false, "when set, enables debug log messages") + if !flag.Parsed() { + flag.Parse() + } + // if the `-debug` flag was passed and captured by the `flag.Parse` + if debugMode { + log.Info("`-debug` flag found") + return debugMode + } + // otherwise, check the OS args + for _, arg := range os.Args { + if arg == "-debug" { + log.Info("`-debug` os env found") + return true + } + } + return false +} diff --git a/types/type_utils_test.go b/types/type_utils_test.go index 5c882d8e..e1100060 100644 --- a/types/type_utils_test.go +++ b/types/type_utils_test.go @@ -3,12 +3,11 @@ package types import ( "github.com/stretchr/testify/require" - _ "github.com/bytesparadise/libasciidoc/test" . "github.com/onsi/ginkgo" "github.com/stretchr/testify/assert" ) -var _ = Describe("normalize string", func() { +var _ = Describe("Normalizing String", func() { It("hello", func() { verify(GinkgoT(), "hello", "hello") }) diff --git a/types/types.go b/types/types.go index a208110e..ecd4c3ab 100644 --- a/types/types.go +++ b/types/types.go @@ -88,13 +88,13 @@ func NewHeading(level interface{}, inlineContent *InlineContent, metadata []inte id, _ = NewElementID(v.NormalizedContent()) } heading := Heading{Level: actualLevel, Content: inlineContent, ID: id} - log.Debugf("New Heading: %v", heading) + log.Debugf("Initializing a ewHeading: %v", heading) return &heading, nil } //String implements the DocElement#String() method func (h Heading) String() string { - return fmt.Sprintf(" '%s'", h.Level, h.Content.String()) + return fmt.Sprintf("<%v %d> '%s'", reflect.TypeOf(h), h.Level, h.Content.String()) } //Accept implements DocElement#Accept(Visitor) @@ -115,32 +115,134 @@ func (h Heading) Accept(v Visitor) error { } // ------------------------------------------ -// ListItem +// Lists // ------------------------------------------ +// List the structure for the lists +type List struct { + ID *ElementID + Items []*ListItem +} + +//NewList initializes a new `ListItem` from the given content +func NewList(elements []interface{}, metadata []interface{}) (*List, error) { + id, _, _ := newMetaElements(metadata) + items := make([]*ListItem, 0) + log.Debugf("Initializing a new List from %d elements", len(elements)) + currentLevel := 1 + lastItems := make([]*ListItem, 10) + for _, element := range elements { + // each "list item" can be a "list item" element followed by an optional blank line (ignored during the processing below) + // also, a list item may need to be divided when it contains lines starting with a caret or a group of stars... + + if itemElements, ok := element.([]interface{}); ok { + if item, ok := itemElements[0].(*ListItem); ok { + //log.Debugf(" processing element of type '%v' with current level=%d...", reflect.TypeOf(itemElements[0]), item.Level) + if item.Level == 1 { + items = append(items, item) + } else if item.Level > currentLevel { + // force the current item level to (last seen level + 1) + item.Level = currentLevel + 1 + } + + if item.Level > 1 { + // now join the item to its parent + parentItem := lastItems[item.Level-2] + if parentItem.Children == nil { + parentItem.Children = &List{} + } + parentItem.Children.Items = append(parentItem.Children.Items, item) + } + // memorizes the current item for further processing + if item.Level > cap(lastItems) { // increase capacity + newCap := 2 * item.Level + newSlice := make([]*ListItem, newCap) + copy(newSlice, lastItems) + lastItems = newSlice + } + if item.Level < currentLevel { // remove some items + for i := item.Level; i < currentLevel; i++ { + lastItems[i] = nil + + } + } + currentLevel = item.Level + lastItems[item.Level-1] = item + } + } + } + return &List{ + ID: id, + Items: items, + }, nil +} + +//String implements the DocElement#String() method +func (l List) String() string { + result := fmt.Sprintf("<%v|size=%d>", reflect.TypeOf(l), len(l.Items)) + for _, item := range l.Items { + result = result + "\n\t" + item.String() + } + return result +} + +//Accept implements DocElement#Accept(Visitor) +func (l List) Accept(v Visitor) error { + err := v.BeforeVisit(l) + if err != nil { + return errors.Wrapf(err, "error while pre-visiting list") + } + err = v.Visit(l) + if err != nil { + return errors.Wrapf(err, "error while visiting list") + } + for _, item := range l.Items { + err := item.Accept(v) + if err != nil { + return errors.Wrapf(err, "error while visiting list item") + } + } + err = v.AfterVisit(l) + if err != nil { + return errors.Wrapf(err, "error while post-visiting list") + } + return nil +} + // ListItem the structure for the list items type ListItem struct { - Content *InlineContent + Level int + Content *ListItemContent + Children *List } //NewListItem initializes a new `ListItem` from the given content -func NewListItem(content *InlineContent) (*ListItem, error) { - log.Debugf("New list item based on %v", content) - // items := strings.Split(value, " ") - // number, err := strconv.Atoi(items[0]) - // if err != nil { - // return nil, errs.Wrapf(err, "failed to create new ListItem") - // } - // content := items[1] - return &ListItem{ - // Number: number, - Content: content, - }, nil +func NewListItem(level interface{}, content *ListItemContent, children *List) (*ListItem, error) { + switch vals := reflect.ValueOf(level); vals.Kind() { + case reflect.Slice: + log.Debugf("Initializing a new ListItem with content '%s' lines and input level '%d'", content, vals.Len()) + return &ListItem{ + Level: vals.Len(), + Content: content, + Children: children, + }, nil + default: + return nil, errors.Errorf("Unable to initialize a ListItem with level '%v", level) + } } //String implements the DocElement#String() method func (i ListItem) String() string { - return fmt.Sprintf(" %v", *i.Content) + return i.StringWithIndent(1) +} + +// StringWithIndent same as String() but with a specified number of spaces at the beginning of the line, to produce a given level of indentation +func (i ListItem) StringWithIndent(indentLevel int) string { + result := fmt.Sprintf("%s<%v|level=%d> %s", strings.Repeat(" ", indentLevel), reflect.TypeOf(i), i.Level, i.Content.String()) + for _, c := range i.Children.Items { + result = result + "\n\t" + c.StringWithIndent(indentLevel+1) + } + return result } //Accept implements DocElement#Accept(Visitor) @@ -157,6 +259,12 @@ func (i ListItem) Accept(v Visitor) error { if err != nil { return errors.Wrapf(err, "error while visiting list item content") } + for _, child := range i.Children.Items { + err := child.Accept(v) + if err != nil { + return errors.Wrapf(err, "error while visiting list item child") + } + } err = v.AfterVisit(i) if err != nil { return errors.Wrapf(err, "error while post-visiting list item") @@ -164,30 +272,79 @@ func (i ListItem) Accept(v Visitor) error { return nil } +// ListItemContent the structure for the list item content +type ListItemContent struct { + Lines []*InlineContent +} + +//NewListItemContent initializes a new `ListItemContent` +func NewListItemContent(text []byte, lines []interface{}) (*ListItemContent, error) { + log.Debugf("Initializing a new ListItemContent with %d line(s)", len(lines)) + typedLines := make([]*InlineContent, 0) + for _, line := range lines { + // here, `line` is an []interface{} in which we need to locate the relevant `*InlineContent` fragment + if lineFragments, ok := line.([]interface{}); ok { + for i := range lineFragments { + if fragment, ok := lineFragments[i].(*InlineContent); ok { + typedLines = append(typedLines, fragment) + } + } + } + } + return &ListItemContent{Lines: typedLines}, nil +} + +//String implements the DocElement#String() method +func (c ListItemContent) String() string { + return fmt.Sprintf("<%v> %v", reflect.TypeOf(c), c.Lines) +} + +//Accept implements DocElement#Accept(Visitor) +func (c ListItemContent) Accept(v Visitor) error { + err := v.BeforeVisit(c) + if err != nil { + return errors.Wrapf(err, "error while pre-visiting ListItemContent") + } + err = v.Visit(c) + if err != nil { + return errors.Wrapf(err, "error while visiting ListItemContent") + } + for _, line := range c.Lines { + err := line.Accept(v) + if err != nil { + return errors.Wrapf(err, "error while visiting ListItemContent line") + } + + } + err = v.AfterVisit(c) + if err != nil { + return errors.Wrapf(err, "error while post-visiting ListItemContent") + } + return nil +} + // ------------------------------------------ // Paragraph // ------------------------------------------ // Paragraph the structure for the paragraph type Paragraph struct { - // Input []byte Lines []*InlineContent } //NewParagraph initializes a new `Paragraph` func NewParagraph(text []byte, lines []interface{}) (*Paragraph, error) { - log.Debugf("New paragraph with %d lines: ", len(lines)) + log.Debugf("Initializing a new Paragraph with %d line(s)", len(lines)) typedLines := make([]*InlineContent, 0) for _, line := range lines { typedLines = append(typedLines, line.(*InlineContent)) } - // return &InlineContent{Input: text, Elements: mergedElements}, nil return &Paragraph{Lines: typedLines}, nil } //String implements the DocElement#String() method func (p Paragraph) String() string { - return fmt.Sprintf(" %[1]v", p.Lines) + return fmt.Sprintf("<%v> %v", reflect.TypeOf(p), p.Lines) } //Accept implements DocElement#Accept(Visitor) @@ -230,13 +387,13 @@ func NewInlineContent(text []byte, elements []interface{}) (*InlineContent, erro for _, e := range merge(elements) { mergedElements = append(mergedElements, e.(DocElement)) } - log.Debugf("New InlineContent: %v (%d)", mergedElements, len(mergedElements)) + log.Debugf("Initialized new InlineContent: %v (%d)", mergedElements, len(mergedElements)) return &InlineContent{Elements: mergedElements}, nil } //String implements the DocElement#String() method func (c InlineContent) String() string { - return fmt.Sprintf(" %[1]v", c.Elements, len(c.Elements)) + return fmt.Sprintf("<%v|size=%d> %v", reflect.TypeOf(c), len(c.Elements), c.Elements) } //Accept implements DocElement#Accept(Visitor) @@ -288,32 +445,32 @@ func NewBlockImage(input []byte, imageMacro BlockImageMacro, metadata []interfac } //String implements the DocElement#String() method -func (img BlockImage) String() string { - return "" + img.Macro.String() +func (i BlockImage) String() string { + return fmt.Sprintf("<%v> %s", reflect.TypeOf(i), i.Macro.String()) } -func (img BlockImage) elements() []Visitable { - return []Visitable{img.ID, img.Link, img.Macro, img.Title} +func (i BlockImage) elements() []Visitable { + return []Visitable{i.ID, i.Link, i.Macro, i.Title} } //Accept implements DocElement#Accept(Visitor) -func (img BlockImage) Accept(v Visitor) error { - err := v.BeforeVisit(img) +func (i BlockImage) Accept(v Visitor) error { + err := v.BeforeVisit(i) if err != nil { return errors.Wrapf(err, "error while pre-visiting block image") } - err = v.Visit(img) + err = v.Visit(i) if err != nil { return errors.Wrapf(err, "error while visiting block image") } - for _, element := range img.elements() { + for _, element := range i.elements() { err := element.Accept(v) if err != nil { return errors.Wrapf(err, "error while visiting block image element") } } - err = v.AfterVisit(img) + err = v.AfterVisit(i) if err != nil { return errors.Wrapf(err, "error while post-visiting block image") } @@ -330,6 +487,7 @@ type BlockImageMacro struct { //NewBlockImageMacro initializes a new `BlockImageMacro` func NewBlockImageMacro(input []byte, path string, attributes *string) (*BlockImageMacro, error) { + log.Debugf("Initializing a new BlockImageMacro from '%s'", input) var alt string var width, height *string if attributes != nil { @@ -358,7 +516,6 @@ func NewBlockImageMacro(input []byte, path string, attributes *string) (*BlockIm } alt = path[offset : len(path)-len(extension)] } - log.Debugf("Initializing a new BlockImageMacro from '%s'", input) return &BlockImageMacro{ Path: path, Alt: alt, @@ -375,7 +532,7 @@ func (m BlockImageMacro) String() string { if m.Height != nil { height = *m.Height } - return fmt.Sprintf(" %s[%s,w=%s h=%s]", m.Path, m.Alt, width, height) + return fmt.Sprintf("<%v> %s[%s,w=%s h=%s]", reflect.TypeOf(m), m.Path, m.Alt, width, height) } //Accept implements DocElement#Accept(Visitor) @@ -427,12 +584,7 @@ func NewDelimitedBlock(kind DelimitedBlockKind, content []interface{}) (*Delimit //String implements the DocElement#String() method func (b DelimitedBlock) String() string { - switch b.Kind { - case SourceBlock: - return fmt.Sprintf(" %v", b.Content) - default: - return fmt.Sprintf(" %v", b.Content) - } + return fmt.Sprintf("<%v> %v", reflect.TypeOf(b), b.Content) } //Accept implements DocElement#Accept(Visitor) @@ -482,13 +634,13 @@ type ElementLink struct { //NewElementLink initializes a new `ElementLink` from the given path func NewElementLink(path string) (*ElementLink, error) { - log.Debugf("New ElementLink with path=%s", path) + log.Debugf("Initializing a new ElementLink with path=%s", path) return &ElementLink{Path: path}, nil } //String implements the DocElement#String() method func (e ElementLink) String() string { - return fmt.Sprintf(" %s", e.Path) + return fmt.Sprintf("<%v> %s", reflect.TypeOf(e), e.Path) } //Accept implements DocElement#Accept(Visitor) @@ -515,13 +667,13 @@ type ElementID struct { //NewElementID initializes a new `ElementID` from the given path func NewElementID(id string) (*ElementID, error) { - log.Debugf("New ElementID with ID=%s", id) + log.Debugf("Initializing a ewElementID with ID=%s", id) return &ElementID{Value: id}, nil } //String implements the DocElement#String() method func (e ElementID) String() string { - return fmt.Sprintf(" %s", e.Value) + return fmt.Sprintf("<%v> %s", reflect.TypeOf(e), e.Value) } //Accept implements DocElement#Accept(Visitor) @@ -553,13 +705,13 @@ func NewElementTitle(content []interface{}) (*ElementTitle, error) { if err != nil { return nil, errors.Wrapf(err, "failed to initialize a new ElementTitle") } - log.Debugf("New ElementTitle with content=%s", c) + log.Debugf("Initializing a ewElementTitle with content=%s", c) return &ElementTitle{Content: *c}, nil } //String implements the DocElement#String() method func (e ElementTitle) String() string { - return fmt.Sprintf(" %s", e.Content) + return fmt.Sprintf("<%v> %s", reflect.TypeOf(e), e.Content) } //Accept implements DocElement#Accept(Visitor) @@ -595,7 +747,7 @@ func NewStringElement(content interface{}) *StringElement { //String implements the DocElement#String() method func (s StringElement) String() string { - return fmt.Sprintf(" '%s' (%d)", s.Content, len(s.Content)) + return fmt.Sprintf("<%v> '%s'", reflect.TypeOf(s), s.Content) } //Accept implements DocElement#Accept(Visitor) @@ -652,7 +804,7 @@ func NewQuotedText(kind QuotedTextKind, content []interface{}) (*QuotedText, err //String implements the DocElement#String() method func (t QuotedText) String() string { - return fmt.Sprintf(" %v", t.Kind, t.Elements) + return fmt.Sprintf("<%v (%d)> %v", reflect.TypeOf(t), t.Kind, t.Elements) } //Accept implements DocElement#Accept(Visitor) @@ -694,7 +846,7 @@ func NewBlankLine() (*BlankLine, error) { //String implements the DocElement#String() method func (l BlankLine) String() string { - return "" + return fmt.Sprintf("<%v>", reflect.TypeOf(l)) } //Accept implements DocElement#Accept(Visitor) @@ -741,7 +893,7 @@ func NewExternalLink(url, text []interface{}) (*ExternalLink, error) { //String implements the DocElement#String() method func (l ExternalLink) String() string { - return fmt.Sprintf(" %s[%s]", l.URL, l.Text) + return fmt.Sprintf("<%v> %s[%s]", reflect.TypeOf(l), l.URL, l.Text) } //Accept implements DocElement#Accept(Visitor)