%% name = RDoc::Markdown %% header { # coding: UTF-8 # frozen_string_literal: true # :markup: markdown ## # RDoc::Markdown as described by the [markdown syntax][syntax]. # # To choose Markdown as your only default format see # RDoc::Options@Saved+Options for instructions on setting up a `.doc_options` # file to store your project default. # # ## Usage # # Here is a brief example of using this parse to read a markdown file by hand. # # data = File.read("README.md") # formatter = RDoc::Markup::ToHtml.new(RDoc::Options.new, nil) # html = RDoc::Markdown.parse(data).accept(formatter) # # # do something with html # # ## Extensions # # The following markdown extensions are supported by the parser, but not all # are used in RDoc output by default. # # ### RDoc # # The RDoc Markdown parser has the following built-in behaviors that cannot be # disabled. # # Underscores embedded in words are never interpreted as emphasis. (While the # [markdown dingus][dingus] emphasizes in-word underscores, neither the # Markdown syntax nor MarkdownTest mention this behavior.) # # For HTML output, RDoc always auto-links bare URLs. # # ### Break on Newline # # The break_on_newline extension converts all newlines into hard line breaks # as in [Github Flavored Markdown][GFM]. This extension is disabled by # default. # # ### CSS # # The #css extension enables CSS blocks to be included in the output, but they # are not used for any built-in RDoc output format. This extension is disabled # by default. # # Example: # # # # ### Definition Lists # # The definition_lists extension allows definition lists using the [PHP # Markdown Extra syntax][PHPE], but only one label and definition are supported # at this time. This extension is enabled by default. # # Example: # # ``` # cat # : A small furry mammal # that seems to sleep a lot # # ant # : A little insect that is known # to enjoy picnics # # ``` # # Produces: # # cat # : A small furry mammal # that seems to sleep a lot # # ant # : A little insect that is known # to enjoy picnics # # ### Strike # # Example: # # ``` # This is ~~striked~~. # ``` # # Produces: # # This is ~~striked~~. # # ### Github # # The #github extension enables a partial set of [Github Flavored Markdown] # [GFM]. This extension is enabled by default. # # Supported github extensions include: # # #### Fenced code blocks # # Use ` ``` ` around a block of code instead of indenting it four spaces. # # #### Syntax highlighting # # Use ` ``` ruby ` as the start of a code fence to add syntax highlighting. # (Currently only `ruby` syntax is supported). # # ### HTML # # Enables raw HTML to be included in the output. This extension is enabled by # default. # # Example: # # # ... #
# # ### Notes # # The #notes extension enables footnote support. This extension is enabled by # default. # # Example: # # Here is some text[^1] including an inline footnote ^[for short footnotes] # # ... # # [^1]: With the footnote text down at the bottom # # Produces: # # Here is some text[^1] including an inline footnote ^[for short footnotes] # # [^1]: With the footnote text down at the bottom # # ## Limitations # # * Link titles are not used # * Footnotes are collapsed into a single paragraph # # ## Author # # This markdown parser is a port to kpeg from [peg-markdown][pegmarkdown] by # John MacFarlane. # # It is used under the MIT license: # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # The port to kpeg was performed by Eric Hodel and Evan Phoenix # # [dingus]: http://daringfireball.net/projects/markdown/dingus # [GFM]: https://github.github.com/gfm/ # [pegmarkdown]: https://github.com/jgm/peg-markdown # [PHPE]: http://michelf.com/projects/php-markdown/extra/#def-list # [syntax]: http://daringfireball.net/projects/markdown/syntax #-- # Last updated to jgm/peg-markdown commit 8f8fc22ef0 } %% { require 'rdoc' require 'rdoc/markup/to_joined_paragraph' require 'rdoc/markdown/entities' require 'rdoc/markdown/literals' ## # Supported extensions EXTENSIONS = [] ## # Extensions enabled by default DEFAULT_EXTENSIONS = [ :definition_lists, :github, :html, :notes, :strike, ] # :section: Extensions ## # Creates extension methods for the `name` extension to enable and disable # the extension and to query if they are active. def self.extension name EXTENSIONS << name define_method "#{name}?" do extension? name end define_method "#{name}=" do |enable| extension name, enable end end ## # Converts all newlines into hard breaks extension :break_on_newline ## # Allow style blocks extension :css ## # Allow PHP Markdown Extras style definition lists extension :definition_lists ## # Allow Github Flavored Markdown extension :github ## # Allow HTML extension :html ## # Enables the notes extension extension :notes ## # Enables the strike extension extension :strike # :section: ## # Parses the `markdown` document into an RDoc::Document using the default # extensions. def self.parse markdown parser = new parser.parse markdown end # TODO remove when kpeg 0.10 is released alias orig_initialize initialize # :nodoc: ## # Creates a new markdown parser that enables the given +extensions+. def initialize extensions = DEFAULT_EXTENSIONS, debug = false @debug = debug @formatter = RDoc::Markup::ToJoinedParagraph.new @extensions = extensions @references = nil @unlinked_references = nil @footnotes = nil @note_order = nil end ## # Wraps `text` in emphasis for rdoc inline formatting def emphasis text if text =~ /\A[a-z\d.\/]+\z/i then "_#{text}_" else "#{text}" end end ## # :category: Extensions # # Is the extension `name` enabled? def extension? name @extensions.include? name end ## # :category: Extensions # # Enables or disables the extension with `name` def extension name, enable if enable then @extensions |= [name] else @extensions -= [name] end end ## # Parses `text` in a clone of this parser. This is used for handling nested # lists the same way as markdown_parser. def inner_parse text # :nodoc: parser = clone parser.setup_parser text, @debug parser.peg_parse doc = parser.result doc.accept @formatter doc.parts end ## # Finds a link reference for `label` and creates a new link to it with # `content` as the link text. If `label` was not encountered in the # reference-gathering parser pass the label and content are reconstructed # with the linking `text` (usually whitespace). def link_to content, label = content, text = nil raise ParseError, 'enable notes extension' if content.start_with? '^' and label.equal? content if ref = @references[label] then "{#{content}}[#{ref}]" elsif label.equal? content then "[#{content}]#{text}" else "[#{content}]#{text}[#{label}]" end end ## # Creates an RDoc::Markup::ListItem by parsing the `unparsed` content from # the first parsing pass. def list_item_from unparsed parsed = inner_parse unparsed.join RDoc::Markup::ListItem.new nil, *parsed end ## # Stores `label` as a note and fills in previously unknown note references. def note label #foottext = "rdoc-label:foottext-#{label}:footmark-#{label}" #ref.replace foottext if ref = @unlinked_notes.delete(label) @notes[label] = foottext #"{^1}[rdoc-label:footmark-#{label}:foottext-#{label}] " end ## # Creates a new link for the footnote `reference` and adds the reference to # the note order list for proper display at the end of the document. def note_for ref @note_order << ref label = @note_order.length "{*#{label}}[rdoc-label:foottext-#{label}:footmark-#{label}]" end ## # The internal kpeg parse method alias peg_parse parse # :nodoc: ## # Creates an RDoc::Markup::Paragraph from `parts` and including # extension-specific behavior def paragraph parts parts = parts.map do |part| if "\n" == part then RDoc::Markup::HardBreak.new else part end end if break_on_newline? RDoc::Markup::Paragraph.new(*parts) end ## # Parses `markdown` into an RDoc::Document def parse markdown @references = {} @unlinked_references = {} markdown += "\n\n" setup_parser markdown, @debug peg_parse 'References' if notes? then @footnotes = {} setup_parser markdown, @debug peg_parse 'Notes' # using note_order on the first pass would be a bug @note_order = [] end setup_parser markdown, @debug peg_parse doc = result if notes? and not @footnotes.empty? then doc << RDoc::Markup::Rule.new(1) @note_order.each_with_index do |ref, index| label = index + 1 note = @footnotes[ref] link = "{^#{label}}[rdoc-label:footmark-#{label}:foottext-#{label}] " note.parts.unshift link doc << note end end doc.accept @formatter doc end ## # Stores `label` as a reference to `link` and fills in previously unknown # link references. def reference label, link if ref = @unlinked_references.delete(label) then ref.replace link end @references[label] = link end ## # Wraps `text` in strong markup for rdoc inline formatting def strong text if text =~ /\A[a-z\d.\/-]+\z/i then "*#{text}*" else "#{text}" end end ## # Wraps `text` in strike markup for rdoc inline formatting def strike text if text =~ /\A[a-z\d.\/-]+\z/i then "~#{text}~" else "#{text}" end end } root = Doc Doc = BOM? Block*:a { RDoc::Markup::Document.new(*a.compact) } Block = @BlankLine* ( BlockQuote | Verbatim | CodeFence | Table | Note | Reference | HorizontalRule | Heading | OrderedList | BulletList | DefinitionList | HtmlBlock | StyleBlock | Para | Plain ) Para = @NonindentSpace Inlines:a @BlankLine+ { paragraph a } Plain = Inlines:a { paragraph a } AtxInline = !@Newline !(@Sp /#*/ @Sp @Newline) Inline AtxStart = < /\#{1,6}/ > { text.length } AtxHeading = AtxStart:s @Sp AtxInline+:a (@Sp /#*/ @Sp)? @Newline { RDoc::Markup::Heading.new(s, a.join) } SetextHeading = SetextHeading1 | SetextHeading2 SetextBottom1 = /={1,}/ @Newline SetextBottom2 = /-{1,}/ @Newline SetextHeading1 = &(@RawLine SetextBottom1) @StartList:a ( !@Endline Inline:b { a << b } )+ @Sp @Newline SetextBottom1 { RDoc::Markup::Heading.new(1, a.join) } SetextHeading2 = &(@RawLine SetextBottom2) @StartList:a ( !@Endline Inline:b { a << b })+ @Sp @Newline SetextBottom2 { RDoc::Markup::Heading.new(2, a.join) } Heading = SetextHeading | AtxHeading BlockQuote = BlockQuoteRaw:a { RDoc::Markup::BlockQuote.new(*a) } BlockQuoteRaw = @StartList:a (( ">" " "? Line:l { a << l } ) ( !">" !@BlankLine Line:c { a << c } )* ( @BlankLine:n { a << n } )* )+ { inner_parse a.join } NonblankIndentedLine = !@BlankLine IndentedLine VerbatimChunk = @BlankLine*:a NonblankIndentedLine+:b { a.concat b } Verbatim = VerbatimChunk+:a { RDoc::Markup::Verbatim.new(*a.flatten) } HorizontalRule = @NonindentSpace ( "*" @Sp "*" @Sp "*" (@Sp "*")* | "-" @Sp "-" @Sp "-" (@Sp "-")* | "_" @Sp "_" @Sp "_" (@Sp "_")*) @Sp @Newline @BlankLine+ { RDoc::Markup::Rule.new 1 } Bullet = !HorizontalRule @NonindentSpace /[+*-]/ @Spacechar+ BulletList = &Bullet (ListTight | ListLoose):a { RDoc::Markup::List.new(:BULLET, *a) } ListTight = ListItemTight+:a @BlankLine* !(Bullet | Enumerator) { a } ListLoose = @StartList:a ( ListItem:b @BlankLine* { a << b } )+ { a } ListItem = ( Bullet | Enumerator ) @StartList:a ListBlock:b { a << b } ( ListContinuationBlock:c { a.push(*c) } )* { list_item_from a } ListItemTight = ( Bullet | Enumerator ) ListBlock:a ( !@BlankLine ListContinuationBlock:b { a.push(*b) } )* !ListContinuationBlock { list_item_from a } ListBlock = !@BlankLine Line:a ListBlockLine*:c { [a, *c] } ListContinuationBlock = @StartList:a ( @BlankLine* { a << "\n" } ) ( Indent ListBlock:b { a.concat b } )+ { a } Enumerator = @NonindentSpace [0-9]+ "." @Spacechar+ OrderedList = &Enumerator (ListTight | ListLoose):a { RDoc::Markup::List.new(:NUMBER, *a) } ListBlockLine = !@BlankLine !( Indent? (Bullet | Enumerator) ) !HorizontalRule OptionallyIndentedLine # Parsers for different kinds of block-level HTML content. # This is repetitive due to constraints of PEG grammar. HtmlOpenAnchor = "<" Spnl ("a" | "A") Spnl HtmlAttribute* ">" HtmlCloseAnchor = "<" Spnl "/" ("a" | "A") Spnl ">" HtmlAnchor = HtmlOpenAnchor (HtmlAnchor | !HtmlCloseAnchor .)* HtmlCloseAnchor HtmlBlockOpenAddress = "<" Spnl ("address" | "ADDRESS") Spnl HtmlAttribute* ">" HtmlBlockCloseAddress = "<" Spnl "/" ("address" | "ADDRESS") Spnl ">" HtmlBlockAddress = HtmlBlockOpenAddress (HtmlBlockAddress | !HtmlBlockCloseAddress .)* HtmlBlockCloseAddress HtmlBlockOpenBlockquote = "<" Spnl ("blockquote" | "BLOCKQUOTE") Spnl HtmlAttribute* ">" HtmlBlockCloseBlockquote = "<" Spnl "/" ("blockquote" | "BLOCKQUOTE") Spnl ">" HtmlBlockBlockquote = HtmlBlockOpenBlockquote (HtmlBlockBlockquote | !HtmlBlockCloseBlockquote .)* HtmlBlockCloseBlockquote HtmlBlockOpenCenter = "<" Spnl ("center" | "CENTER") Spnl HtmlAttribute* ">" HtmlBlockCloseCenter = "<" Spnl "/" ("center" | "CENTER") Spnl ">" HtmlBlockCenter = HtmlBlockOpenCenter (HtmlBlockCenter | !HtmlBlockCloseCenter .)* HtmlBlockCloseCenter HtmlBlockOpenDir = "<" Spnl ("dir" | "DIR") Spnl HtmlAttribute* ">" HtmlBlockCloseDir = "<" Spnl "/" ("dir" | "DIR") Spnl ">" HtmlBlockDir = HtmlBlockOpenDir (HtmlBlockDir | !HtmlBlockCloseDir .)* HtmlBlockCloseDir HtmlBlockOpenDiv = "<" Spnl ("div" | "DIV") Spnl HtmlAttribute* ">" HtmlBlockCloseDiv = "<" Spnl "/" ("div" | "DIV") Spnl ">" HtmlBlockDiv = HtmlBlockOpenDiv (HtmlBlockDiv | !HtmlBlockCloseDiv .)* HtmlBlockCloseDiv HtmlBlockOpenDl = "<" Spnl ("dl" | "DL") Spnl HtmlAttribute* ">" HtmlBlockCloseDl = "<" Spnl "/" ("dl" | "DL") Spnl ">" HtmlBlockDl = HtmlBlockOpenDl (HtmlBlockDl | !HtmlBlockCloseDl .)* HtmlBlockCloseDl HtmlBlockOpenFieldset = "<" Spnl ("fieldset" | "FIELDSET") Spnl HtmlAttribute* ">" HtmlBlockCloseFieldset = "<" Spnl "/" ("fieldset" | "FIELDSET") Spnl ">" HtmlBlockFieldset = HtmlBlockOpenFieldset (HtmlBlockFieldset | !HtmlBlockCloseFieldset .)* HtmlBlockCloseFieldset HtmlBlockOpenForm = "<" Spnl ("form" | "FORM") Spnl HtmlAttribute* ">" HtmlBlockCloseForm = "<" Spnl "/" ("form" | "FORM") Spnl ">" HtmlBlockForm = HtmlBlockOpenForm (HtmlBlockForm | !HtmlBlockCloseForm .)* HtmlBlockCloseForm HtmlBlockOpenH1 = "<" Spnl ("h1" | "H1") Spnl HtmlAttribute* ">" HtmlBlockCloseH1 = "<" Spnl "/" ("h1" | "H1") Spnl ">" HtmlBlockH1 = HtmlBlockOpenH1 (HtmlBlockH1 | !HtmlBlockCloseH1 .)* HtmlBlockCloseH1 HtmlBlockOpenH2 = "<" Spnl ("h2" | "H2") Spnl HtmlAttribute* ">" HtmlBlockCloseH2 = "<" Spnl "/" ("h2" | "H2") Spnl ">" HtmlBlockH2 = HtmlBlockOpenH2 (HtmlBlockH2 | !HtmlBlockCloseH2 .)* HtmlBlockCloseH2 HtmlBlockOpenH3 = "<" Spnl ("h3" | "H3") Spnl HtmlAttribute* ">" HtmlBlockCloseH3 = "<" Spnl "/" ("h3" | "H3") Spnl ">" HtmlBlockH3 = HtmlBlockOpenH3 (HtmlBlockH3 | !HtmlBlockCloseH3 .)* HtmlBlockCloseH3 HtmlBlockOpenH4 = "<" Spnl ("h4" | "H4") Spnl HtmlAttribute* ">" HtmlBlockCloseH4 = "<" Spnl "/" ("h4" | "H4") Spnl ">" HtmlBlockH4 = HtmlBlockOpenH4 (HtmlBlockH4 | !HtmlBlockCloseH4 .)* HtmlBlockCloseH4 HtmlBlockOpenH5 = "<" Spnl ("h5" | "H5") Spnl HtmlAttribute* ">" HtmlBlockCloseH5 = "<" Spnl "/" ("h5" | "H5") Spnl ">" HtmlBlockH5 = HtmlBlockOpenH5 (HtmlBlockH5 | !HtmlBlockCloseH5 .)* HtmlBlockCloseH5 HtmlBlockOpenH6 = "<" Spnl ("h6" | "H6") Spnl HtmlAttribute* ">" HtmlBlockCloseH6 = "<" Spnl "/" ("h6" | "H6") Spnl ">" HtmlBlockH6 = HtmlBlockOpenH6 (HtmlBlockH6 | !HtmlBlockCloseH6 .)* HtmlBlockCloseH6 HtmlBlockOpenMenu = "<" Spnl ("menu" | "MENU") Spnl HtmlAttribute* ">" HtmlBlockCloseMenu = "<" Spnl "/" ("menu" | "MENU") Spnl ">" HtmlBlockMenu = HtmlBlockOpenMenu (HtmlBlockMenu | !HtmlBlockCloseMenu .)* HtmlBlockCloseMenu HtmlBlockOpenNoframes = "<" Spnl ("noframes" | "NOFRAMES") Spnl HtmlAttribute* ">" HtmlBlockCloseNoframes = "<" Spnl "/" ("noframes" | "NOFRAMES") Spnl ">" HtmlBlockNoframes = HtmlBlockOpenNoframes (HtmlBlockNoframes | !HtmlBlockCloseNoframes .)* HtmlBlockCloseNoframes HtmlBlockOpenNoscript = "<" Spnl ("noscript" | "NOSCRIPT") Spnl HtmlAttribute* ">" HtmlBlockCloseNoscript = "<" Spnl "/" ("noscript" | "NOSCRIPT") Spnl ">" HtmlBlockNoscript = HtmlBlockOpenNoscript (HtmlBlockNoscript | !HtmlBlockCloseNoscript .)* HtmlBlockCloseNoscript HtmlBlockOpenOl = "<" Spnl ("ol" | "OL") Spnl HtmlAttribute* ">" HtmlBlockCloseOl = "<" Spnl "/" ("ol" | "OL") Spnl ">" HtmlBlockOl = HtmlBlockOpenOl (HtmlBlockOl | !HtmlBlockCloseOl .)* HtmlBlockCloseOl HtmlBlockOpenP = "<" Spnl ("p" | "P") Spnl HtmlAttribute* ">" HtmlBlockCloseP = "<" Spnl "/" ("p" | "P") Spnl ">" HtmlBlockP = HtmlBlockOpenP (HtmlBlockP | !HtmlBlockCloseP .)* HtmlBlockCloseP HtmlBlockOpenPre = "<" Spnl ("pre" | "PRE") Spnl HtmlAttribute* ">" HtmlBlockClosePre = "<" Spnl "/" ("pre" | "PRE") Spnl ">" HtmlBlockPre = HtmlBlockOpenPre (HtmlBlockPre | !HtmlBlockClosePre .)* HtmlBlockClosePre HtmlBlockOpenTable = "<" Spnl ("table" | "TABLE") Spnl HtmlAttribute* ">" HtmlBlockCloseTable = "<" Spnl "/" ("table" | "TABLE") Spnl ">" HtmlBlockTable = HtmlBlockOpenTable (HtmlBlockTable | !HtmlBlockCloseTable .)* HtmlBlockCloseTable HtmlBlockOpenUl = "<" Spnl ("ul" | "UL") Spnl HtmlAttribute* ">" HtmlBlockCloseUl = "<" Spnl "/" ("ul" | "UL") Spnl ">" HtmlBlockUl = HtmlBlockOpenUl (HtmlBlockUl | !HtmlBlockCloseUl .)* HtmlBlockCloseUl HtmlBlockOpenDd = "<" Spnl ("dd" | "DD") Spnl HtmlAttribute* ">" HtmlBlockCloseDd = "<" Spnl "/" ("dd" | "DD") Spnl ">" HtmlBlockDd = HtmlBlockOpenDd (HtmlBlockDd | !HtmlBlockCloseDd .)* HtmlBlockCloseDd HtmlBlockOpenDt = "<" Spnl ("dt" | "DT") Spnl HtmlAttribute* ">" HtmlBlockCloseDt = "<" Spnl "/" ("dt" | "DT") Spnl ">" HtmlBlockDt = HtmlBlockOpenDt (HtmlBlockDt | !HtmlBlockCloseDt .)* HtmlBlockCloseDt HtmlBlockOpenFrameset = "<" Spnl ("frameset" | "FRAMESET") Spnl HtmlAttribute* ">" HtmlBlockCloseFrameset = "<" Spnl "/" ("frameset" | "FRAMESET") Spnl ">" HtmlBlockFrameset = HtmlBlockOpenFrameset (HtmlBlockFrameset | !HtmlBlockCloseFrameset .)* HtmlBlockCloseFrameset HtmlBlockOpenLi = "<" Spnl ("li" | "LI") Spnl HtmlAttribute* ">" HtmlBlockCloseLi = "<" Spnl "/" ("li" | "LI") Spnl ">" HtmlBlockLi = HtmlBlockOpenLi (HtmlBlockLi | !HtmlBlockCloseLi .)* HtmlBlockCloseLi HtmlBlockOpenTbody = "<" Spnl ("tbody" | "TBODY") Spnl HtmlAttribute* ">" HtmlBlockCloseTbody = "<" Spnl "/" ("tbody" | "TBODY") Spnl ">" HtmlBlockTbody = HtmlBlockOpenTbody (HtmlBlockTbody | !HtmlBlockCloseTbody .)* HtmlBlockCloseTbody HtmlBlockOpenTd = "<" Spnl ("td" | "TD") Spnl HtmlAttribute* ">" HtmlBlockCloseTd = "<" Spnl "/" ("td" | "TD") Spnl ">" HtmlBlockTd = HtmlBlockOpenTd (HtmlBlockTd | !HtmlBlockCloseTd .)* HtmlBlockCloseTd HtmlBlockOpenTfoot = "<" Spnl ("tfoot" | "TFOOT") Spnl HtmlAttribute* ">" HtmlBlockCloseTfoot = "<" Spnl "/" ("tfoot" | "TFOOT") Spnl ">" HtmlBlockTfoot = HtmlBlockOpenTfoot (HtmlBlockTfoot | !HtmlBlockCloseTfoot .)* HtmlBlockCloseTfoot HtmlBlockOpenTh = "<" Spnl ("th" | "TH") Spnl HtmlAttribute* ">" HtmlBlockCloseTh = "<" Spnl "/" ("th" | "TH") Spnl ">" HtmlBlockTh = HtmlBlockOpenTh (HtmlBlockTh | !HtmlBlockCloseTh .)* HtmlBlockCloseTh HtmlBlockOpenThead = "<" Spnl ("thead" | "THEAD") Spnl HtmlAttribute* ">" HtmlBlockCloseThead = "<" Spnl "/" ("thead" | "THEAD") Spnl ">" HtmlBlockThead = HtmlBlockOpenThead (HtmlBlockThead | !HtmlBlockCloseThead .)* HtmlBlockCloseThead HtmlBlockOpenTr = "<" Spnl ("tr" | "TR") Spnl HtmlAttribute* ">" HtmlBlockCloseTr = "<" Spnl "/" ("tr" | "TR") Spnl ">" HtmlBlockTr = HtmlBlockOpenTr (HtmlBlockTr | !HtmlBlockCloseTr .)* HtmlBlockCloseTr HtmlBlockOpenScript = "<" Spnl ("script" | "SCRIPT") Spnl HtmlAttribute* ">" HtmlBlockCloseScript = "<" Spnl "/" ("script" | "SCRIPT") Spnl ">" HtmlBlockScript = HtmlBlockOpenScript (!HtmlBlockCloseScript .)* HtmlBlockCloseScript HtmlBlockOpenHead = "<" Spnl ("head" | "HEAD") Spnl HtmlAttribute* ">" HtmlBlockCloseHead = "<" Spnl "/" ("head" | "HEAD") Spnl ">" HtmlBlockHead = HtmlBlockOpenHead (!HtmlBlockCloseHead .)* HtmlBlockCloseHead HtmlBlockInTags = HtmlAnchor | HtmlBlockAddress | HtmlBlockBlockquote | HtmlBlockCenter | HtmlBlockDir | HtmlBlockDiv | HtmlBlockDl | HtmlBlockFieldset | HtmlBlockForm | HtmlBlockH1 | HtmlBlockH2 | HtmlBlockH3 | HtmlBlockH4 | HtmlBlockH5 | HtmlBlockH6 | HtmlBlockMenu | HtmlBlockNoframes | HtmlBlockNoscript | HtmlBlockOl | HtmlBlockP | HtmlBlockPre | HtmlBlockTable | HtmlBlockUl | HtmlBlockDd | HtmlBlockDt | HtmlBlockFrameset | HtmlBlockLi | HtmlBlockTbody | HtmlBlockTd | HtmlBlockTfoot | HtmlBlockTh | HtmlBlockThead | HtmlBlockTr | HtmlBlockScript | HtmlBlockHead HtmlBlock = < ( HtmlBlockInTags | HtmlComment | HtmlBlockSelfClosing | HtmlUnclosed) > @BlankLine+ { if html? then RDoc::Markup::Raw.new text end } HtmlUnclosed = "<" Spnl HtmlUnclosedType Spnl HtmlAttribute* Spnl ">" HtmlUnclosedType = "HR" | "hr" HtmlBlockSelfClosing = "<" Spnl HtmlBlockType Spnl HtmlAttribute* "/" Spnl ">" HtmlBlockType = "ADDRESS" | "BLOCKQUOTE" | "CENTER" | "DD" | "DIR" | "DIV" | "DL" | "DT" | "FIELDSET" | "FORM" | "FRAMESET" | "H1" | "H2" | "H3" | "H4" | "H5" | "H6" | "HR" | "ISINDEX" | "LI" | "MENU" | "NOFRAMES" | "NOSCRIPT" | "OL" | "P" | "PRE" | "SCRIPT" | "TABLE" | "TBODY" | "TD" | "TFOOT" | "TH" | "THEAD" | "TR" | "UL" | "address" | "blockquote" | "center" | "dd" | "dir" | "div" | "dl" | "dt" | "fieldset" | "form" | "frameset" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "hr" | "isindex" | "li" | "menu" | "noframes" | "noscript" | "ol" | "p" | "pre" | "script" | "table" | "tbody" | "td" | "tfoot" | "th" | "thead" | "tr" | "ul" StyleOpen = "<" Spnl ("style" | "STYLE") Spnl HtmlAttribute* ">" StyleClose = "<" Spnl "/" ("style" | "STYLE") Spnl ">" InStyleTags = StyleOpen (!StyleClose .)* StyleClose StyleBlock = < InStyleTags > @BlankLine* { if css? then RDoc::Markup::Raw.new text end } Inlines = ( !@Endline Inline:i { i } | @Endline:c !( &{ github? } Ticks3 /[^`\n]*$/ ) &Inline { c } )+:chunks @Endline? { chunks } Inline = Str | @Endline | UlOrStarLine | @Space | Strong | Emph | Strike | Image | Link | NoteReference | InlineNote | Code | RawHtml | Entity | EscapedChar | Symbol Space = @Spacechar+ { " " } Str = @StartList:a < @NormalChar+ > { a = text } ( StrChunk:c { a << c } )* { a } StrChunk = < (@NormalChar | /_+/ &Alphanumeric)+ > { text } EscapedChar = "\\" !@Newline < /[:\\`|*_{}\[\]()#+.!><-]/ > { text } Entity = ( HexEntity | DecEntity | CharEntity ):a { a } Endline = @LineBreak | @TerminalEndline | @NormalEndline NormalEndline = @Sp @Newline !@BlankLine !">" !AtxStart !(Line /={1,}|-{1,}/ @Newline) { "\n" } TerminalEndline = @Sp @Newline @Eof LineBreak = " " @NormalEndline { RDoc::Markup::HardBreak.new } Symbol = < @SpecialChar > { text } # This keeps the parser from getting bogged down on long strings of '*' or '_', # or strings of '*' or '_' with space on each side: UlOrStarLine = (UlLine | StarLine):a { a } StarLine = < /\*{4,}/ > { text } | < @Spacechar /\*+/ &@Spacechar > { text } UlLine = < /_{4,}/ > { text } | < @Spacechar /_+/ &@Spacechar > { text } Emph = EmphStar | EmphUl Whitespace = @Spacechar | @Newline EmphStar = "*" !@Whitespace @StartList:a ( !"*" Inline:b { a << b } | StrongStar:b { a << b } )+ "*" { emphasis a.join } EmphUl = "_" !@Whitespace @StartList:a ( !"_" Inline:b { a << b } | StrongUl:b { a << b } )+ "_" { emphasis a.join } Strong = StrongStar | StrongUl StrongStar = "**" !@Whitespace @StartList:a ( !"**" Inline:b { a << b } )+ "**" { strong a.join } StrongUl = "__" !@Whitespace @StartList:a ( !"__" Inline:b { a << b } )+ "__" { strong a.join } Strike = &{ strike? } "~~" !@Whitespace @StartList:a ( !"~~" Inline:b { a << b } )+ "~~" { strike a.join } # TODO alt text support Image = "!" ( ExplicitLink | ReferenceLink ):a { "rdoc-image:#{a[/\[(.*)\]/, 1]}" } Link = ExplicitLink | ReferenceLink | AutoLink ReferenceLink = ReferenceLinkDouble | ReferenceLinkSingle ReferenceLinkDouble = Label:content < Spnl > !"[]" Label:label { link_to content, label, text } ReferenceLinkSingle = Label:content < (Spnl "[]")? > { link_to content, content, text } ExplicitLink = Label:l "(" @Sp Source:s Spnl Title @Sp ")" { "{#{l}}[#{s}]" } Source = ( "<" < SourceContents > ">" | < SourceContents > ) { text } SourceContents = ( ( !"(" !")" !">" Nonspacechar )+ | "(" SourceContents ")")* Title = ( TitleSingle | TitleDouble | "" ):a { a } TitleSingle = "'" ( !( "'" @Sp ( ")" | @Newline ) ) . )* "'" TitleDouble = "\"" ( !( "\"" @Sp ( ")" | @Newline ) ) . )* "\"" AutoLink = AutoLinkUrl | AutoLinkEmail AutoLinkUrl = "<" < /[A-Za-z]+/ "://" ( !@Newline !">" . )+ > ">" { text } AutoLinkEmail = "<" ("mailto:")? < /[\w+.\/!%~$-]+/i "@" ( !@Newline !">" . )+ > ">" { "mailto:#{text}" } Reference = @NonindentSpace !"[]" Label:label ":" Spnl RefSrc:link RefTitle @BlankLine+ { # TODO use title reference label, link nil } Label = "[" ( !"^" &{ notes? } | &. &{ !notes? } ) @StartList:a ( !"]" Inline:l { a << l } )* "]" { a.join.gsub(/\s+/, ' ') } RefSrc = < Nonspacechar+ > { text } RefTitle = ( RefTitleSingle | RefTitleDouble | RefTitleParens | EmptyTitle ) EmptyTitle = "" RefTitleSingle = Spnl "'" < ( !( "'" @Sp @Newline | @Newline ) . )* > "'" { text } RefTitleDouble = Spnl "\"" < ( !("\"" @Sp @Newline | @Newline) . )* > "\"" { text } RefTitleParens = Spnl "(" < ( !(")" @Sp @Newline | @Newline) . )* > ")" { text } References = ( Reference | SkipBlock )* Ticks1 = "`" !"`" Ticks2 = "``" !"`" Ticks3 = "```" !"`" Ticks4 = "````" !"`" Ticks5 = "`````" !"`" Code = ( Ticks1 @Sp < ( ( !"`" Nonspacechar )+ | !Ticks1 /`+/ | !( @Sp Ticks1 ) ( @Spacechar | @Newline !@BlankLine ) )+ > @Sp Ticks1 | Ticks2 @Sp < ( ( !"`" Nonspacechar )+ | !Ticks2 /`+/ | !( @Sp Ticks2 ) ( @Spacechar | @Newline !@BlankLine ) )+ > @Sp Ticks2 | Ticks3 @Sp < ( ( !"`" Nonspacechar )+ | !Ticks3 /`+/ | !( @Sp Ticks3 ) ( @Spacechar | @Newline !@BlankLine ) )+ > @Sp Ticks3 | Ticks4 @Sp < ( ( !"`" Nonspacechar )+ | !Ticks4 /`+/ | !( @Sp Ticks4 ) ( @Spacechar | @Newline !@BlankLine ) )+ > @Sp Ticks4 | Ticks5 @Sp < ( ( !"`" Nonspacechar )+ | !Ticks5 /`+/ | !( @Sp Ticks5 ) ( @Spacechar | @Newline !@BlankLine ) )+ > @Sp Ticks5 ) { "#{text}" } RawHtml = < (HtmlComment | HtmlBlockScript | HtmlTag) > { if html? then text else '' end } BlankLine = @Sp @Newline { "\n" } Quoted = "\"" (!"\"" .)* "\"" | "'" (!"'" .)* "'" HtmlAttribute = (AlphanumericAscii | "-")+ Spnl ("=" Spnl (Quoted | (!">" Nonspacechar)+))? Spnl HtmlComment = "" .)* "-->" HtmlTag = "<" Spnl "/"? AlphanumericAscii+ Spnl HtmlAttribute* "/"? Spnl ">" Eof = !. Nonspacechar = !@Spacechar !@Newline . Sp = @Spacechar* Spnl = @Sp (@Newline @Sp)? SpecialChar = /[~*_`&\[\]() ";" { [text.to_i(16)].pack 'U' } DecEntity = "&#" < /[0-9]+/ > ";" { [text.to_i].pack 'U' } CharEntity = "&" ";" { if entity = HTML_ENTITIES[text] then entity.pack 'U*' else "&#{text};" end } NonindentSpace = / {0,3}/ Indent = /\t| / IndentedLine = Indent Line OptionallyIndentedLine = Indent? Line # StartList starts a list data structure that can be added to with cons: StartList = &. { [] } Line = @RawLine:a { a } RawLine = ( < (!"\r" !"\n" .)* @Newline > | < .+ > @Eof ) { text } SkipBlock = HtmlBlock | ( !"#" !SetextBottom1 !SetextBottom2 !@BlankLine @RawLine )+ @BlankLine* | @BlankLine+ | @RawLine # Syntax extensions ExtendedSpecialChar = &{ notes? } ( "^" ) NoteReference = &{ notes? } RawNoteReference:ref { note_for ref } RawNoteReference = "[^" < ( !@Newline !"]" . )+ > "]" { text } # TODO multiple paragraphs for a footnote Note = &{ notes? } @NonindentSpace RawNoteReference:ref ":" @Sp @StartList:a RawNoteBlock:i { a.concat i } ( &Indent RawNoteBlock:i { a.concat i } )* { @footnotes[ref] = paragraph a nil } InlineNote = &{ notes? } "^[" @StartList:a ( !"]" Inline:l { a << l } )+ "]" { ref = [:inline, @note_order.length] @footnotes[ref] = paragraph a note_for ref } Notes = ( Note | SkipBlock )* RawNoteBlock = @StartList:a ( !@BlankLine OptionallyIndentedLine:l { a << l } )+ ( < @BlankLine* > { a << text } ) { a } # Markdown extensions added by RDoc follow CodeFence = &{ github? } Ticks3 (@Sp StrChunk:format)? Spnl < ( ( !"`" Nonspacechar )+ | !Ticks3 /`+/ | Spacechar | @Newline )+ > Ticks3 @Sp @Newline* { verbatim = RDoc::Markup::Verbatim.new text verbatim.format = format.intern if format.instance_of?(String) verbatim } Table = &{ github? } TableRow:header TableLine:line TableRow+:body { table = RDoc::Markup::Table.new(header, line, body) } TableRow = TableItem+:row "|" @Newline { row } TableItem = "|" < (!"|" !@Newline .)+ > { text.strip } TableLine = TableColumn+:line "|" @Newline { line } TableColumn = "|" < ( "-"+ ":"? | ":" "-"* ) > { text.start_with?(":") ? :left : text.end_with?(":") ? :right : nil } DefinitionList = &{ definition_lists? } ( DefinitionListItem+:list ) { RDoc::Markup::List.new :NOTE, *list.flatten } DefinitionListItem = ( DefinitionListLabel+ ):label ( DefinitionListDefinition+ ):defns { list_items = [] list_items << RDoc::Markup::ListItem.new(label, defns.shift) list_items.concat defns.map { |defn| RDoc::Markup::ListItem.new nil, defn } unless list_items.empty? list_items } DefinitionListLabel = StrChunk:label @Sp @Newline { label } DefinitionListDefinition = @NonindentSpace ":" @Space Inlines:a @BlankLine+ { paragraph a }