package cnm import ( "reflect" "strings" "testing" ) var parseTests = map[string]*Document{ "": &Document{}, "foo\n\tbar\ntitle\n\ttest": &Document{ Title: "test", }, "foo\n\tbar\ntitle\n\ttest\nfoo\n\tbar": &Document{ Title: "test", }, "title\n\ttest": &Document{ Title: "test", }, "\ntitle\n\ttest\n": &Document{ Title: "test", }, "title\n\ttest\n\n": &Document{ Title: "test", }, "\ntitle\n\t\t\t\t\t\n\t\tfoo bar": &Document{ Title: "foo bar", }, "site\n\tfoo\nsite\n\t\tbar\n": &Document{ Site: Site{Children: []Site{ Site{Path: "foo"}, }}, }, "content\n\ttext\n\t\tfoo\ncontent\n\t\tbar\n": &Document{ Content: &ContentBlock{ name: "content", children: []Block{ &TextBlock{ Format: "", Contents: TextPlainContents{ Paragraphs: []string{"foo"}, }, }, }, }, }, "\n\ttitle\n\t\t\t\t\t\n\tfoo bar": &Document{}, "\ttitle\n\t\tfoo\n": &Document{}, "\tsite\n\t\tfoo\n": &Document{}, "\tlinks\n\t\tfoo\n": &Document{}, "links\n\tfoo": &Document{ Links: []Link{ Link{ URL: "foo", }, }, }, "qwe\ntitle\n\tasd": &Document{ Title: "asd", }, "links\n\t qwe\n\tasd": &Document{ Links: []Link{ Link{URL: "asd"}, }, }, "site\n\t qwe\n\tasd": &Document{ Site: Site{Children: []Site{ Site{Path: "asd"}, }}, }, "site\n\tba\\nr": &Document{ Site: Site{ Children: []Site{ Site{ Path: "ba\nr", }, }, }, }, "site\n\t\t\t\tba\\nr": &Document{}, "site\n\tfoo\tbar": &Document{ Site: Site{ Children: []Site{ Site{ Path: "foo", Name: "bar", }, }, }, }, "\t\tsite\n\t\t\t\tfoo": &Document{}, "\tsite\n\tbar": &Document{}, "content\n\tsection test\n": &Document{ Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: []string{"test"}, }}, }, }, }, "content\n\ttext __nonexistent__\n\t\tqwe zxc\n\n\t\tasd\\n123": &Document{ Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &TextBlock{ Format: "__nonexistent__", Contents: TextRawContents{"qwe zxc\n\nasd\\n123"}, }, }, }, }, "content\n\tnosuchblock\n\tsection test\n": &Document{ Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: []string{"test"}, }}, }, }, }, "content\n\tnosuchblock\n\tsection test\n\n\tnosuchblock2": &Document{ Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: []string{"test"}, }}, }, }, }, "content\n\tsection\n\t\tnosuchblock\n\t\tsection\n\t\t\ttext\n\t\t\t\ttest": &Document{ Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: nil, children: []Block{ &TextBlock{ Format: "", Contents: TextPlainContents{Paragraphs: []string{ "test", }}, }, }, }}, }, }}, }, }, }, "content\n\ttable\n\t\tnosuchblock\n\t\trow\n\t\t\ttext\n\t\t\t\ttest": &Document{ Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &TableBlock{rows: []Block{ &RowBlock{ContentBlock{ name: "row", args: nil, children: []Block{ &TextBlock{ Format: "", Contents: TextPlainContents{Paragraphs: []string{ "test", }}, }, }, }}, }}, }, }, }, "site\n\t\ttest\n\tfoo\\ bar baz\n" + "links\n\tfoo\\ bar baz\n\t\t\tquux\n" + "content\n" + "links\n\t\ttest\n" + "content\n" + "\tsection\n\tsection qweasd\n" + "\tsection foo\\ bar baz\n\t\ttext\n\t\t\ttest\n": &Document{ Site: Site{ Children: []Site{ Site{ Path: "foo bar", Name: "baz", }, }, }, Links: []Link{ Link{ URL: "foo bar", Name: "baz", Description: "quux", }, }, Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: nil, }}, &SectionBlock{ContentBlock{ name: "section", args: []string{"qweasd"}, }}, &SectionBlock{ContentBlock{ name: "section", args: []string{"foo bar baz"}, children: []Block{ &TextBlock{ Contents: TextPlainContents{[]string{ "test", }}, }, }, }}, }, }, }, "title\n\tfoo bar\n" + "links\n\tqwe asd\n\tzxc 123\n" + "site\n\tfoo\n\t\tbar\n\t\tbaz/quux\n\t\t\t123\n" + "title\n\tbaz\n" + "links\n\tfoo\n" + "site\n\ttest": &Document{ Title: "foo bar baz", Links: []Link{ Link{"qwe", "asd", ""}, Link{"zxc", "123", ""}, Link{"foo", "", ""}, }, Site: Site{ Children: []Site{ Site{ Path: "foo", Children: []Site{ Site{Path: "bar"}, Site{ Path: "baz/quux", Children: []Site{ Site{Path: "123"}, }, }, }, }, Site{Path: "test"}, }, }, }, ` thing stuff whatever title blah Test title document content section Test section text This is \n just a text pre t e \n s t preformatted text raw text/plain of various \n features section of\ the table header text Column 1 text Column 2 row text CNM text document format row section lorem list text ipsum list ordered list unordered text dolor sit amet embed text/cnm cnp://example.com/ stuff thing whatever `: &Document{ Title: "Test document", Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: []string{"Test section"}, children: []Block{ &TextBlock{ Contents: TextPlainContents{[]string{ "This is \n just a", }}, }, &TextBlock{ Format: "pre", Contents: TextPreContents{ " t e \n s t \n\t\n\n\npreformatted text", }, }, &RawBlock{ Syntax: "text/plain", Contents: TextRawContents{"of various \\n features"}, }, &SectionBlock{ContentBlock{ name: "section", args: []string{"of the"}, children: []Block{ &TableBlock{[]Block{ &HeaderBlock{ContentBlock{ name: "header", args: nil, children: []Block{ &TextBlock{ Contents: TextPlainContents{[]string{ "Column 1", }}, }, &TextBlock{ Contents: TextPlainContents{[]string{ "Column 2", }}, }, }, }}, &RowBlock{ContentBlock{ name: "row", args: nil, children: []Block{ &TextBlock{ Contents: TextPlainContents{[]string{ "CNM", }}, }, &TextBlock{ Contents: TextPlainContents{[]string{ "document", "format", }}, }, }, }}, &RowBlock{ContentBlock{ name: "row", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: nil, }}, &ListBlock{ContentBlock{ name: "list", args: nil, children: []Block{ &TextBlock{ Contents: TextPlainContents{[]string{ "ipsum", }}, }, &ListBlock{ContentBlock{ name: "list", args: []string{"ordered"}, children: []Block{ &ListBlock{ContentBlock{ name: "list", args: []string{"unordered"}, children: []Block{ &TextBlock{ Contents: TextPlainContents{[]string{ "dolor", "sit amet", }}, }, }, }}, }, }}, }, }}, }, }}, }}, }, }}, }, }}, &EmbedBlock{ Type: "text/cnm", URL: "cnp://example.com/", Description: "thing whatever", }, }, }, }, } func TestParse(t *testing.T) { for k, v := range parseTests { t.Run(k, func(t *testing.T) { d, err := ParseDocument(strings.NewReader(k)) if err != nil { t.Fatalf("ParseDocument(%q): error: %v", k, err) } if !documentEqual(d, v) { t.Fatalf("ParseDocument(%q):\nexpected:\n%#v\n got:\n%#v\n\n%#v\n%#v", k, v, d, v.Content.Children()[0], d.Content.Children()[0]) } }) } } func documentEqual(a, b *Document) bool { if a.Title != b.Title { return false } if len(a.Links) != len(b.Links) { return false } for i := range a.Links { if !linkEqual(a.Links[i], b.Links[i]) { return false } } if !siteEqual(a.Site, b.Site) { return false } if !contentBlockEqual(a.Content, b.Content) { return false } return true } func linkEqual(a, b Link) bool { return a == b } func siteEqual(a, b Site) bool { if a.Path != b.Path { return false } if a.Name != b.Name { return false } if len(a.Children) != len(b.Children) { return false } for i := range a.Children { if !siteEqual(a.Children[i], b.Children[i]) { return false } } return true } func blockEqual(a, b Block) bool { switch va := a.(type) { case *SectionBlock: vb, ok := b.(*SectionBlock) if !ok { return false } return sectionBlockEqual(va, vb) case *TextBlock: vb, ok := b.(*TextBlock) if !ok { return false } return textBlockEqual(va, vb) case *RawBlock: vb, ok := b.(*RawBlock) if !ok { return false } return rawBlockEqual(va, vb) case *ListBlock: vb, ok := b.(*ListBlock) if !ok { return false } return listBlockEqual(va, vb) case *TableBlock: vb, ok := b.(*TableBlock) if !ok { return false } return tableBlockEqual(va, vb) case *HeaderBlock: vb, ok := b.(*HeaderBlock) if !ok { return false } return headerBlockEqual(va, vb) case *RowBlock: vb, ok := b.(*RowBlock) if !ok { return false } return rowBlockEqual(va, vb) case *EmbedBlock: vb, ok := b.(*EmbedBlock) if !ok { return false } return embedBlockEqual(va, vb) case *ContentBlock: vb, ok := b.(*ContentBlock) if !ok { return false } return contentBlockEqual(va, vb) default: // shouldn't happen return false } } func contentBlockEqual(a, b *ContentBlock) bool { if (a == nil) != (b == nil) { return false } if a == nil { return true } if a.Name() != b.Name() { return false } if !argsEqual(a.Args(), b.Args()) { return false } ca, cb := a.Children(), b.Children() if len(ca) != len(cb) { return false } for i := range ca { if !blockEqual(ca[i], cb[i]) { return false } } return true } func argsEqual(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true } func sectionBlockEqual(a, b *SectionBlock) bool { return a.Title() == b.Title() && contentBlockEqual(&a.ContentBlock, &b.ContentBlock) } func textBlockEqual(a, b *TextBlock) bool { if !argsEqual(a.Args(), b.Args()) { return false } return textContentsEqual(a.Contents, b.Contents) } func textContentsEqual(a, b TextContents) bool { switch va := a.(type) { case TextPlainContents: vb, ok := b.(TextPlainContents) if !ok { return false } return textPlainContentsEqual(va, vb) case TextPreContents: vb, ok := b.(TextPreContents) if !ok { return false } return textPreContentsEqual(va, vb) case TextRawContents: vb, ok := b.(TextRawContents) if !ok { return false } return textRawContentsEqual(va, vb) default: // handle unknown contents, e.g. text fmt return reflect.TypeOf(a) == reflect.TypeOf(b) && reflect.DeepEqual(a, b) } } func textPlainContentsEqual(a, b TextPlainContents) bool { if len(a.Paragraphs) != len(b.Paragraphs) { return false } for i := range a.Paragraphs { if a.Paragraphs[i] != b.Paragraphs[i] { return false } } return true } func textPreContentsEqual(a, b TextPreContents) bool { return a == b } func textRawContentsEqual(a, b TextRawContents) bool { return a.Text == b.Text } func rawBlockEqual(a, b *RawBlock) bool { return argsEqual(a.Args(), b.Args()) && textRawContentsEqual(a.Contents, b.Contents) } func listBlockEqual(a, b *ListBlock) bool { return a.Ordered() == b.Ordered() && contentBlockEqual(&a.ContentBlock, &b.ContentBlock) } func tableBlockEqual(a, b *TableBlock) bool { ra, rb := a.Children(), b.Children() if len(ra) != len(rb) { return false } for i := range ra { if !blockEqual(ra[i], rb[i]) { return false } } return argsEqual(a.Args(), b.Args()) } func rowBlockEqual(a, b *RowBlock) bool { return contentBlockEqual(&a.ContentBlock, &b.ContentBlock) } func headerBlockEqual(a, b *HeaderBlock) bool { return contentBlockEqual(&a.ContentBlock, &b.ContentBlock) } func embedBlockEqual(a, b *EmbedBlock) bool { return argsEqual(a.Args(), b.Args()) }