diff --git a/pkg/parser/delimited_block_source_test.go b/pkg/parser/delimited_block_source_test.go index dcc3b877..34b7ebea 100644 --- a/pkg/parser/delimited_block_source_test.go +++ b/pkg/parser/delimited_block_source_test.go @@ -8,6 +8,8 @@ import ( . "github.com/onsi/gomega" //nolint golint ) +const cookies = "life" + var _ = Describe("source blocks", func() { Context("draft documents", func() { @@ -143,12 +145,77 @@ type Foo struct{ } Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected)) }) + + It("with callout and admonition block afterwards", func() { + source := `[source] +---- +const cookies = "cookies" <1> +---- +<1> a constant + +[NOTE] +==== +a note +====` + + expected := types.DraftDocument{ + Elements: []interface{}{ + types.ListingBlock{ + Attributes: types.Attributes{ + types.AttrBlockKind: types.Source, + }, + Lines: [][]interface{}{ + { + types.StringElement{ + Content: `const cookies = "cookies" `, + }, + types.Callout{ + Ref: 1, + }, + }, + }, + }, + types.CalloutListItem{ + Ref: 1, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a constant", + }, + }, + }, + }, + }, + }, + types.BlankLine{}, + types.ExampleBlock{ + Attributes: types.Attributes{ + types.AttrAdmonitionKind: types.Note, + }, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a note", + }, + }, + }, + }, + }, + }, + }, + } + Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected)) + }) }) }) Context("final documents", func() { - Context("source block", func() { + Context("delimited block", func() { It("with source attribute only", func() { source := `[source] @@ -290,6 +357,74 @@ end } Expect(ParseDocument(source)).To(MatchDocument(expected)) }) + + It("with callout and admonition block afterwards", func() { + source := `[source] +---- +const cookies = "cookies" <1> +---- +<1> a constant + +[NOTE] +==== +a note +====` + + expected := types.Document{ + Elements: []interface{}{ + types.ListingBlock{ + Attributes: types.Attributes{ + types.AttrBlockKind: types.Source, + }, + Lines: [][]interface{}{ + { + types.StringElement{ + Content: `const cookies = "cookies" `, + }, + types.Callout{ + Ref: 1, + }, + }, + }, + }, + types.CalloutList{ + Items: []types.CalloutListItem{ + { + Ref: 1, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a constant", + }, + }, + }, + }, + }, + }, + }, + }, + types.ExampleBlock{ + Attributes: types.Attributes{ + types.AttrAdmonitionKind: types.Note, + }, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a note", + }, + }, + }, + }, + }, + }, + }, + } + Expect(ParseDocument(source)).To(MatchDocument(expected)) + }) }) }) }) diff --git a/pkg/parser/document_processing_rearrange_lists.go b/pkg/parser/document_processing_rearrange_lists.go index ee83a94d..6ad987b0 100644 --- a/pkg/parser/document_processing_rearrange_lists.go +++ b/pkg/parser/document_processing_rearrange_lists.go @@ -12,196 +12,190 @@ import ( // rearrangeListItems moves the list items into lists, and nested lists if needed func rearrangeListItems(blocks []interface{}, withinDelimitedBlock bool) ([]interface{}, error) { - // log.Debug("rearranging list items:") - result := make([]interface{}, 0, len(blocks)) // maximum capacity cannot exceed initial input - lists := []types.List{} // at each level (or depth), we have a list, whatever its type. - // track if the previous block was a blank line. - // also, count the blanklines to determine the level of parent attachment when reaching a `ContinuedListItemElement` - blanklineCount := 0 + log.Debug("rearranging list items...") + a := &listArranger{ + blocks: make([]interface{}, 0, len(blocks)), + lists: []types.List{}, + blanklineCounter: 0, + withinDelimitedBlock: withinDelimitedBlock, + } var err error for _, block := range blocks { switch block := block.(type) { case types.ExampleBlock: - if block.Elements, err = rearrangeListItemsInBlocks(block.Elements); err != nil { + if block.Elements, err = rearrangeListItems(block.Elements, true); err != nil { return nil, err } - result = append(result, block) + a.appendBlock(block) case types.QuoteBlock: - if block.Elements, err = rearrangeListItemsInBlocks(block.Elements); err != nil { + if block.Elements, err = rearrangeListItems(block.Elements, true); err != nil { return nil, err } - result = append(result, block) + a.appendBlock(block) case types.SidebarBlock: - if block.Elements, err = rearrangeListItemsInBlocks(block.Elements); err != nil { + if block.Elements, err = rearrangeListItems(block.Elements, true); err != nil { return nil, err } - result = append(result, block) + a.appendBlock(block) case types.OrderedListItem, types.UnorderedListItem, types.LabeledListItem, types.CalloutListItem: // there's a special case: if the next list item has attributes and was preceded by a // blank line, then we need to start a new list - if blanklineCount > 0 && len(block.(types.DocumentElement).GetAttributes()) > 0 { - if len(lists) > 0 { - for _, list := range pruneLists(lists, 0) { - result = append(result, unPtr(list)) - } - // reset the list for further usage while processing the rest of the document - lists = []types.List{} - } + if a.blanklineCounter > 0 && len(block.(types.DocumentElement).GetAttributes()) > 0 { + a.appendPendingLists() } - var err error - lists, err = appendListItem(lists, block) - if err != nil { + if err = a.appendListItem(block); err != nil { return nil, errors.Wrapf(err, "unable to rearrange list items in delimited block") } - blanklineCount = 0 case types.ContinuedListItemElement: - block.Offset = blanklineCount - lists = appendContinuedListItemElement(lists, block) - blanklineCount = 0 + block.Offset = a.blanklineCounter + a.appendContinuedListItemElement(block) case types.BlankLine: - // blank lines are not part of the resulting Document sections (or top-level), but they are part of the delimited blocks - // in some cases, they can also be used to split lists apart (when the next item has some attributes, - // or if the next block is a comment) - if withinDelimitedBlock && len(lists) == 0 { // only retain blank lines if within a delimited block, but not currently dealing with a list (or a set of nested lists) - result = append(result, block) - } - blanklineCount++ + a.appendBlankline(block) + default: - blanklineCount = 0 - // an block which is not a list item was found. - // the first thing to do is to process the pending list items, - // then only append this block to the result - if len(lists) > 0 { - log.Debugf("appending %d lists before processing element of type %T", len(lists), block) - for _, list := range pruneLists(lists, 0) { - result = append(result, unPtr(list)) - } - // reset the list for further usage while processing the rest of the document - lists = []types.List{} - } - result = append(result, block) + a.appendBlock(block) } } // also when all is done, process the remaining pending list items - if len(lists) > 0 { - log.Debugf("processing the remaining %d lists...", len(lists)) - for _, list := range pruneLists(lists, 0) { - result = append(result, unPtr(list)) + a.appendPendingLists() + return a.blocks, nil +} + +type listArranger struct { + blocks []interface{} + lists []types.List + blanklineCounter int + withinDelimitedBlock bool +} + +// an block which is not a list item was found. +// the first thing to do is to process the pending list items, +// then only append this block to the result +func (a *listArranger) appendBlock(block interface{}) { + if len(a.lists) > 0 { + log.Debugf("appending %d lists before processing element of type %T", len(a.lists), block) + a.pruneLists(0) + for _, list := range a.lists { + a.blocks = append(a.blocks, unPtr(list)) } + // reset the list for further usage while processing the rest of the document + a.lists = []types.List{} } - return result, nil + a.blocks = append(a.blocks, block) } -func rearrangeListItemsInBlocks(blocks []interface{}) ([]interface{}, error) { - // process and replace the elements within this delimited block - blocks, err := rearrangeListItems(blocks, true) - if err != nil { - return nil, errors.Wrapf(err, "unable to rearrange list items in delimited block") - } - // if len(lists) > 0 { - // switch list := lists[0].(type) { // just add the top-level list - // case *types.OrderedList: - // result = append(result, *list) - // case *types.UnorderedList: - // result = append(result, *list) - // case *types.LabeledList: - // result = append(result, *list) - // case *types.CalloutList: - // result = append(result, *list) - // } - // // reset the list for further usage while processing the rest of the document - // lists = []types.List{} - // } - return blocks, nil +func (a *listArranger) appendList(list types.List) { + a.lists = append(a.lists, list) } -func unPtr(value interface{}) interface{} { - v := reflect.ValueOf(value) - k := v.Kind() - if k == reflect.Ptr && v.Elem().IsValid() { - return v.Elem().Interface() +func (a *listArranger) appendBlankline(l types.BlankLine) { + // blank lines are not part of the resulting Document sections (or top-level), but they are part of the delimited blocks + // in some cases, they can also be used to split lists apart (when the next item has some attributes, + // or if the next block is a comment) + if a.withinDelimitedBlock && len(a.lists) == 0 { // only retain blank lines if within a delimited block, but not currently dealing with a list (or a set of nested lists) + a.appendBlock(l) } - return value + a.blanklineCounter++ +} + +func (a *listArranger) appendContinuedListItemElement(item types.ContinuedListItemElement) { + item.Offset = a.blanklineCounter + a.pruneLists(len(a.lists) - 1 - item.Offset) + log.Debugf("appending continued list item element with offset=%d (depth=%d)", item.Offset, len(a.lists)) + // lookup the list at which the item should be attached + parentList := &(a.lists[len(a.lists)-1]) + parentItem := (*parentList).LastItem() + parentItem.AddElement(item.Element) + a.blanklineCounter = 0 } -func appendListItem(lists []types.List, item interface{}) ([]types.List, error) { +func (a *listArranger) appendListItem(item interface{}) error { + a.blanklineCounter = 0 switch item := item.(type) { case types.OrderedListItem: - return appendOrderedListItem(lists, &item) + return a.appendOrderedListItem(&item) case types.UnorderedListItem: - return appendUnorderedListItem(lists, &item) + return a.appendUnorderedListItem(&item) case types.LabeledListItem: - return appendLabeledListItem(lists, item) + return a.appendLabeledListItem(item) case types.CalloutListItem: - return appendCalloutListItem(lists, item) + return a.appendCalloutListItem(item) } - return lists, nil + return nil } -func appendOrderedListItem(lists []types.List, item *types.OrderedListItem) ([]types.List, error) { +func (a *listArranger) appendPendingLists() { + if len(a.lists) > 0 { + log.Debugf("processing the remaining %d lists...", len(a.lists)) + a.pruneLists(0) + for _, list := range a.lists { + a.blocks = append(a.blocks, unPtr(list)) + } + a.lists = []types.List{} + } +} + +func (a *listArranger) appendOrderedListItem(item *types.OrderedListItem) error { maxLevel := 0 log.Debugf("looking-up list for ordered list having items with level=%d and number style=%v", item.Level, item.Style) - for i, list := range lists { + for i, list := range a.lists { if list, ok := list.(*types.OrderedList); ok { // assume we can't have empty lists maxLevel++ if list.Items[0].Style == item.Style { log.Debugf("found a matching ordered list at level %d", list.Items[0].Level) // prune items of "deeper/lower" level - lists = pruneLists(lists, i) + a.pruneLists(i) // apply the same level item.Level = list.Items[0].Level list.AddItem(*item) // also, prune the pointers to the remaining sublists (in case there is any...) - return lists, nil + return nil } } } + // no match found: create a new list and if needed, adjust the level of the item // force the current item level to (last seen level + 1) item.Level = maxLevel + 1 - // no match found: create a new list and if needed, adjust the level of the item log.Debugf("adding a new ordered list") - list := types.NewOrderedList(item) - // also, force the current item level to (last seen level + 1) - item.Level = maxLevel + 1 - return append(lists, list), nil + a.appendList(types.NewOrderedList(item)) + return nil } -func appendCalloutListItem(lists []types.List, item types.CalloutListItem) ([]types.List, error) { - for i, list := range lists { +func (a *listArranger) appendCalloutListItem(item types.CalloutListItem) error { + for i, list := range a.lists { if list, ok := list.(*types.CalloutList); ok { // assume we can't have empty lists log.Debugf("found a matching callout list") // prune items of "deeper/lower" level - lists = pruneLists(lists, i) + a.pruneLists(i) // apply the same level list.AddItem(item) // also, prune the pointers to the remaining sublists (in case there is any...) - return lists, nil + return nil } } // no match found: create a new list and if needed, adjust the level of the item log.Debugf("adding a new callout list") - list := types.NewCalloutList(item) - // also, force the current item level to (last seen level + 1) - return append(lists, list), nil + a.appendList(types.NewCalloutList(item)) + return nil } -func appendUnorderedListItem(lists []types.List, item *types.UnorderedListItem) ([]types.List, error) { +func (a *listArranger) appendUnorderedListItem(item *types.UnorderedListItem) error { maxLevel := 0 log.Debugf("looking-up list for unordered list item with level=%d and bullet style=%v", item.Level, item.BulletStyle) - for i, list := range lists { + for i, list := range a.lists { if list, ok := list.(*types.UnorderedList); ok { // assume we can't have empty lists maxLevel++ if list.Items[0].BulletStyle == item.BulletStyle { log.Debugf("found a matching unordered list at level %d", list.Items[0].Level) // prune items of "deeper/lower" level - lists = pruneLists(lists, i) + a.pruneLists(i) // apply the same level item.Level = list.Items[0].Level list.AddItem(*item) - return lists, nil + return nil } } } @@ -210,32 +204,32 @@ func appendUnorderedListItem(lists []types.List, item *types.UnorderedListItem) // also, force the current item level to (last seen level + 1) item.Level = maxLevel + 1 // also, force the bullet-style based on the list on the level above (if it exists) - if len(lists) > 0 { - parentList := &(lists[len(lists)-1]) + if len(a.lists) > 0 { + parentList := &(a.lists[len(a.lists)-1]) parentItem := (*parentList).LastItem() // also, force the bullet style if parentItem, ok := parentItem.(*types.UnorderedListItem); ok { item.BulletStyle = item.BulletStyle.NextLevel(parentItem.BulletStyle) } } - list := types.NewUnorderedList(item) - return append(lists, list), nil + a.appendList(types.NewUnorderedList(item)) + return nil } -func appendLabeledListItem(lists []types.List, item types.LabeledListItem) ([]types.List, error) { +func (a *listArranger) appendLabeledListItem(item types.LabeledListItem) error { // first, let's parse the labeled list item term for more complex content if len(item.Term) == 1 { if term, ok := item.Term[0].(types.StringElement); ok { var err error item.Term, err = parseLabeledListItemTerm(term.Content) if err != nil { - return nil, err + return err } } } maxLevel := 0 log.Debugf("looking-up list for labeled list item with level=%d and term=%s", item.Level, item.Term) - for i, list := range lists { + for i, list := range a.lists { log.Debugf(" comparing with list of type %T at level %d", list, i) if list, ok := list.(*types.LabeledList); ok { // assume we can't have empty lists @@ -243,10 +237,10 @@ func appendLabeledListItem(lists []types.List, item types.LabeledListItem) ([]ty log.Debugf(" comparing with list item level %d vs %d", list.Items[0].Level, item.Level) if list.Items[0].Level == item.Level { log.Debugf("found a matching labeled list") - lists = pruneLists(lists, i) + a.pruneLists(i) list.AddItem(item) log.Debugf("labeled list at level %d now has %d items", maxLevel, len(list.Items)) - return lists, nil + return nil } } } @@ -254,8 +248,8 @@ func appendLabeledListItem(lists []types.List, item types.LabeledListItem) ([]ty log.Debugf("adding a new labeled list") // also, force the current item level to (last seen level + 1) item.Level = maxLevel + 1 - list := types.NewLabeledList(item) - return append(lists, list), nil + a.appendList(types.NewLabeledList(item)) + return nil } // a labeled list item term may contain links, images, quoted text, footnotes, etc. @@ -270,26 +264,16 @@ func parseLabeledListItemTerm(term string) ([]interface{}, error) { return result, nil } -func appendContinuedListItemElement(lists []types.List, item types.ContinuedListItemElement) []types.List { - lists = pruneLists(lists, len(lists)-1-item.Offset) - log.Debugf("appending continued list item element with offset=%d (depth=%d)", item.Offset, len(lists)) - // lookup the list at which the item should be attached - parentList := &(lists[len(lists)-1]) - parentItem := (*parentList).LastItem() - parentItem.AddElement(item.Element) - return lists -} - -func pruneLists(lists []types.List, level int) []types.List { - if level+1 < len(lists) { - log.Debugf("pruning the list path from %d to %d level(s) deep", len(lists), level+1) +func (a *listArranger) pruneLists(level int) { + if level+1 < len(a.lists) { + log.Debugf("pruning the list path from %d to %d level(s) deep", len(a.lists), level+1) // add the last list(s) as children of their parent, in reverse order, // because we copy the value, not the pointers - for i := len(lists) - 1; i > level; i-- { + for i := len(a.lists) - 1; i > level; i-- { log.Debugf("appending list at depth %d to the last item of the parent list...", (i + 1)) - parentList := &(lists[i-1]) + parentList := &(a.lists[i-1]) parentItem := (*parentList).LastItem() - switch childList := lists[i].(type) { + switch childList := a.lists[i].(type) { case *types.OrderedList: parentItem.AddElement(*childList) case *types.UnorderedList: @@ -299,7 +283,15 @@ func pruneLists(lists []types.List, level int) []types.List { } } // also, prune the pointers to the remaining sublists - return lists[0 : level+1] + a.lists = a.lists[0 : level+1] + } +} + +func unPtr(value interface{}) interface{} { + v := reflect.ValueOf(value) + k := v.Kind() + if k == reflect.Ptr && v.Elem().IsValid() { + return v.Elem().Interface() } - return lists + return value } diff --git a/pkg/parser/document_processing_rearrange_lists_test.go b/pkg/parser/document_processing_rearrange_lists_test.go index 374f8277..84e16918 100644 --- a/pkg/parser/document_processing_rearrange_lists_test.go +++ b/pkg/parser/document_processing_rearrange_lists_test.go @@ -500,12 +500,9 @@ var _ = Describe("rearrange lists", func() { Expect(rearrangeListItems(actual, false)).To(Equal(expected)) }) - It("callout list with rich terms", func() { + It("callout list items and a block afterwards", func() { actual := []interface{}{ types.CalloutListItem{ - Attributes: types.Attributes{ - types.AttrTitle: "callout title", - }, Elements: []interface{}{ types.Paragraph{ Lines: [][]interface{}{ @@ -527,12 +524,22 @@ var _ = Describe("rearrange lists", func() { }, }, }, + types.ExampleBlock{ + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "foo", + }, + }, + }, + }, + }, + }, } expected := []interface{}{ types.CalloutList{ - Attributes: types.Attributes{ - types.AttrTitle: "callout title", - }, Items: []types.CalloutListItem{ { Elements: []interface{}{ @@ -558,6 +565,553 @@ var _ = Describe("rearrange lists", func() { }, }, }, + types.ExampleBlock{ + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "foo", + }, + }, + }, + }, + }, + }, + } + Expect(rearrangeListItems(actual, false)).To(Equal(expected)) + }) + + It("unordered list items and continued list item attached to grandparent", func() { + // * grandparent list item + // ** parent list item + // *** child list item + // + // + // + + // paragraph attached to parent list item + + actual := []interface{}{ + types.UnorderedListItem{ + Level: 1, + BulletStyle: types.OneAsterisk, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "grandparent list item"}, + }, + }, + }, + }, + }, + types.UnorderedListItem{ + Level: 2, + BulletStyle: types.TwoAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "parent list item"}, + }, + }, + }, + }, + }, + types.UnorderedListItem{ + Level: 3, + BulletStyle: types.ThreeAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "child list item"}, + }, + }, + }, + }, + }, + types.BlankLine{}, + types.BlankLine{}, + types.ContinuedListItemElement{ + Element: types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "paragraph attached to grandparent list item"}, + }, + }, + }, + }, + } + expected := []interface{}{ + types.UnorderedList{ + Items: []types.UnorderedListItem{ + { + Level: 1, + BulletStyle: types.OneAsterisk, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "grandparent list item"}, + }, + }, + }, + types.UnorderedList{ + Items: []types.UnorderedListItem{ + { + Level: 2, + BulletStyle: types.TwoAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "parent list item"}, + }, + }, + }, + types.UnorderedList{ + Items: []types.UnorderedListItem{ + { + Level: 3, + BulletStyle: types.ThreeAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "child list item"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "paragraph attached to grandparent list item"}, + }, + }, + }, + }, + }, + }, + }, + } + Expect(rearrangeListItems(actual, false)).To(Equal(expected)) + }) + + It("unordered list items and continued list item attached to parent", func() { + // * grandparent list item + // ** parent list item + // *** child list item + // + // + + // paragraph attached to parent list item + + actual := []interface{}{ + types.UnorderedListItem{ + Level: 1, + BulletStyle: types.OneAsterisk, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "grandparent list item"}, + }, + }, + }, + }, + }, + types.UnorderedListItem{ + Level: 2, + BulletStyle: types.TwoAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "parent list item"}, + }, + }, + }, + }, + }, + types.UnorderedListItem{ + Level: 3, + BulletStyle: types.ThreeAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "child list item"}, + }, + }, + }, + }, + }, + types.BlankLine{}, + types.ContinuedListItemElement{ + Element: types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "paragraph attached to parent list item"}, + }, + }, + }, + }, + } + expected := []interface{}{ + types.UnorderedList{ + Items: []types.UnorderedListItem{ + { + Level: 1, + BulletStyle: types.OneAsterisk, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "grandparent list item"}, + }, + }, + }, + types.UnorderedList{ + Items: []types.UnorderedListItem{ + { + Level: 2, + BulletStyle: types.TwoAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "parent list item"}, + }, + }, + }, + types.UnorderedList{ + Items: []types.UnorderedListItem{ + { + Level: 3, + BulletStyle: types.ThreeAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "child list item"}, + }, + }, + }, + }, + }, + }, + }, + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "paragraph attached to parent list item"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + Expect(rearrangeListItems(actual, false)).To(Equal(expected)) + }) + + It("unordered list items and continued list item attached to parent", func() { + // * grandparent list item + // ** parent list item + // *** child list item + // + // + + // paragraph attached to parent list item + + actual := []interface{}{ + types.UnorderedListItem{ + Level: 1, + BulletStyle: types.OneAsterisk, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "grandparent list item"}, + }, + }, + }, + }, + }, + types.UnorderedListItem{ + Level: 2, + BulletStyle: types.TwoAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "parent list item"}, + }, + }, + }, + }, + }, + types.UnorderedListItem{ + Level: 3, + BulletStyle: types.ThreeAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "child list item"}, + }, + }, + }, + }, + }, + types.BlankLine{}, + types.ContinuedListItemElement{ + Element: types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "paragraph attached to parent list item"}, + }, + }, + }, + }, + } + expected := []interface{}{ + types.UnorderedList{ + Items: []types.UnorderedListItem{ + { + Level: 1, + BulletStyle: types.OneAsterisk, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "grandparent list item"}, + }, + }, + }, + types.UnorderedList{ + Items: []types.UnorderedListItem{ + { + Level: 2, + BulletStyle: types.TwoAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "parent list item"}, + }, + }, + }, + types.UnorderedList{ + Items: []types.UnorderedListItem{ + { + Level: 3, + BulletStyle: types.ThreeAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "child list item"}, + }, + }, + }, + }, + }, + }, + }, + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "paragraph attached to parent list item"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + Expect(rearrangeListItems(actual, false)).To(Equal(expected)) + }) + + It("unordered list items and continued list item attached to parent and grandparent", func() { + // * grandparent list item + // ** parent list item + // *** child list item + // + // + + // paragraph attached to parent list item + // + // + + // paragraph attached to grandparent list item + + actual := []interface{}{ + types.UnorderedListItem{ + Level: 1, + BulletStyle: types.OneAsterisk, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "grandparent list item"}, + }, + }, + }, + }, + }, + types.UnorderedListItem{ + Level: 2, + BulletStyle: types.TwoAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "parent list item"}, + }, + }, + }, + }, + }, + types.UnorderedListItem{ + Level: 3, + BulletStyle: types.ThreeAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "child list item"}, + }, + }, + }, + }, + }, + types.BlankLine{}, + types.ContinuedListItemElement{ + Element: types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "paragraph attached to parent list item"}, + }, + }, + }, + }, + types.BlankLine{}, + types.ContinuedListItemElement{ + Element: types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "paragraph attached to grandparent list item"}, + }, + }, + }, + }, + } + expected := []interface{}{ + types.UnorderedList{ + Items: []types.UnorderedListItem{ + { + Level: 1, + BulletStyle: types.OneAsterisk, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "grandparent list item"}, + }, + }, + }, + types.UnorderedList{ + Items: []types.UnorderedListItem{ + { + Level: 2, + BulletStyle: types.TwoAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "parent list item"}, + }, + }, + }, + types.UnorderedList{ + Items: []types.UnorderedListItem{ + { + Level: 3, + BulletStyle: types.ThreeAsterisks, + CheckStyle: types.NoCheck, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "child list item"}, + }, + }, + }, + }, + }, + }, + }, + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "paragraph attached to parent list item"}, + }, + }, + }, + }, + }, + }, + }, + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{Content: "paragraph attached to grandparent list item"}, + }, + }, + }, + }, + }, + }, + }, } Expect(rearrangeListItems(actual, false)).To(Equal(expected)) }) diff --git a/pkg/parser/unordered_list_test.go b/pkg/parser/unordered_list_test.go index b70eba50..9d66ff9d 100644 --- a/pkg/parser/unordered_list_test.go +++ b/pkg/parser/unordered_list_test.go @@ -1372,13 +1372,13 @@ another delimited block Context("attach to ancestor", func() { It("attach to grandparent item", func() { - source := `* grand parent list item + source := `* grandparent list item ** parent list item *** child list item + -paragraph attached to grand parent list item` +paragraph attached to grandparent list item` expected := types.DraftDocument{ Elements: []interface{}{ types.UnorderedListItem{ @@ -1389,7 +1389,7 @@ paragraph attached to grand parent list item` types.Paragraph{ Lines: [][]interface{}{ { - types.StringElement{Content: "grand parent list item"}, + types.StringElement{Content: "grandparent list item"}, }, }, }, @@ -1430,7 +1430,7 @@ paragraph attached to grand parent list item` Element: types.Paragraph{ Lines: [][]interface{}{ { - types.StringElement{Content: "paragraph attached to grand parent list item"}, + types.StringElement{Content: "paragraph attached to grandparent list item"}, }, }, }, @@ -2953,13 +2953,13 @@ another delimited block Context("attach to ancestor", func() { It("attach to grandparent item", func() { - source := `* grand parent list item + source := `* grandparent list item ** parent list item *** child list item + -paragraph attached to grand parent list item` +paragraph attached to grandparent list item` expected := types.Document{ Elements: []interface{}{ types.UnorderedList{ @@ -2972,7 +2972,7 @@ paragraph attached to grand parent list item` types.Paragraph{ Lines: [][]interface{}{ { - types.StringElement{Content: "grand parent list item"}, + types.StringElement{Content: "grandparent list item"}, }, }, }, @@ -3015,7 +3015,7 @@ paragraph attached to grand parent list item` types.Paragraph{ Lines: [][]interface{}{ { - types.StringElement{Content: "paragraph attached to grand parent list item"}, + types.StringElement{Content: "paragraph attached to grandparent list item"}, }, }, }, diff --git a/pkg/renderer/sgml/html5/delimited_block_source_test.go b/pkg/renderer/sgml/html5/delimited_block_source_test.go index 475cb77d..d4b88fa2 100644 --- a/pkg/renderer/sgml/html5/delimited_block_source_test.go +++ b/pkg/renderer/sgml/html5/delimited_block_source_test.go @@ -180,8 +180,53 @@ printf("Hello world!\n"); // <1> ` Expect(RenderHTML(source)).To(MatchHTML(expected)) }) - It("with callouts and syntax highlighting", func() { - source := `[source,java] + + It("with callout and admonition block afterwards", func() { + source := `[source] +---- +const cookies = "cookies" <1> +---- +<1> a constant + +[NOTE] +==== +a note +====` + + expected := `
+
+
const cookies = "cookies" (1)
+
+
+
+
    +
  1. +

    a constant

    +
  2. +
+
+
+ + + + + +
+
Note
+
+
+

a note

+
+
+
+` + Expect(RenderHTML(source)).To(MatchHTML(expected)) + }) + + Context("with syntax highlighting", func() { + + It("with callouts and syntax highlighting", func() { + source := `[source,java] ---- @QuarkusTest public class GreetingResourceTest { @@ -205,7 +250,7 @@ public class GreetingResourceTest { ---- <1> We need to use the @RestClient CDI qualifier, since Quarkus creates the GreetingService bean with this qualifier. ` - expected := `
+ expected := `
@QuarkusTest
 public class GreetingResourceTest {
@@ -236,24 +281,22 @@ public class GreetingResourceTest {
 
 
` - Expect(RenderHTML(source)).To(MatchHTML(expected)) - }) - - Context("with syntax highlighting", func() { + Expect(RenderHTML(source)).To(MatchHTML(expected)) + }) It("should render source block with go syntax only", func() { source := `:source-highlighter: pygments - + [source,go] ---- type Foo struct{ - Field string + Field string } ----` expected := `
type Foo struct{
-	Field string
+    Field string
 }
@@ -263,17 +306,17 @@ type Foo struct{ It("should render source block without highlighter when language is not set", func() { source := `:source-highlighter: pygments - + [source] ---- type Foo struct{ - Field string + Field string } ----` expected := `
type Foo struct{
-	Field string
+    Field string
 }
@@ -283,17 +326,17 @@ type Foo struct{ It("should render source block without highlighter when language is not set", func() { source := `:source-highlighter: pygments - + [source] ---- type Foo struct{ - Field string + Field string } ----` expected := `
type Foo struct{
-	Field string
+    Field string
 }
diff --git a/pkg/types/types.go b/pkg/types/types.go index 83b3c617..02d52053 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -1493,6 +1493,11 @@ type BlockWithLineSubstitution interface { // Delimited blocks // ------------------------------------------ +// BlockOfBlocks a delimited block which may contain other blocks +type BlockOfBlocks interface { + +} + // ExampleBlock the structure for the example blocks type ExampleBlock struct { Attributes Attributes