%% name = RDoc::Markdown

%% header { # coding: UTF-8 # frozen_string_literal: true # :markup: markdown

## # RDoc::Markdown as described by the [markdown syntax]. # # To choose Markdown as your only default format see # Saved Options at RDoc::Options for instructions on setting up a β€˜.rdoc_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] 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]. 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: # # <style type=β€œtext/css”> # h1 { font-size: 3em } # </style> # # ### Definition Lists # # The definition_lists extension allows definition lists using the [PHP # Markdown Extra syntax], 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: # # <table> # … # </table> # # ### Notes # # The notes extension enables footnote support. This extension is enabled by # default. # # Example: # # Here is some text including an inline footnote ^[for short footnotes] # # … # # [^1]: With the footnote text down at the bottom # # Produces: # # Here is some text 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] 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]: daringfireball.net/projects/markdown/dingus # [GFM]: github.github.com/gfm/ # [pegmarkdown]: github.com/jgm/peg-markdown # [PHPE]: michelf.ca/projects/php-markdown/extra/#def-list # [syntax]: daringfireball.net/projects/markdown/syntax – # Last updated to jgm/peg-markdown commit 8f8fc22ef0

}

%% {

require_relative '../rdoc'
require_relative 'markup/to_joined_paragraph'
require_relative 'markdown/entities'

require_relative '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
    "<em>#{text}</em>"
  end
end

##

#

#
# Is the extension `name` enabled?

def extension? name
  @extensions.include? name
end

##

#

#
# 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] or raise ParseError, "footnote [^#{ref}] not found"

      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
    "<b>#{text}</b>"
  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
    "<s>#{text}</s>"
  end
end

##
# Wraps `text` in code markup for rdoc inline formatting

def code text
  # trim even spaces
  text = $2 while /\A( +|\t+)(.*)\1\z/ =~ text
  # escape unescaped backslash at the end
  backslash_at_end = "\\" if /(?<!\\)(?:\\\\)*\\\z/.match?(text)
  "<code>#{text}#{backslash_at_end}</code>"
end

##
# Parses inline markdown in table cells

def parse_table_cells(table)
  # Parse header cells
  table.header = table.header.map { |cell| parse_cell_inline(cell) }

  # Parse body cells
  table.body = table.body.map do |row|
    row.map { |cell| parse_cell_inline(cell) }
  end

  table
end

##
# Parses inline markdown in a single table cell

def parse_cell_inline(text)
  return text if text.nil? || text.empty?

  # Create a new parser instance for the cell
  cell_parser = RDoc::Markdown.new(@extensions, @debug)

  # Parse the cell content
  doc = cell_parser.parse(text)

  # Extract the parsed content
  if doc && doc.parts && !doc.parts.empty?
    para = doc.parts.first
    if para.is_a?(RDoc::Markup::Paragraph)
      para.parts.join
    else
      text
    end
  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 @Spacechar+ 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 }

Image = β€œ!” ExplicitLinkWithLabel:a

{ "rdoc-image:#{a[:link]}:#{a[:label]}" }

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 = ExplicitLinkWithLabel:a

{ "{#{a[:label]}}[#{a[:link]}]" }

ExplicitLinkWithLabel = Label:label β€œ(” @Sp Source:link Spnl Title @Sp β€œ)”

{ { label: label, link: link } }

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 < (

    ( !"`" Nonspacechar )+ | !Ticks1 /`+/ |
    !Ticks1 ( @Spacechar | @Newline !@BlankLine )
  )+ > Ticks1 |
  Ticks2 < (
    ( !"`" Nonspacechar )+ |
    !Ticks2 /`+/ |
    !Ticks2 ( @Spacechar | @Newline !@BlankLine )
  )+ > Ticks2 |
  Ticks3 < (
    ( !"`" Nonspacechar )+ |
    !Ticks3 /`+/ |
    !Ticks3 ( @Spacechar | @Newline !@BlankLine )
  )+ > Ticks3 |
  Ticks4 < (
    ( !"`" Nonspacechar )+ |
    !Ticks4 /`+/ |
    !Ticks4 ( @Spacechar | @Newline !@BlankLine )
  )+ > Ticks4 |
  Ticks5 < (
    ( !"`" Nonspacechar )+ |
    !Ticks5 /`+/ |
    !Ticks5 ( @Spacechar | @Newline !@BlankLine )
  )+ > Ticks5
)
{ code 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 = /[~*_`&()<!#\β€™β€œ]/ | @ExtendedSpecialChar NormalChar = !( @SpecialChar | @Spacechar | @Newline ) . Digit = [0-9]

%literals = RDoc::Markdown::Literals Alphanumeric = %literals.Alphanumeric AlphanumericAscii = %literals.AlphanumericAscii BOM = %literals.BOM Newline = %literals.Newline Spacechar = %literals.Spacechar

HexEntity = /&#x/i < /[0-9a-fA-F]+/ > β€œ;”

{ [text.to_i(16)].pack 'U' }

DecEntity = β€œ&#” < /[0-9]+/ > β€œ;”

{ [text.to_i].pack 'U' }

CharEntity = β€œ&” </[A-Za-z0-9]+/ > β€œ;”

{ 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 = ( < /[^rn]*/ @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 !RawNoteReference 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? }

TableHead:header TableLine:line TableRow+:body
{
  table = RDoc::Markup::Table.new(header, line, body)
  parse_table_cells(table)
}

TableHead = TableItem2+:items β€œ|”? @Newline

{ items }

TableRow = ( ( TableItem:item1 TableItem2*:items { [item1, *items] } ):row | TableItem2+:row ) β€œ|”? @Newline

{ row }

TableItem2 = β€œ|” TableItem TableItem = < /(?:\.|[^|n])+/ >

{ text.strip.gsub(/\\([|])/, '\1')  }

TableLine = ( ( TableAlign:align1 TableAlign2*:aligns {[align1, *aligns] } ):line | TableAlign2+:line ) β€œ|”? @Newline

{ line }

TableAlign2 = β€œ|” @Sp TableAlign TableAlign = < /:?-+:?/ > @Sp

{
  text.start_with?(":") ?
  (text.end_with?(":") ? :center : :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 = Inline:label @Sp @Newline

{ label }

DefinitionListDefinition = @NonindentSpace β€œ:” @Space Inlines:a @BlankLine+

{ paragraph a }