package cnm import ( "io" "strings" ) func init() { RegisterTextContentParser("", parseTextPlain) RegisterTextContentParser("plain", parseTextPlain) RegisterTextContentParser("pre", parseTextPre) } // Block represents an arbitrary CNM within the "content" top-level block. type Block interface { // Name returns the name of the block. Name() string // Args returns the block arguments. Args() []string WriteIndent(w io.Writer, n int) error } // ContainerBlock represents a content block that contains only other blocks. type ContainerBlock interface { Block // Children returns the child blocks. Children() []Block } // ContentBlock represents a block that holds other content blocks. type ContentBlock struct { name string args []string children []Block } // WriteIndent writes the block header and its children indented by n tabs. func (cb *ContentBlock) WriteIndent(w io.Writer, n int) error { ss := []string{Escape(cb.name)} ss = append(ss, cb.args...) if err := WriteIndent(w, JoinEscape(ss), n); err != nil { return err } for _, ch := range cb.children { if err := ch.WriteIndent(w, n+1); err != nil { return err } } return nil } // NewContentBlock creates a new ContentBlock with a name and argument. func NewContentBlock(name string, args ...string) *ContentBlock { var a []string for _, arg := range args { if arg != "" { a = append(a, arg) } } return &ContentBlock{name: name, args: a} } // Name returns the block's name. func (cb *ContentBlock) Name() string { return cb.name } // Args returns the block arguments. func (cb *ContentBlock) Args() []string { return cb.args } // Children returns the block's child blocks. func (cb *ContentBlock) Children() []Block { return cb.children } // AppendChild adds a new child block to the end of the list of children. func (cb *ContentBlock) AppendChild(block Block) { cb.children = append(cb.children, block) } func (cb *ContentBlock) parse(p *Parser, block *TokenBlock) (err error) { for err == nil { if !p.Empty() && p.Indent() <= block.Indent() { break } token := p.Block() if blk, ok := token.(*TokenBlock); ok { var b Block switch blk.Name { case "section": b, err = parseContentSection(p, blk) case "text": b, err = parseContentText(p, blk) case "raw": b, err = parseContentRaw(p, blk) case "list": b, err = parseContentList(p, blk) case "table": b, err = parseContentTable(p, blk) case "embed": b, err = parseContentEmbed(p, blk) default: err = parseUnknown(p, blk) } if b != nil { cb.AppendChild(b) } } else if err = p.Next(); err != nil { break } } return } // SectionBlock represents a "section" content block. type SectionBlock struct { ContentBlock } // NewSectionBlock creates a new SectionBlock with a title. func NewSectionBlock(title string) *SectionBlock { return &SectionBlock{*NewContentBlock("section", title)} } // Title returns the section block's title. func (b *SectionBlock) Title() string { return strings.Join(b.args, " ") } func parseContentSection(p *Parser, block *TokenBlock) (*SectionBlock, error) { sec := NewSectionBlock(strings.Join(block.Args, " ")) if err := p.Next(); err != nil { return sec, err } return sec, sec.parse(p, block) } // TextBlock represents a "text" content block. type TextBlock struct { // Format is the text format (first word of the block argument). Format string // Contents are the text contents. Contents TextContents } // NewTextBlock creates a new TextBlock containing arbitrary text contents. func NewTextBlock(format string, contents TextContents) *TextBlock { return &TextBlock{format, contents} } // Name returns the block name "text". func (t *TextBlock) Name() string { return "text" } // Args returns the block's arguments (format). func (t *TextBlock) Args() []string { if t.Format == "" { return nil } return []string{t.Format} } // WriteIndent writes the block header and its content indented by n tabs. func (t *TextBlock) WriteIndent(w io.Writer, n int) error { s := t.Name() if t.Format != "" { s += " " + Escape(t.Format) } if err := WriteIndent(w, s, n); err != nil { return err } if err := t.Contents.WriteIndent(w, n+1); err != nil { return err } return nil } func parseContentText(p *Parser, block *TokenBlock) (*TextBlock, error) { format := "" if len(block.Args) >= 1 { format = block.Args[0] } tb := NewTextBlock(format, nil) if err := p.Next(); err != nil { return tb, err } var err error tb.Contents, err = parseTextFormat(p, block, tb.Format) return tb, err } func parseTextFormat(p *Parser, block *TokenBlock, format string) (TextContents, error) { if parser := GetTextContentParser(format); parser != nil { return parser(p, block) } return parseTextRaw(p, block) } // TextContents represents the textual contents of a text block. type TextContents interface { WriteIndent(w io.Writer, n int) error } // TextContentParser parses text content in a text block. type TextContentParser func(p *Parser, block *TokenBlock) (TextContents, error) var textContentParsers = map[string]TextContentParser{} // GetTextContentParser retrieves a text content parser or nil if it doesn't // exist. func GetTextContentParser(name string) TextContentParser { return textContentParsers[name] } // RegisterTextContentParser registers a new text content parser for a format. func RegisterTextContentParser(name string, parser TextContentParser) { if parser == nil { delete(textContentParsers, name) } else { textContentParsers[name] = parser } } // TextPlainContents represents a list of simple text paragraphs. type TextPlainContents struct { // Paragraphs is a list of simple text paragraphs. Paragraphs []string } // WriteIndent writes the plain text content indented by n tabs. func (t TextPlainContents) WriteIndent(w io.Writer, n int) error { for i, p := range t.Paragraphs { if i != 0 { if err := WriteIndent(w, "", 0); err != nil { return err } } if err := WriteIndent(w, Escape(p), n); err != nil { return err } } return nil } // NewTextPlainBlock creates a new TextBlock containing TextPlainContents. func NewTextPlainBlock(paragraphs []string) *TextBlock { par := make([]string, len(paragraphs)) copy(par, paragraphs) return NewTextBlock("", TextPlainContents{par}) } func parseTextPlain(p *Parser, block *TokenBlock) (TextContents, error) { txt := TextPlainContents{} paragraph := "" var err error for err == nil { if !p.Empty() && p.Indent() <= block.Indent() { break } token := p.SimpleText() end := false if text, ok := token.(*TokenSimpleText); ok { if text.Text == "" { end = true } else if paragraph == "" { paragraph = text.Text } else { paragraph += " " + text.Text } } else if _, ok := token.(*TokenEmptyLine); ok && paragraph != "" { end = true } if end { txt.Paragraphs = append(txt.Paragraphs, paragraph) paragraph = "" } err = p.Next() } if paragraph != "" { txt.Paragraphs = append(txt.Paragraphs, paragraph) } return txt, err } // TextPreContents represents preformatted contents of a text block. type TextPreContents struct { // Text is the preformatted content. Text string } // WriteIndent writes the preformatted text content indented by n tabs. func (t TextPreContents) WriteIndent(w io.Writer, n int) error { ss := strings.Split(t.Text, "\n") for _, s := range ss { if err := WriteIndent(w, EscapeNonspace(s), n); err != nil { return err } } return nil } // NewTextPreBlock creates a new TextBlock containing TextPreContents. func NewTextPreBlock(text string) *TextBlock { return NewTextBlock("", TextPreContents{text}) } func parseTextPre(p *Parser, block *TokenBlock) (TextContents, error) { var lines []string var ls []string var err error for err == nil { if !p.Empty() && p.Indent() <= block.Indent() { break } token := p.RawText() if text, ok := token.(*TokenRawText); ok { if len(ls) > 0 { lines = append(lines, ls...) ls = ls[:0] } lines = append(lines, Unescape(text.Text)) } else if _, ok := token.(*TokenEmptyLine); ok && len(lines) > 0 { ls = append(ls, "") } err = p.Next() } return TextPreContents{strings.Join(lines, "\n")}, err } // TextRawContents represents raw contents of a text or raw block. type TextRawContents struct { // Text is the raw content. Text string } // WriteIndent writes the raw content indented by n tabs. func (t TextRawContents) WriteIndent(w io.Writer, n int) error { ss := strings.Split(t.Text, "\n") for _, s := range ss { if err := WriteIndent(w, s, n); err != nil { return err } } return nil } func parseTextRaw(p *Parser, block *TokenBlock) (TextContents, error) { var lines []string var ls []string var err error for err == nil { if !p.Empty() && p.Indent() <= block.Indent() { break } token := p.RawText() if text, ok := token.(*TokenRawText); ok { if len(ls) > 0 { lines = append(lines, ls...) ls = ls[:0] } lines = append(lines, text.Text) } else if _, ok := token.(*TokenEmptyLine); ok && len(lines) > 0 { ls = append(ls, "") } err = p.Next() } return TextRawContents{strings.Join(lines, "\n")}, err } // RawBlock represents a "raw" content block. type RawBlock struct { // Syntax is the syntax of the block contents (first word of block argument) Syntax string // Contents is the raw content. Contents TextRawContents } // NewRawBlock creates a new RawBlock. func NewRawBlock(syntax, contents string) *RawBlock { return &RawBlock{syntax, TextRawContents{contents}} } // Name returns the block name "raw". func (r *RawBlock) Name() string { return "raw" } // Args returns the block's arguments (syntax). func (r *RawBlock) Args() []string { if r.Syntax == "" { return nil } return []string{r.Syntax} } // WriteIndent writes the raw content indented by n tabs. func (r *RawBlock) WriteIndent(w io.Writer, n int) error { s := r.Name() if r.Syntax != "" { s += " " + Escape(r.Syntax) } if err := WriteIndent(w, s, n); err != nil { return err } r.Contents.WriteIndent(w, n+1) return nil } func parseContentRaw(p *Parser, block *TokenBlock) (*RawBlock, error) { arg := "" if len(block.Args) > 0 { arg = block.Args[0] } rb := &RawBlock{arg, TextRawContents{}} if err := p.Next(); err != nil { return rb, err } tc, err := parseTextRaw(p, block) if tc != nil { rb.Contents = tc.(TextRawContents) } return rb, err } // ListBlock represents a "list" content block. type ListBlock struct { ContentBlock } // NewListBlock creates a new ListBlock. // // If the ordered parameter is true, the list is created in "ordered" mode. func NewListBlock(ordered bool) *ListBlock { arg := "" if ordered { arg = "ordered" } return &ListBlock{*NewContentBlock("list", arg)} } // Ordered returns true if the list is in ordered mode (first word of the // block argument is "ordered"). func (b *ListBlock) Ordered() bool { return len(b.args) >= 1 && b.args[0] == "ordered" } func parseContentList(p *Parser, block *TokenBlock) (*ListBlock, error) { list := NewListBlock(false) list.args = block.Args if err := p.Next(); err != nil { return list, err } return list, list.parse(p, block) } // TableBlock represents a "table" content block. type TableBlock struct { rows []Block } // NewTableBlock creates a new TableBlock. func NewTableBlock() *TableBlock { return &TableBlock{} } // Name returns the block name "table". func (t *TableBlock) Name() string { return "table" } // Args returns the block's nil arguments. func (t *TableBlock) Args() []string { return nil } // WriteIndent writes the table header and contents indented by n tabs. func (t *TableBlock) WriteIndent(w io.Writer, n int) error { if err := WriteIndent(w, t.Name(), n); err != nil { return err } for _, row := range t.rows { if err := row.WriteIndent(w, n+1); err != nil { return err } } return nil } // Children returns the table's rows. func (t *TableBlock) Children() []Block { return t.rows } // AppendRow adds a new row to the end of the table. func (t *TableBlock) AppendRow(row Block) { t.rows = append(t.rows, row) } func (t *TableBlock) parse(p *Parser, block *TokenBlock) (err error) { for err == nil { if !p.Empty() && p.Indent() <= block.Indent() { break } token := p.Block() if blk, ok := token.(*TokenBlock); ok { var b Block switch blk.Name { case "row": b, err = parseTableRow(p, blk) case "header": b, err = parseTableHeader(p, blk) default: err = parseUnknown(p, blk) } if b != nil { t.AppendRow(b) } } else if err = p.Next(); err != nil { break } } return } func parseContentTable(p *Parser, block *TokenBlock) (*TableBlock, error) { table := NewTableBlock() if err := p.Next(); err != nil { return table, err } return table, table.parse(p, block) } // RowBlock represents a "row" table block. type RowBlock struct { ContentBlock } // NewRowBlock creates a new RowBlock. func NewRowBlock() *RowBlock { return &RowBlock{*NewContentBlock("row", "")} } func parseTableRow(p *Parser, block *TokenBlock) (*RowBlock, error) { row := NewRowBlock() if err := p.Next(); err != nil { return row, err } return row, row.parse(p, block) } // HeaderBlock represents a "header" table block. type HeaderBlock struct { ContentBlock } // NewHeaderBlock creates a new HeaderBlock. func NewHeaderBlock() *HeaderBlock { return &HeaderBlock{*NewContentBlock("header", "")} } func parseTableHeader(p *Parser, block *TokenBlock) (*HeaderBlock, error) { hdr := NewHeaderBlock() if err := p.Next(); err != nil { return hdr, err } return hdr, hdr.parse(p, block) } // EmbedBlock represents an "embed" content block. type EmbedBlock struct { // Type is the content type (first word of block argument). Type string // URL is the content URL (second word of the block argument). URL string // Description is the content description (block body as simple text). Description string } // NewEmbedBlock creates a new EmbedBlock. func NewEmbedBlock(typ, url, desc string) *EmbedBlock { return &EmbedBlock{typ, url, desc} } // Name returns the block name "embed". func (e *EmbedBlock) Name() string { return "embed" } // Args returns the block argument (type and URL). func (e *EmbedBlock) Args() []string { if e.Type != "" && e.URL != "" { return []string{e.Type, e.URL} } return []string{e.Type} } // WriteIndent writes the embed block header and contents indented by n tabs. func (e *EmbedBlock) WriteIndent(w io.Writer, n int) error { if e.URL == "" { return nil } s := e.Name() + " " if e.Type == "" { s += "*/*" } else { s += Escape(e.Type) } s += " " + Escape(e.URL) if err := WriteIndent(w, s, n); err != nil { return err } if err := WriteIndent(w, Escape(e.Description), n+1); err != nil { return err } return nil } func parseContentEmbed(p *Parser, block *TokenBlock) (*EmbedBlock, error) { embed := &EmbedBlock{} if len(block.Args) >= 1 { embed.Type = block.Args[0] if len(block.Args) >= 2 { embed.URL = block.Args[1] } } if err := p.Next(); err != nil { return embed, err } s, err := getSimpleText(p, block) embed.Description = s return embed, err }