Best Python code snippet using stestr_python
converter.py
Source:converter.py  
1# Copyright 2014 Google Inc. All Rights Reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7#     http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14"""Handles conversion of Wiki files."""15import urlparse16from . import constants17class Converter(object):18  """Class that handles the actual parsing and generation."""19  # A map of HTML tags to a list of the supported args for that tag.20  _BASIC_HTML_ARGS = ["title", "dir", "lang"]21  _BASIC_HTML_SIZEABLE_ARGS = (_BASIC_HTML_ARGS +22                               ["border", "height", "width", "align"])23  _BASIC_HTML_TABLE_ARGS = (_BASIC_HTML_SIZEABLE_ARGS +24                            ["valign", "cellspacing", "cellpadding"])25  _ALLOWED_HTML_TAGS = {26      "a": _BASIC_HTML_ARGS + ["href"],27      "b": _BASIC_HTML_ARGS,28      "br": _BASIC_HTML_ARGS,29      "blockquote": _BASIC_HTML_ARGS,30      "code": _BASIC_HTML_ARGS + ["language"],31      "dd": _BASIC_HTML_ARGS,32      "div": _BASIC_HTML_ARGS,33      "dl": _BASIC_HTML_ARGS,34      "dt": _BASIC_HTML_ARGS,35      "em": _BASIC_HTML_ARGS,36      "font": _BASIC_HTML_ARGS + ["face", "size", "color"],37      "h1": _BASIC_HTML_ARGS,38      "h2": _BASIC_HTML_ARGS,39      "h3": _BASIC_HTML_ARGS,40      "h4": _BASIC_HTML_ARGS,41      "h5": _BASIC_HTML_ARGS,42      "i": _BASIC_HTML_ARGS,43      "img": _BASIC_HTML_SIZEABLE_ARGS + ["src", "alt"],44      "li": _BASIC_HTML_ARGS,45      "ol": _BASIC_HTML_ARGS + ["type", "start"],46      "p": _BASIC_HTML_ARGS + ["align"],47      "pre": _BASIC_HTML_ARGS,48      "q": _BASIC_HTML_ARGS,49      "s": _BASIC_HTML_ARGS,50      "span": _BASIC_HTML_ARGS,51      "strike": _BASIC_HTML_ARGS,52      "strong": _BASIC_HTML_ARGS,53      "sub": _BASIC_HTML_ARGS,54      "sup": _BASIC_HTML_ARGS,55      "table": _BASIC_HTML_TABLE_ARGS,56      "tbody": _BASIC_HTML_TABLE_ARGS,57      "td": _BASIC_HTML_TABLE_ARGS,58      "tfoot": _BASIC_HTML_TABLE_ARGS,59      "th": _BASIC_HTML_TABLE_ARGS,60      "thead": _BASIC_HTML_TABLE_ARGS + ["colspan", "rowspan"],61      "tr": _BASIC_HTML_TABLE_ARGS + ["colspan", "rowspan"],62      "tt": _BASIC_HTML_ARGS,63      "u": _BASIC_HTML_ARGS,64      "ul": _BASIC_HTML_ARGS + ["type"],65      "var": _BASIC_HTML_ARGS,66  }67  # These plugins consume raw text.68  _RAW_PLUGINS = ["code", "wiki:comment", "pre"]69  # Parameters supported by the g:plusone plugin.70  _PLUSONE_ARGS = ["count", "size", "href"]71  # Parameters supported by the wiki:video plugin.72  _VIDEO_ARGS = ["url", "width", "height"]73  _VIDEO_DEFAULT_WIDTH = "425"74  _VIDEO_DEFAULT_HEIGHT = "344"75  def __init__(76      self,77      pragma_handler,78      formatting_handler,79      warning_method,80      project,81      wikipages):82    """Create a converter.83    Args:84        pragma_handler: Handler for parsed pragmas.85        formatting_handler: Handler for parsed formatting rules.86        warning_method: A function to call to display a warning message.87        project: The name of the Google Code project for the Wiki page.88        wikipages: Wiki pages assumed to exist for auto-linking.89    """90    self._pragma_handler = pragma_handler91    self._formatting_handler = formatting_handler92    self._warning_method = warning_method93    self._wikipages = wikipages94    self._project = project95  def Convert(self, input_stream, output_stream):96    """Converts a file in Google Code Wiki format to Github-flavored Markdown.97    Args:98        input_stream: Input Wiki file.99        output_stream: Output Markdown file.100    """101    # For simpler processing just load the entire file into memory.102    input_lines = input_stream.readlines()103    input_line = 1104    # First extract pragmas, which must be placed at the top of the file.105    input_line = self._ExtractPragmas(input_line, input_lines, output_stream)106    # Now ignore any starting vertical whitespace.107    input_line = self._MoveToMain(input_line, input_lines, output_stream)108    # At the main text, begin processing.109    input_line = self._ProcessBody(input_line, input_lines, output_stream)110    # Done, but sanity check the amount of input processed.111    remaining_lines = len(input_lines) - input_line + 1112    if remaining_lines != 0:113      self._warning_method(114          input_line,115          u"Processing completed, but not all lines were processed. "116          "Remaining lines: {0}.".format(remaining_lines))117  def _ExtractPragmas(self, input_line, input_lines, output_stream):118    """Extracts pragmas from a given input.119    Args:120        input_line: Current line number being processed.121        input_lines: Input Wiki file lines.122        output_stream: Output Markdown file.123    Returns:124        The new value of input_line after processing.125    """126    for line in input_lines[input_line - 1:]:127      pragma_match = constants.PRAGMA_RE.match(line)128      if not pragma_match:129        # Found all the pragmas.130        break131      # Found a pragma, strip it and pass it to the handler.132      pragma_type, pragma_value = pragma_match.groups()133      self._pragma_handler.HandlePragma(134          input_line,135          output_stream,136          pragma_type.strip(),137          pragma_value.strip())138      # Moving on to the next line.139      input_line += 1140    return input_line141  def _MoveToMain(self, input_line, input_lines, unused_output_stream):142    """Move the input line position to the main body, after pragmas.143    Args:144        input_line: Current line number being processed.145        input_lines: Input Wiki file lines.146    Returns:147        The new value of input_line after processing.148    """149    for line in input_lines[input_line - 1:]:150      if line.strip():151        # Skipped all the whitespace.152        break153      # Moving on to the next line.154      input_line += 1155    return input_line156  def _ProcessBody(self, input_line, input_lines, output_stream):157    """The process core.158    It is a simple loop that tries to match formatting rules159    then pass it to the correct handler. It processes the matches160    in the same order as Google Code's wiki parser.161    Args:162        input_line: Current line number being processed.163        input_lines: Input Wiki file lines.164        output_stream: Output Markdown file.165    Returns:166        The new value of input_line after processing.167    """168    # State tracked during processing:169    self._code_block_depth = 0  # How many code block openings we've seen.170    self._code_block_lines = []  # What lines we've collected for a code block.171    self._indents = []  # 2-tuple of indent position and list type.172    self._open_tags = []  # List of open tags, like bold or italic.173    self._table_columns = []  # Table column sizes, taken from the header row.174    self._table_column = 0  # Current column in the table body, or zero if none.175    self._plugin_stack = []  # Current stack of plugins and their parameters.176    first_line = True177    for line in input_lines[input_line - 1:]:178      stripped_line = line.strip()179      self._ProcessLine(180          first_line,181          input_line,182          line,183          stripped_line,184          output_stream)185      # Moving on to the next line.186      input_line += 1187      first_line = False188    if self._code_block_depth:189      # Forgotten code block ending, close it implicitly.190      code = "".join(self._code_block_lines)191      self._formatting_handler.HandleText(input_line, output_stream, code)192      self._formatting_handler.HandleCodeBlockClose(input_line, output_stream)193    return input_line194  def _ProcessLine(195      self,196      first_line,197      input_line,198      line,199      stripped_line,200      output_stream):201    """Processes a single line, depending on state.202    Args:203        first_line: True if this is the first line, false otherwise.204        input_line: Current line number being processed.205        line: The raw line string.206        stripped_line: The line string, stripped of surrounding whitepsace.207        output_stream: Output Markdown file.208    Returns:209        The new value of input_line after processing.210    """211    # Check for the start of a code block.212    if constants.START_CODEBLOCK_RE.match(stripped_line):213      if self._code_block_depth == 0:214        # Start a new collection of lines.215        self._code_block_lines = []216      else:217        # Just an embedded code block.218        self._code_block_lines.append(line)219      self._code_block_depth += 1220      return221    # Check for the end of a code block.222    if constants.END_CODEBLOCK_RE.match(stripped_line):223      self._code_block_depth -= 1224      if self._code_block_depth == 0:225        # Closed the highest-level code block, handle it.226        self._formatting_handler.HandleEscapedText(227            input_line,228            output_stream,229            "\n")230        self._formatting_handler.HandleCodeBlockOpen(231            input_line,232            output_stream,233            None)234        code = "".join(self._code_block_lines)235        self._formatting_handler.HandleText(input_line, output_stream, code)236        self._formatting_handler.HandleCodeBlockClose(input_line, output_stream)237      else:238        # Just closed an embedded clode block.239        self._code_block_lines.append(line)240      return241    # Check if we're in a code block.242    # If we are, just put the raw text into code_block_lines.243    if self._code_block_depth != 0:244      self._code_block_lines.append(line)245      return246    # For empty lines, close all formatting.247    if not stripped_line:248      if not self._ConsumeTextForPlugin():249        self._SetCurrentList(input_line, 0, " ", output_stream)250        self._CloseTags(input_line, output_stream)251        if self._table_columns:252          self._formatting_handler.HandleTableClose(input_line, output_stream)253        self._table_columns = []254        self._table_column = 0255      self._formatting_handler.HandleParagraphBreak(input_line, output_stream)256      return257    # Non-empty line, finish the previous line's newline.258    if not first_line:259      self._formatting_handler.HandleEscapedText(260          input_line,261          output_stream,262          "\n")263    # Now check if we're processing within a list.264    indent_pos = constants.INDENT_RE.match(line).end()265    if (indent_pos and indent_pos < len(line) and266        not self._ConsumeTextForPlugin()):267      list_type = constants.LIST_TYPES.get(line[indent_pos], "blockquote")268      if self._SetCurrentList(input_line, indent_pos, list_type, output_stream):269        # Blockquotes take the entire remainder of the line,270        # but everything else skips the list symbol plus the space after.271        # (In case there is no space after, the first character is skipped;272        # we will warn if this is detected, as it was probably unintended.)273        if list_type == "blockquote":274          line = line[indent_pos:]275        else:276          if line[indent_pos + 1] != " ":277            self._warning_method(278                input_line,279                u"Missing space after list symbol: {0}, "280                "'{1}' was removed instead."281                .format(line[indent_pos], line[indent_pos + 1]))282          line = line[indent_pos + 2:]283        stripped_line = line.strip()284      else:285        # Reset to no indent.286        self._SetCurrentList(input_line, 0, " ", output_stream)287    # Finally, split the line into formatting primitives.288    # We do so without whitespace so we can catch line breaks across tags.289    if constants.LINE_FORMAT_RE.match(stripped_line):290      self._ProcessMatch(291          input_line,292          constants.LINE_FORMAT_RE,293          stripped_line,294          output_stream)295    else:296      self._ProcessMatch(297          input_line,298          constants.TEXT_FORMAT_RE,299          stripped_line,300          output_stream)301    self._CloseTableRow(input_line, output_stream)302  def _SetCurrentList(self, input_line, indent_pos, list_type, output_stream):303    """Set the current list level based on the indentation.304    Args:305      input_line: Current line number being processed.306      indent_pos: How far into the line we are indented.307      list_type: What the type of the list should be.308      output_stream: Output Markdown file.309    Returns:310      True if we are in a list item, False otherwise.311    """312    # Pop and close the lists until we hit a313    # list that is at the current position and type314    while self._indents and self._indents[-1][0] >= indent_pos:315      indents_top = self._indents[-1]316      if indents_top[0] == indent_pos and indents_top[1] == list_type:317        break318      self._formatting_handler.HandleListClose(input_line, output_stream)319      self._indents.pop()320    # If we just popped everything off, we're not in a list.321    if indent_pos == 0:322      return False323    if not self._indents or indent_pos >= self._indents[-1][0]:324      # Add a new indentation if this is the first item overall,325      # or the first item at this indentation position.326      if not self._indents or indent_pos > self._indents[-1][0]:327        self._indents.append((indent_pos, list_type))328      # Add the leading Markdown for the list.329      indentation_level = len(self._indents)330      if list_type == "numeric":331        self._formatting_handler.HandleNumericListOpen(332            input_line,333            output_stream,334            indentation_level)335      elif list_type == "bullet":336        self._formatting_handler.HandleBulletListOpen(337            input_line,338            output_stream,339            indentation_level)340      elif list_type == "blockquote":341        self._formatting_handler.HandleBlockQuoteOpen(342            input_line,343            output_stream,344            indentation_level)345      else:346        self._warning_method(347            input_line,348            u"Bad list type: '{0}'".format(list_type))349    return True350  def _OpenTag(self, input_line, tag, output_stream):351    """Open a tag and add it to the open tags list.352    Args:353      input_line: Current line number being processed.354      tag: Tag to open.355      output_stream: Output Markdown file.356    """357    handler = getattr(358        self._formatting_handler, u"Handle{0}Open".format(tag), None)359    if handler:360      handler(input_line, output_stream)361    else:362      self._warning_method(input_line, u"Bad open tag: '{0}'".format(tag))363    self._open_tags.append(tag)364  def _CloseTag(self, input_line, tag, output_stream):365    """Close a tag and remove it from the open tags list.366    Args:367      input_line: Current line number being processed.368      tag: Tag to close.369      output_stream: Output Markdown file.370    """371    handler = getattr(372        self._formatting_handler, u"Handle{0}Close".format(tag), None)373    if handler:374      handler(input_line, output_stream)375    else:376      self._warning_method(input_line, u"Bad close tag: '{0}'".format(tag))377    self._open_tags.remove(tag)378  def _CloseTags(self, input_line, output_stream):379    """Close all tags.380    Args:381      input_line: Current line number being processed.382      output_stream: Output Markdown file.383    """384    for tag in self._open_tags:385      self._CloseTag(input_line, tag, output_stream)386  def _CloseTableRow(self, input_line, output_stream):387    """Close table row, if any.388    Args:389      input_line: Current line number being processed.390      output_stream: Output Markdown file.391    """392    if self._table_columns:393      if self._table_column != 1:394        self._formatting_handler.HandleTableRowEnd(input_line, output_stream)395      # Check if we just finished the header row.396      if not self._table_column:397        self._formatting_handler.HandleTableHeader(398            input_line,399            output_stream,400            self._table_columns)401      # In a table body, set the current column to 1.402      self._table_column = 1403  def _ConsumeTextForPlugin(self):404    """Check if text should be consumed raw for a plugin.405    Returns:406      True if the current plugin is consuming raw text, false otherwise.407    """408    return (self._plugin_stack and409            self._plugin_stack[-1]["id"] in self._RAW_PLUGINS)410  def _ProcessMatch(self, input_line, match_regex, line, output_stream):411    """Process text, using a regex to match against.412    Args:413      input_line: Current line number being processed.414      match_regex: Regex to match the line against.415      line: The line being processed.416      output_stream: Output Markdown file.417    """418    lastpos = 0419    for fullmatch in match_regex.finditer(line):420      # Add text before the match as regular text.421      if lastpos < fullmatch.start():422        starting_line = line[lastpos:fullmatch.start()]423        if self._ConsumeTextForPlugin():424          self._formatting_handler.HandleText(425              input_line,426              output_stream,427              starting_line)428        else:429          self._formatting_handler.HandleEscapedText(430              input_line,431              output_stream,432              starting_line)433      for rulename, match in fullmatch.groupdict().items():434        if match is not None:435          if self._ConsumeTextForPlugin() and rulename != "PluginEnd":436            self._formatting_handler.HandleText(437                input_line,438                output_stream,439                match)440          else:441            handler = getattr(self, u"_Handle{0}".format(rulename), None)442            handler(input_line, match, output_stream)443      lastpos = fullmatch.end()444    # Add remainder of the line as regular text.445    if lastpos < len(line):446      remaining_line = line[lastpos:]447      if self._ConsumeTextForPlugin():448        self._formatting_handler.HandleText(449            input_line,450            output_stream,451            remaining_line)452      else:453        self._formatting_handler.HandleEscapedText(454            input_line,455            output_stream,456            remaining_line)457  def _HandleHeading(self, input_line, match, output_stream):458    """Handle a heading formatter.459    Args:460      input_line: Current line number being processed.461      match: Matched text.462      output_stream: Output Markdown file.463    """464    match = match.strip()465    # Count the equals on the left side.466    leftequalcount = 0467    for char in match:468      if char != "=":469        break470      leftequalcount += 1471    # Count the equals on the right side.472    rightequalcount = 0473    for char in reversed(match):474      if char != "=":475        break476      rightequalcount += 1477    # Users often forget to have the same number of equals signs on478    # both sides. Rather than simply error out, we say the level is479    # the number of equals signs on the left side.480    header_level = leftequalcount481    # If the level is greater than 6, the header is invalid and the contents482    # are parsed as if no header markup were provided.483    if header_level > 6:484      header_level = None485    # Everything else is the heading text.486    heading_text = match[leftequalcount:-rightequalcount].strip()487    if header_level:488      self._formatting_handler.HandleHeaderOpen(489          input_line,490          output_stream,491          header_level)492    self._ProcessMatch(493        input_line,494        constants.TEXT_FORMAT_RE,495        heading_text,496        output_stream)497    if header_level:498      self._formatting_handler.HandleHeaderClose(499          input_line,500          output_stream,501          header_level)502  def _HandleHRule(self, input_line, unused_match, output_stream):503    """Handle a heading formatter.504    Args:505      input_line: Current line number being processed.506      unused_match: Matched text.507      output_stream: Output Markdown file.508    """509    self._formatting_handler.HandleHRule(input_line, output_stream)510  def _HandleBold(self, input_line, unused_match, output_stream):511    """Handle a bold formatter.512    Args:513      input_line: Current line number being processed.514      unused_match: Matched text.515      output_stream: Output Markdown file.516    """517    self._HandleTag(input_line, "Bold", output_stream)518  def _HandleItalic(self, input_line, unused_match, output_stream):519    """Handle a italic formatter.520    Args:521      input_line: Current line number being processed.522      unused_match: Matched text.523      output_stream: Output Markdown file.524    """525    self._HandleTag(input_line, "Italic", output_stream)526  def _HandleStrikethrough(self, input_line, unused_match, output_stream):527    """Handle a strikethrough formatter.528    Args:529      input_line: Current line number being processed.530      unused_match: Matched text.531      output_stream: Output Markdown file.532    """533    self._HandleTag(input_line, "Strikethrough", output_stream)534  def _HandleSuperscript(self, input_line, match, output_stream):535    """Handle superscript.536    Args:537      input_line: Current line number being processed.538      match: Matched text.539      output_stream: Output Markdown file.540    """541    self._formatting_handler.HandleSuperscript(input_line, output_stream, match)542  def _HandleSubscript(self, input_line, match, output_stream):543    """Handle subscript.544    Args:545      input_line: Current line number being processed.546      match: Matched text.547      output_stream: Output Markdown file.548    """549    self._formatting_handler.HandleSubscript(input_line, output_stream, match)550  def _HandleInlineCode(self, input_line, match, output_stream):551    """Handle inline code, method one.552    Args:553      input_line: Current line number being processed.554      match: Matched text.555      output_stream: Output Markdown file.556    """557    self._formatting_handler.HandleInlineCode(input_line, output_stream, match)558  def _HandleInlineCode2(self, input_line, match, output_stream):559    """Handle inline code, method two.560    Args:561      input_line: Current line number being processed.562      match: Matched text.563      output_stream: Output Markdown file.564    """565    self._formatting_handler.HandleInlineCode(input_line, output_stream, match)566  def _HandleTableCell(self, input_line, match, output_stream):567    """Handle a table cell.568    Args:569      input_line: Current line number being processed.570      match: Matched text.571      output_stream: Output Markdown file.572    """573    # Table cells end previous formatting.574    self._CloseTags(input_line, output_stream)575    # Count the pipes to calculate the column span.576    pipecount = 0577    for char in match:578      if char != "|":579        break580      pipecount += 1581    span = pipecount / 2582    # Now output the cell, tracking the size of the contents.583    self._formatting_handler.HandleTableCellBorder(input_line, output_stream)584    starting_pos = output_stream.tell()585    self._ProcessMatch(586        input_line,587        constants.TEXT_FORMAT_RE,588        match[pipecount:],589        output_stream)590    ending_pos = output_stream.tell()591    # Handle the cell width, either tracking or padding.592    cell_width = ending_pos - starting_pos593    if not self._table_column:594      # In the header row, track the column sizes.595      self._table_columns.append(cell_width)596    else:597      # In the table body, pad the cell (for prettier raw text viewing).598      header_cell_width = self._table_columns[self._table_column - 1]599      remaining_width = header_cell_width - cell_width600      if remaining_width > 0:601        padding = " " * remaining_width602        self._formatting_handler.HandleEscapedText(603            input_line,604            output_stream,605            padding)606      self._table_column += 1607    if span > 1:608      self._warning_method(609          input_line,610          "Multi-span cells are not directly supported in GFM. They have been "611          "emulated by adding empty cells. This may give the correct rendered "612          "result, but the plain-text representation may be noisy. Consider "613          "removing the multi-span cells from your table, or using HTML.")614      while span > 1:615        # Empty cell.616        self._formatting_handler.HandleTableCellBorder(617            input_line,618            output_stream)619        self._formatting_handler.HandleEscapedText(620            input_line,621            output_stream,622            " ")623        self._table_columns.append(1)624        span -= 1625  def _HandleTableRowEnd(self, input_line, unused_match, output_stream):626    """Handle a table row ending.627    Args:628      input_line: Current line number being processed.629      unused_match: Matched text.630      output_stream: Output Markdown file.631    """632    # Table cells end previous formatting.633    self._CloseTags(input_line, output_stream)634    self._CloseTableRow(input_line, output_stream)635  def _HandleUrl(self, input_line, match, output_stream):636    """Handle an auto-linked URL.637    Args:638      input_line: Current line number being processed.639      match: Matched text.640      output_stream: Output Markdown file.641    """642    self._formatting_handler.HandleLink(input_line, output_stream, match, None)643  def _HandleUrlBracket(self, input_line, match, output_stream):644    """Handle a bracketed URL.645    Args:646      input_line: Current line number being processed.647      match: Matched text.648      output_stream: Output Markdown file.649    """650    # First, strip the brackets off to get to the URL and description.651    core = match[1:-1]652    # Now strip out the description.653    parts = constants.WHITESPACE_RE.split(core, 1)654    if len(parts) == 1:655      url = parts[0]656      description = None657    else:658      url = parts[0]659      description = parts[1]660    self._formatting_handler.HandleLink(661        input_line,662        output_stream,663        url,664        description)665  def _HandleWikiWord(self, input_line, match, output_stream):666    """Handle a wiki word.667    Args:668      input_line: Current line number being processed.669      match: Matched text.670      output_stream: Output Markdown file.671    """672    if match[0] == "!":673      self._formatting_handler.HandleEscapedText(674          input_line,675          output_stream,676          match[1:])677    elif match not in self._wikipages:678      self._formatting_handler.HandleEscapedText(679          input_line,680          output_stream,681          match)682    else:683      self._formatting_handler.HandleWiki(684          input_line,685          output_stream,686          match,687          None)688  def _HandleWikiWordBracket(self, input_line, match, output_stream):689    """Handle a bracketed wiki word.690    Args:691      input_line: Current line number being processed.692      match: Matched text.693      output_stream: Output Markdown file.694    """695    # First, strip the brackets off to get to the wiki and description.696    core = match[1:-1]697    # Now strip out the description.698    parts = constants.WHITESPACE_RE.split(core, 1)699    if len(parts) == 1:700      wiki = parts[0]701      description = None702    else:703      wiki = parts[0]704      description = parts[1]705    self._formatting_handler.HandleWiki(706        input_line,707        output_stream,708        wiki,709        description)710  def _HandleIssueLink(self, input_line, match, output_stream):711    """Handle an auto-linked issue.712    Args:713      input_line: Current line number being processed.714      match: Matched text.715      output_stream: Output Markdown file.716    """717    issue = match[len("issue"):].strip()718    prefix = match[:-len(issue)]719    self._formatting_handler.HandleIssue(720        input_line,721        output_stream,722        prefix,723        issue)724  def _HandleRevisionLink(self, input_line, match, output_stream):725    """Handle an auto-linked revision.726    Args:727      input_line: Current line number being processed.728      match: Matched text.729      output_stream: Output Markdown file.730    """731    if match[1].lower() == "e":732      revision = match[len("revision"):].strip()733    else:734      revision = match[len("r"):].strip()735    prefix = match[:-len(revision)]736    self._formatting_handler.HandleRevision(737        input_line,738        output_stream,739        prefix,740        revision)741  def _HandlePlugin(self, input_line, match, output_stream):742    """Handle a plugin tag.743    Args:744      input_line: Current line number being processed.745      match: Matched text.746      output_stream: Output Markdown file.747    """748    # Plugins close formatting tags.749    self._CloseTags(input_line, output_stream)750    # Get the core of the tag, check if this is also an end tag.751    if match.endswith("/>"):752      core = match[1:-2]753      has_end = True754    else:755      core = match[1:-1]756      has_end = False757    # Extract the ID for the plugin.758    plugin_id = constants.PLUGIN_ID_RE.match(core).group(0)759    core_params = core[len(plugin_id):].strip()760    # Extract the parameters for the plugin.761    params = {}762    for name, value in constants.PLUGIN_PARAM_RE.findall(core_params):763      # Remove quotes from the value, if they exist764      if value.startswith("'"):765        value = value.strip("'")766      elif value.startswith("\""):767        value = value.strip("\"")768      params[name] = value769    # Now figure out what to do with the plugin.770    if plugin_id in self._ALLOWED_HTML_TAGS:771      self._HandlePluginHtml(772          input_line,773          plugin_id,774          params,775          has_end,776          output_stream)777    elif plugin_id == "g:plusone":778      self._HandlePluginGPlus(779          input_line,780          plugin_id,781          params,782          output_stream)783    elif plugin_id == "wiki:comment":784      self._HandlePluginWikiComment(785          input_line,786          plugin_id,787          params,788          output_stream)789    elif plugin_id == "wiki:gadget":790      self._HandlePluginWikiGadget(input_line, match, output_stream)791    elif plugin_id == "wiki:video":792      self._HandlePluginWikiVideo(793          input_line,794          plugin_id,795          params,796          output_stream)797    elif plugin_id == "wiki:toc":798      self._HandlePluginWikiToc(input_line, match, output_stream)799    else:800      self._warning_method(801          input_line,802          u"Unknown plugin was given, outputting "803          "as plain text:\n\t{0}".format(match))804      # Wiki syntax put this class of error on its own line.805      self._formatting_handler.HandleEscapedText(806          input_line,807          output_stream,808          u"\n\n{0}\n\n".format(match))809    # Add plugin and parameters to the stack.810    if not has_end:811      plugin_info = {"id": plugin_id, "params": params}812      self._plugin_stack.append(plugin_info)813  def _HandlePluginHtml(814      self,815      input_line,816      plugin_id,817      params,818      has_end,819      output_stream):820    """Handle a plugin tag for HTML.821    Args:822      input_line: Current line number being processed.823      plugin_id: The plugin ID.824      params: The plugin params.825      has_end: Plugin has an end tag.826      output_stream: Output Markdown file.827    """828    # Filter the parameters. These are only filtered for output,829    # they still have the effect of being usable variables.830    allowed_parameters = self._ALLOWED_HTML_TAGS[plugin_id]831    filtered_params = {}832    for name, value in params.items():833      if name in allowed_parameters:834        filtered_params[name] = value835      else:836        self._warning_method(837            input_line,838            u"The following parameter was given for the '{0}' tag, "839            "but will not be present in the outputted HTML:\n\t'{1}': '{2}'"840            .format(plugin_id, name, value))841    if plugin_id == "code":842      self._formatting_handler.HandleCodeBlockOpen(843          input_line,844          output_stream,845          filtered_params.get("language"))846    else:847      self._formatting_handler.HandleHtmlOpen(848          input_line,849          output_stream,850          plugin_id,851          filtered_params,852          has_end)853  def _HandlePluginGPlus(854      self,855      input_line,856      plugin_id,857      params,858      output_stream):859    """Handle a plugin tag for +1 button.860    Args:861      input_line: Current line number being processed.862      plugin_id: The plugin ID.863      params: The plugin params.864      output_stream: Output Markdown file.865    """866    filtered_params = {}867    for name, value in params.items():868      if name in self._PLUSONE_ARGS:869        filtered_params[name] = value870      else:871        self._warning_method(872            input_line,873            u"The following parameter was given for the '{0}' tag, "874            "but will not be present in the outputted HTML:\n\t'{1}': '{2}'"875            .format(plugin_id, name, value))876    self._formatting_handler.HandleGPlusOpen(877        input_line,878        output_stream,879        filtered_params)880  def _HandlePluginWikiComment(881      self,882      input_line,883      plugin_id,884      params,885      output_stream):886    """Handle a plugin tag for a wiki comment.887    Args:888      input_line: Current line number being processed.889      plugin_id: The plugin ID.890      params: The plugin params.891      output_stream: Output Markdown file.892    """893    for name, value in params.items():894      self._warning_method(895          input_line,896          u"The following parameter was given for the '{0}' tag, "897          "but will not be present in the outputted HTML:\n\t'{1}': '{2}'"898          .format(plugin_id, name, value))899    self._formatting_handler.HandleCommentOpen(input_line, output_stream)900  def _HandlePluginWikiGadget(self, input_line, match, output_stream):901    """Handle a plugin tag for a wiki gadget.902    Args:903      input_line: Current line number being processed.904      match: Matched text.905      output_stream: Output Markdown file.906    """907    self._warning_method(908        input_line,909        u"A wiki gadget was used, but this must be manually converted to a "910        "GFM-supported method, if possible. Outputting as plain text:\n\t{0}"911        .format(match))912    self._formatting_handler.HandleEscapedText(913        input_line,914        output_stream,915        match)916  def _HandlePluginWikiVideo(917      self,918      input_line,919      plugin_id,920      params,921      output_stream):922    """Handle a plugin tag for a wiki video.923    Args:924      input_line: Current line number being processed.925      plugin_id: The plugin ID.926      params: The plugin params.927      output_stream: Output Markdown file.928    """929    filtered_params = {}930    for name, value in params.items():931      if name in self._VIDEO_ARGS:932        filtered_params[name] = value933      else:934        self._warning_method(935            input_line,936            u"The following parameter was given for the '{0}' tag, "937            "but will not be present in the outputted HTML:\n\t'{1}': '{2}'"938            .format(plugin_id, name, value))939    if "url" in filtered_params:940      width = filtered_params.get("width", self._VIDEO_DEFAULT_WIDTH)941      height = filtered_params.get("height", self._VIDEO_DEFAULT_HEIGHT)942      extracted = urlparse.urlparse(filtered_params["url"])943      query = urlparse.parse_qs(extracted.query)944      video_id = query.get("v", [""])[0]945      if not video_id and extracted.path.startswith("/v/"):946        video_id = extracted.path[3:]947      if not constants.YOUTUBE_VIDEO_ID_RE.match(video_id):948        output = ("wiki:video: cannot find YouTube "949                  "video id within parameter \"url\".")950        self._warning_method(951            input_line,952            u"Video plugin has invalid video ID, outputting error:\n\t{0}"953            .format(output))954        # Wiki syntax put this class of error on its own line.955        self._formatting_handler.HandleEscapedText(956            input_line,957            output_stream,958            u"\n\n{0}\n\n".format(output))959      else:960        self._formatting_handler.HandleVideoOpen(961            input_line,962            output_stream,963            video_id,964            width,965            height)966    else:967      output = "wiki:video: missing mandatory parameter \"url\"."968      self._warning_method(969          input_line,970          u"Video plugin is missing 'url' parameter, outputting error:\n\t{0}"971          .format(output))972      # Wiki syntax put this class of error on its own line.973      self._formatting_handler.HandleEscapedText(974          input_line,975          output_stream,976          u"\n\n{0}\n\n".format(output))977  def _HandlePluginWikiToc(self, input_line, match, output_stream):978    """Handle a plugin tag for a wiki table of contents.979    Args:980      input_line: Current line number being processed.981      match: Matched text.982      output_stream: Output Markdown file.983    """984    self._warning_method(985        input_line,986        u"A table of contents plugin was used for this wiki:\n"987        "\t{0}\n"988        "The Gollum wiki system supports table of content generation.\n"989        "See https://github.com/gollum/gollum/wiki for more information.\n"990        "It has been removed."991        .format(match))992  def _HandlePluginEnd(self, input_line, match, output_stream):993    """Handle a plugin ending tag.994    Args:995      input_line: Current line number being processed.996      match: Matched text.997      output_stream: Output Markdown file.998    """999    core = match[2:-1]1000    plugin_id = constants.PLUGIN_ID_RE.match(core).group(0)1001    if self._plugin_stack and self._plugin_stack[-1]["id"] == plugin_id:1002      self._plugin_stack.pop()1003      if plugin_id in self._ALLOWED_HTML_TAGS:1004        if plugin_id == "code":1005          self._formatting_handler.HandleCodeBlockClose(1006              input_line,1007              output_stream)1008        else:1009          self._formatting_handler.HandleHtmlClose(1010              input_line,1011              output_stream,1012              plugin_id)1013      elif plugin_id == "g:plusone":1014        self._formatting_handler.HandleGPlusClose(input_line, output_stream)1015      elif plugin_id == "wiki:comment":1016        self._formatting_handler.HandleCommentClose(input_line, output_stream)1017      elif plugin_id == "wiki:gadget":1018        # A warning was already issued on the opening tag.1019        self._formatting_handler.HandleEscapedText(1020            input_line,1021            output_stream,1022            match)1023      elif plugin_id == "wiki:video":1024        self._formatting_handler.HandleVideoClose(input_line, output_stream)1025      elif plugin_id == "wiki:toc":1026        # A warning was already issued on the opening tag.1027        pass1028      else:1029        self._warning_method(1030            input_line,1031            u"Unknown but matching plugin end was given, outputting "1032            "as plain text:\n\t{0}".format(match))1033        # Wiki syntax put this class of error on its own line.1034        self._formatting_handler.HandleEscapedText(1035            input_line,1036            output_stream,1037            u"\n\n{0}\n\n".format(match))1038    else:1039      self._warning_method(1040          input_line,1041          u"Unknown/unmatched plugin end was given, outputting "1042          "as plain text with errors:\n\t{0}".format(match))1043      # Wiki syntax put this class of error on its own line,1044      # with a prefix error message, and did not display the tag namespace.1045      tag_without_ns = plugin_id.split(":", 1)[-1]1046      self._formatting_handler.HandleEscapedText(1047          input_line,1048          output_stream,1049          u"\n\nUnknown end tag for </{0}>\n\n".format(tag_without_ns))1050  def _HandleVariable(self, input_line, match, output_stream):1051    """Handle a variable.1052    Args:1053      input_line: Current line number being processed.1054      match: Matched text.1055      output_stream: Output Markdown file.1056    """1057    output = None1058    instructions = None1059    # If the variable is defined somewhere in the plugin stack, use it.1060    if self._plugin_stack:1061      value = None1062      for plugin_info in reversed(self._plugin_stack):1063        if match in plugin_info["params"]:1064          value = plugin_info["params"][match]1065          break1066      if value:1067        output = value1068    # Otherwise, it needs to be globally-defined.1069    if not output and match == "username":1070      output = "(TODO: Replace with username.)"1071      instructions = ("On Google Code this would have been replaced with the "1072                      "username of the current user, but GitHub has no "1073                      "direct support for equivalent behavior. It has been "1074                      "replaced with\n\t{0}\nConsider removing this altogether."1075                      .format(output))1076    elif not output and match == "email":1077      output = "(TODO: Replace with email address.)"1078      instructions = ("On Google Code this would have been replaced with the "1079                      "email address of the current user, but GitHub has no "1080                      "direct support for equivalent behavior. It has been "1081                      "replaced with\n\t{0}\nConsider removing this altogether."1082                      .format(output))1083    elif not output and match == "project":1084      if self._project:1085        output = self._project1086        instructions = (u"It has been replaced with static text containing the "1087                        "name of the project:\n\t{0}".format(self._project))1088      else:1089        output = "(TODO: Replace with project name.)"1090        instructions = ("Because no project name was specified, the text has "1091                        "been replaced with:\n\t{0}".format(output))1092    # Not defined anywhere, just treat as regular text.1093    if not output:1094      # Add surrounding %% back on.1095      output = u"%%{0}%%".format(match)1096    self._formatting_handler.HandleEscapedText(1097        input_line,1098        output_stream,1099        output)1100    if instructions:1101      self._warning_method(1102          input_line,1103          u"A variable substitution was performed with %%{0}%%. {1}"1104          .format(match, instructions))1105  def _HandleTag(self, input_line, tag, output_stream):1106    """Handle a tag, which has an opening and closing.1107    Args:1108      input_line: Current line number being processed.1109      tag: The tag to handle.1110      output_stream: Output Markdown file.1111    """1112    if tag not in self._open_tags:1113      self._OpenTag(input_line, tag, output_stream)1114    else:...formatting_handler.py
Source:formatting_handler.py  
1# Copyright 2014 Google Inc. All Rights Reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7#     http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14"""Handles converting of formatting."""15import cgi16from . import constants17class FormattingHandler(object):18  """Class that handles the conversion of formatting."""19  # Links with these URL schemas are auto-linked by GFM.20  _GFM_AUTO_URL_SCHEMAS = ("http://", "https://")21  # Images that were inlined automatically by Wiki syntax22  # had to have these URL schemas and image extensions.23  _IMAGE_URL_SCHEMAS = ("http://", "https://", "ftp://")24  _IMAGE_EXTENSIONS = (".png", ".gif", ".jpg", ".jpeg", ".svg")25  # Template for linking to a video.26  _VIDEO_TEMPLATE = (27      u"<a href='http://www.youtube.com/watch?feature=player_embedded&v={0}' "28      "target='_blank'><img src='http://img.youtube.com/vi/{0}/0.jpg' "29      "width='{1}' height={2} /></a>")30  # Formatting tags for list-to-HTML conversion.31  _HTML_LIST_TAGS = {32      "Numeric list": {33          "ListTag": "ol",34          "ItemTag": "li",35      },36      "Bulleted list": {37          "ListTag": "ul",38          "ItemTag": "li",39      },40      "Blockquote": {41          "ListTag": "blockquote",42          "ItemTag": None,43      },44  }45  # Formatting tags for formatting-to-HTML conversion.46  _HTML_FORMAT_TAGS = {47      "Bold": {48          "Markdown": "**",49          "HTML": "b",50      },51      "Italic": {52          "Markdown": "_",53          "HTML": "i",54      },55      "Strikethrough": {56          "Markdown": "~~",57          "HTML": "del",58      },59  }60  # How a single indentation is outputted.61  _SINGLE_INDENTATION = " " * 262  def __init__(self, warning_method, project, issue_map, symmetric_headers):63    """Create a formatting handler.64    Args:65        warning_method: A function to call to display a warning message.66        project: The name of the Google Code project for the Wiki page.67        issue_map: A dictionary of Google Code issues to GitHub issues.68        symmetric_headers: True if header denotations are symmetric.69    """70    self._warning_method = warning_method71    self._project = project72    self._issue_map = issue_map73    self._symmetric_headers = symmetric_headers74    # GFM has a quirk with nested blockquotes where a blank line is needed75    # after closing a nested blockquote while continuing into another.76    self._last_blockquote_indent = 077    # GFM will not apply formatting if whitespace surrounds the text being78    # formatted, but Wiki will. To work around this, we maintain a buffer79    # of text to be outputted, and when the tag is closed we can trim the80    # buffer before applying formatting. If the trimmed buffer is empty, we81    # can omit the formatting altogether to avoid GFM rendering issues.82    self._format_buffer = []83    # GitHub won't render formatting within HTML tags. Track if this is the84    # case so we can issue a warning and try a work-around.85    self._in_html = 0  # Number of tags currently open.86    self._in_code_block = False  # If we're in a code block in HTML.87    self._has_written_text = False  # If we've written text since the last tag.88    self._list_tags = []  # If writing HTML for lists, the current list tags.89    self._table_status = None  # Where we are in outputting an HTML table.90    # GitHub doesn't support HTML comments, so as a workaround we give91    # a bogus and empty <a> tag, which renders as nothing.92    self._in_comment = False93  def HandleHeaderOpen(self, input_line, output_stream, header_level):94    """Handle the output for opening a header.95    Args:96        input_line: Current line number being processed.97        output_stream: Output Markdown file.98        header_level: The header level.99    """100    if self._in_html:101      tag = u"h{0}".format(header_level)102      self.HandleHtmlOpen(input_line, output_stream, tag, {}, False)103    else:104      self._Write("#" * header_level + " ", output_stream)105  def HandleHeaderClose(106      self,107      input_line,108      output_stream,109      header_level):110    """Handle the output for closing a header.111    Args:112        input_line: Current line number being processed.113        output_stream: Output Markdown file.114        header_level: The header level.115    """116    if self._in_html:117      tag = u"h{0}".format(header_level)118      self.HandleHtmlClose(input_line, output_stream, tag)119    else:120      if self._symmetric_headers:121        self._Write(" " + "#" * header_level, output_stream)122  def HandleHRule(self, input_line, output_stream):123    """Handle the output for a horizontal rule.124    Args:125        input_line: Current line number being processed.126        output_stream: Output Markdown file.127    """128    if self._in_html:129      self.HandleHtmlOpen(input_line, output_stream, "hr", {}, True)130    else:131      # One newline needed before to separate from text, and not make a header.132      self._Write("\n---\n", output_stream)133  def HandleCodeBlockOpen(self, input_line, output_stream, specified_language):134    """Handle the output for starting a code block.135    Args:136        input_line: Current line number being processed.137        output_stream: Output Markdown file.138        specified_language: Language for the code block, or None.139    """140    if self._in_html:141      self._PrintHtmlWarning(input_line, "Code")142      self.HandleHtmlOpen(input_line, output_stream, "pre", {}, False)143      self.HandleHtmlOpen(input_line, output_stream, "code", {}, False)144    else:145      if not specified_language:146        specified_language = ""147      self._Write(u"```{0}\n".format(specified_language), output_stream)148    self._in_code_block = True149  def HandleCodeBlockClose(self, input_line, output_stream):150    """Handle the output for ending a code block.151    Args:152        input_line: Current line number being processed.153        output_stream: Output Markdown file.154    """155    self._in_code_block = False156    if self._in_html:157      self.HandleHtmlClose(input_line, output_stream, "code")158      self.HandleHtmlClose(input_line, output_stream, "pre")159    else:160      self._Write("```", output_stream)161  def HandleNumericListOpen(162      self,163      input_line,164      output_stream,165      indentation_level):166    """Handle the output for the opening of a numeric list item.167    Args:168        input_line: Current line number being processed.169        output_stream: Output Markdown file.170        indentation_level: The indentation level for the item.171    """172    if self._in_html:173      self._HandleHtmlListOpen(174          input_line,175          output_stream,176          indentation_level,177          "Numeric list")178    else:179      self._Indent(output_stream, indentation_level)180      # Just using any number implies a numbered item,181      # so we take the easy route.182      self._Write("1. ", output_stream)183  def HandleBulletListOpen(184      self,185      input_line,186      output_stream,187      indentation_level):188    """Handle the output for the opening of a bulleted list item.189    Args:190        input_line: Current line number being processed.191        output_stream: Output Markdown file.192        indentation_level: The indentation level for the item.193    """194    if self._in_html:195      self._HandleHtmlListOpen(196          input_line,197          output_stream,198          indentation_level,199          "Bulleted list")200    else:201      self._Indent(output_stream, indentation_level)202      self._Write("* ", output_stream)203  def HandleBlockQuoteOpen(204      self,205      input_line,206      output_stream,207      indentation_level):208    """Handle the output for the opening of a block quote line.209    Args:210        input_line: Current line number being processed.211        output_stream: Output Markdown file.212        indentation_level: The indentation level for the item.213    """214    if self._in_html:215      self._HandleHtmlListOpen(216          input_line,217          output_stream,218          indentation_level,219          "Blockquote")220    else:221      if self._last_blockquote_indent > indentation_level:222        self._Write("\n", output_stream)223      self._last_blockquote_indent = indentation_level224      # Blockquotes are nested not by indentation but by nesting.225      self._Write("> " * indentation_level, output_stream)226  def HandleListClose(self, input_line, output_stream):227    """Handle the output for the closing of a list.228    Args:229        input_line: Current line number being processed.230        output_stream: Output Markdown file.231    """232    if self._in_html:233      self._HandleHtmlListClose(input_line, output_stream)234  def HandleParagraphBreak(self, unused_input_line, output_stream):235    """Handle the output for a new paragraph.236    Args:237        unused_input_line: Current line number being processed.238        output_stream: Output Markdown file.239    """240    self._Write("\n", output_stream)241  def HandleBoldOpen(self, input_line, unused_output_stream):242    """Handle the output for starting bold formatting.243    Args:244        input_line: Current line number being processed.245        unused_output_stream: Output Markdown file.246    """247    if self._in_html:248      self._PrintHtmlWarning(input_line, "Bold")249    # Open up another buffer.250    self._format_buffer.append("")251  def HandleBoldClose(self, input_line, output_stream):252    """Handle the output for ending bold formatting.253    Args:254        input_line: Current line number being processed.255        output_stream: Output Markdown file.256    """257    self._HandleFormatClose(input_line, output_stream, "Bold")258  def HandleItalicOpen(self, input_line, unused_output_stream):259    """Handle the output for starting italic formatting.260    Args:261        input_line: Current line number being processed.262        unused_output_stream: Output Markdown file.263    """264    if self._in_html:265      self._PrintHtmlWarning(input_line, "Italic")266    # Open up another buffer.267    self._format_buffer.append("")268  def HandleItalicClose(self, input_line, output_stream):269    """Handle the output for ending italic formatting.270    Args:271        input_line: Current line number being processed.272        output_stream: Output Markdown file.273    """274    self._HandleFormatClose(input_line, output_stream, "Italic")275  def HandleStrikethroughOpen(self, input_line, unused_output_stream):276    """Handle the output for starting strikethrough formatting.277    Args:278        input_line: Current line number being processed.279        unused_output_stream: Output Markdown file.280    """281    if self._in_html:282      self._PrintHtmlWarning(input_line, "Strikethrough")283    # Open up another buffer.284    self._format_buffer.append("")285  def HandleStrikethroughClose(self, input_line, output_stream):286    """Handle the output for ending strikethrough formatting.287    Args:288        input_line: Current line number being processed.289        output_stream: Output Markdown file.290    """291    self._HandleFormatClose(input_line, output_stream, "Strikethrough")292  def HandleSuperscript(self, unused_input_line, output_stream, text):293    """Handle the output for superscript text.294    Args:295        unused_input_line: Current line number being processed.296        output_stream: Output Markdown file.297        text: The text to output.298    """299    # Markdown currently has no dedicated markup for superscript.300    self._Write(u"<sup>{0}</sup>".format(text), output_stream)301  def HandleSubscript(self, unused_input_line, output_stream, text):302    """Handle the output for subscript text.303    Args:304        unused_input_line: Current line number being processed.305        output_stream: Output Markdown file.306        text: The text to output.307    """308    # Markdown currently has no dedicated markup for subscript.309    self._Write(u"<sub>{0}</sub>".format(text), output_stream)310  def HandleInlineCode(self, input_line, output_stream, code):311    """Handle the output for a code block.312    Args:313        input_line: Current line number being processed.314        output_stream: Output Markdown file.315        code: The code inlined.316    """317    if self._in_html:318      self.HandleHtmlOpen(input_line, output_stream, "code", {}, False)319      self.HandleText(input_line, output_stream, cgi.escape(code))320      self.HandleHtmlClose(input_line, output_stream, "code")321    else:322      # To render backticks within inline code, the surrounding tick count323      # must be one greater than the number of consecutive ticks in the code.324      # E.g.:325      #   `this is okay, no ticks in the code`326      #   `` `one consecutive tick in the code implies two in the delimiter` ``327      #   ``` `` `and two consecutive ticks in here implies three -> ```328      max_consecutive_ticks = 0329      consecutive_ticks = 0330      for char in code:331        if char == "`":332          consecutive_ticks += 1333          max_consecutive_ticks = max(max_consecutive_ticks, consecutive_ticks)334        else:335          consecutive_ticks = 0336      surrounding_ticks = "`" * (max_consecutive_ticks + 1)337      self._Write(u"{0}{1}{0}".format(surrounding_ticks, code), output_stream)338  def HandleTableCellBorder(self, input_line, output_stream):339    """Handle the output for a table cell border.340    Args:341        input_line: Current line number being processed.342        output_stream: Output Markdown file.343    """344    if self._in_html:345      if not self._table_status:346        # Starting a new table.347        self._PrintHtmlWarning(input_line, "Table")348        self.HandleHtmlOpen(input_line, output_stream, "table", {}, False)349        self.HandleHtmlOpen(input_line, output_stream, "thead", {}, False)350        self.HandleHtmlOpen(input_line, output_stream, "th", {}, False)351        self._table_status = "header"352      elif self._table_status == "header":353        # Header cell. Close the previous cell, open the next one.354        self.HandleHtmlClose(input_line, output_stream, "th")355        self.HandleHtmlOpen(input_line, output_stream, "th", {}, False)356      elif self._table_status == "rowstart":357        # First row cell.358        self.HandleHtmlOpen(input_line, output_stream, "tr", {}, False)359        self.HandleHtmlOpen(input_line, output_stream, "td", {}, False)360        self._table_status = "row"361      elif self._table_status == "row":362        # Row cell. Close the previous cell, open the next one.363        self.HandleHtmlClose(input_line, output_stream, "td")364        self.HandleHtmlOpen(input_line, output_stream, "td", {}, False)365    else:366      self._Write("|", output_stream)367  def HandleTableRowEnd(self, input_line, output_stream):368    """Handle the output for a table row end.369    Args:370        input_line: Current line number being processed.371        output_stream: Output Markdown file.372    """373    if self._in_html:374      if self._table_status == "header":375        # Closing header. Close the previous cell and header, start the body.376        self.HandleHtmlClose(input_line, output_stream, "th")377        self.HandleHtmlClose(input_line, output_stream, "thead")378        self.HandleHtmlOpen(input_line, output_stream, "tbody", {}, False)379      elif self._table_status == "row":380        # Closing row. Close the previous cell and row.381        self.HandleHtmlClose(input_line, output_stream, "td")382        self.HandleHtmlClose(input_line, output_stream, "tr")383      self._table_status = "rowstart"384    else:385      self._Write("|", output_stream)386  def HandleTableClose(self, input_line, output_stream):387    """Handle the output for a table end.388    Args:389        input_line: Current line number being processed.390        output_stream: Output Markdown file.391    """392    if self._in_html:393      # HandleTableRowEnd will have been called by this point.394      # All we need to do is close the body and table.395      self.HandleHtmlClose(input_line, output_stream, "tbody")396      self.HandleHtmlClose(input_line, output_stream, "table")397      self._table_status = None398  def HandleTableHeader(self, input_line, output_stream, columns):399    """Handle the output for starting a table header.400    Args:401        input_line: Current line number being processed.402        output_stream: Output Markdown file.403        columns: Column sizes.404    """405    if self._in_html:406      return407    self.HandleText(input_line, output_stream, "\n")408    for column_width in columns:409      self.HandleTableCellBorder(input_line, output_stream)410      # Wiki tables are left-aligned, which takes one character to specify.411      self._Write(u":{0}".format("-" * (column_width - 1)), output_stream)412    self.HandleTableCellBorder(input_line, output_stream)413  def HandleLink(self, input_line, output_stream, target, description):414    """Handle the output of a link.415    Args:416        input_line: Current line number being processed.417        output_stream: Output Markdown file.418        target: The target URL of the link.419        description: The description for the target.420    """421    # There are six cases to handle in general:422    # 1. Image link with image description:423    #   Link to image, using image from description as content.424    # 2. Image link with non-image description:425    #   Link to image, using description text as content.426    # 3. Image link with no description:427    #   Inline image.428    # 4. URL link with image description:429    #   Link to URL, using image from description as content.430    # 5. URL link with non-image description:431    #   Link to URL, using description text as content.432    # 6. URL link with no description:433    #   Link to URL, using URL as content.434    # Only in case 3 is no actual link present.435    is_image = target.endswith(self._IMAGE_EXTENSIONS)436    is_image_description = (description and437                            description.startswith(self._IMAGE_URL_SCHEMAS) and438                            description.endswith(self._IMAGE_EXTENSIONS))439    if self._in_html:440      self._PrintHtmlWarning(input_line, "Link")441      # Handle inline image case.442      if is_image and not description:443        self.HandleHtmlOpen(444            input_line,445            output_stream,446            "img",447            {"src": target},448            True)449      else:450        # Handle link cases.451        self.HandleHtmlOpen(452            input_line,453            output_stream,454            "a",455            {"href": target},456            False)457        if is_image_description:458          self.HandleHtmlOpen(459              input_line,460              output_stream,461              "img",462              {"src": description},463              True)464        else:465          description = description or target466          self._Write(cgi.escape(description), output_stream)467        self.HandleHtmlClose(input_line, output_stream, "a")468    else:469      # If description is None, this means that only the URL was given. We'd470      # like to let GFM auto-link it, because it's prettier. However, while Wiki471      # syntax would auto-link a variety of URL schemes, GFM only supports http472      # and https. In other cases and in the case of images, we explicitly link.473      is_autolinkable = target.startswith(self._GFM_AUTO_URL_SCHEMAS)474      autolink = (description is None) and is_autolinkable and (not is_image)475      if autolink:476        self._Write(target, output_stream)477      else:478        # If the descriptive text looks like an image URL, Wiki syntax would479        # make the link description an inlined image. We do this by setting480        # the output description to the syntax used to inline an image.481        if is_image_description:482          description = u"".format(description)483        elif description:484          description = self._Escape(description)485        else:486          description = target487          is_image_description = is_image488        # Prefix ! if linking to an image without a text description.489        prefix = "!" if is_image and is_image_description else ""490        output = u"{0}[{1}]({2})".format(prefix, description, target)491        self._Write(output, output_stream)492  def HandleWiki(self, input_line, output_stream, target, text):493    """Handle the output of a wiki link.494    Args:495        input_line: Current line number being processed.496        output_stream: Output Markdown file.497        target: The target URL of the link.498        text: The description for the target.499    """500    # A wiki link is just like a regular link, except under the wiki directory.501    # At this point we make the text equal to the original target if unset.502    # We do however append ".md", assuming the wiki files now have that extension.503    self.HandleLink(input_line, output_stream, target + ".md", text or target)504  def HandleIssue(self, input_line, output_stream, prefix, issue):505    """Handle the output for an auto-linked issue.506    Args:507        input_line: Current line number being processed.508        output_stream: Output Markdown file.509        prefix: The text that came before the issue number.510        issue: The issue number.511    """512    handled = False513    # Preferred handler is to map the Google Code issue to a GitHub issue.514    if self._issue_map and issue in self._issue_map:515      migrated_issue_url = self._issue_map[issue]516      migrated_issue = migrated_issue_url.rsplit("/", 1)[1]517      self.HandleLink(518          input_line,519          output_stream,520          migrated_issue_url,521          u"{0}{1}".format(prefix, migrated_issue))522      handled = True523      instructions = (u"In the output, it has been linked to the migrated issue "524                      "on GitHub: {0}. Please verify this issue on GitHub "525                      "corresponds to the original issue on Google Code. "526                      .format(migrated_issue))527    elif self._issue_map:528      instructions = ("However, it was not found in the issue migration map; "529                      "please verify that this issue has been correctly "530                      "migrated to GitHub and that the issue mapping is put "531                      "in the issue migration map file. ")532    else:533      instructions = ("However, no issue migration map was specified. You "534                      "can use issue_migration.py to migrate your Google "535                      "Code issues to GitHub, and supply the resulting issue "536                      "migration map file to this converter. Your old issues "537                      "will be auto-linked to your migrated issues. ")538    # If we couldn't handle it in the map, try linking to the old issue.539    if not handled and self._project:540      old_link = (u"https://code.google.com/p/{0}/issues/detail?id={1}"541                  .format(self._project, issue))542      self.HandleLink(543          input_line,544          output_stream,545          old_link,546          u"{0}{1}".format(prefix, issue))547      handled = True548      instructions += (u"As a placeholder, the text has been modified to "549                       "link to the original Google Code issue page:\n\t{0}"550                       .format(old_link))551    elif not handled:552      instructions += ("Additionally, because no project name was specified "553                       "the issue could not be linked to the original Google "554                       "Code issue page.")555    # Couldn't map it to GitHub nor could we link to the old issue.556    if not handled:557      output = u"{0}{1} (on Google Code)".format(prefix, issue)558      self._Write(output, output_stream)559      handled = True560      instructions += (u"The auto-link has been removed and the text has been "561                       "modified from '{0}{1}' to '{2}'."562                       .format(prefix, issue, output))563    self._warning_method(564        input_line,565        u"Issue {0} was auto-linked. {1}".format(issue, instructions))566  def HandleRevision(self, input_line, output_stream, prefix, revision):567    """Handle the output for an auto-linked issue.568    Args:569        input_line: Current line number being processed.570        output_stream: Output Markdown file.571        prefix: The text that came before the revision number.572        revision: The revision number.573    """574    # Google Code only auto-linked revision numbers, not hashes, so575    # revision auto-linking cannot be done for the conversion.576    if self._project:577      old_link = (u"https://code.google.com/p/{0}/source/detail?r={1}"578                  .format(self._project, revision))579      self.HandleLink(580          input_line,581          output_stream,582          old_link,583          u"{0}{1}".format(prefix, revision))584      instructions = (u"As a placeholder, the text has been modified to "585                      "link to the original Google Code source page:\n\t{0}"586                      .format(old_link))587    else:588      output = u"{0}{1} (on Google Code)".format(prefix, revision)589      self._Write(output, output_stream)590      instructions = (u"Additionally, because no project name was specified "591                      "the revision could not be linked to the original "592                      "Google Code source page. The auto-link has been removed "593                      "and the text has been modified from '{0}{1}' to '{2}'."594                      .format(prefix, revision, output))595    self._warning_method(596        input_line,597        u"Revision {0} was auto-linked. SVN revision numbers are not sensible "598        "in Git; consider updating this link or removing it altogether. {1}"599        .format(revision, instructions))600  def HandleHtmlOpen(601      self,602      unused_input_line,603      output_stream,604      html_tag,605      params,606      has_end):607    """Handle the output for an opening HTML tag.608    Args:609        unused_input_line: Current line number being processed.610        output_stream: Output Markdown file.611        html_tag: The HTML tag name.612        params: The parameters for the tag.613        has_end: True if the tag was self-closed.614    """615    core_params = self._SerializeHtmlParams(params)616    core = u"{0}{1}".format(html_tag, core_params)617    if has_end:618      output = u"<{0} />".format(core)619    else:620      output = u"<{0}>".format(core)621      self._in_html += 1622    self._Write(output, output_stream)623    self._has_written_text = False624  def HandleHtmlClose(self, unused_input_line, output_stream, html_tag):625    """Handle the output for an closing HTML tag.626    Args:627        unused_input_line: Current line number being processed.628        output_stream: Output Markdown file.629        html_tag: The HTML tag name.630    """631    self._Write(u"</{0}>".format(html_tag), output_stream)632    self._in_html -= 1633    self._has_written_text = False634  def HandleGPlusOpen(self, input_line, output_stream, unused_params):635    """Handle the output for opening a +1 button.636    Args:637        input_line: Current line number being processed.638        output_stream: Output Markdown file.639        unused_params: The parameters for the tag.640    """641    self._warning_method(642        input_line,643        "A Google+ +1 button was embedded on this page, but GitHub does not "644        "currently support this. Should it become supported in the future, "645        "see https://developers.google.com/+/web/+1button/ for more "646        "information.\nIt has been removed.")647  def HandleGPlusClose(self, unused_input_line, unused_output_stream):648    """Handle the output for closing a +1 button.649    Args:650        unused_input_line: Current line number being processed.651        unused_output_stream: Output Markdown file.652    """653    pass654  def HandleCommentOpen(self, input_line, output_stream):655    """Handle the output for opening a comment.656    Args:657        input_line: Current line number being processed.658        output_stream: Output Markdown file.659    """660    self._warning_method(661        input_line,662        "A comment was used in the wiki file, but GitHub does not currently "663        "support Markdown or HTML comments. As a work-around, the comment will "664        "be placed in a bogus and empty <a> tag.")665    self._Write("<a href='Hidden comment: ", output_stream)666    self._in_comment = True667  def HandleCommentClose(self, unused_input_line, output_stream):668    """Handle the output for closing a comment.669    Args:670        unused_input_line: Current line number being processed.671        output_stream: Output Markdown file.672    """673    self._in_comment = False674    self._Write("'></a>", output_stream)675  def HandleVideoOpen(self, input_line, output_stream, video_id, width, height):676    """Handle the output for opening a video.677    Args:678        input_line: Current line number being processed.679        output_stream: Output Markdown file.680        video_id: The video ID to play.681        width: Width of the resulting widget.682        height: Height of the resulting widget.683    """684    self._warning_method(685        input_line,686        "GFM does not support embedding the YouTube player directly. Instead "687        "an image link to the video is being used, maintaining sizing options.")688    output = self._VIDEO_TEMPLATE.format(video_id, width, height)689    self._Write(output, output_stream)690  def HandleVideoClose(self, unused_input_line, output_stream):691    """Handle the output for closing a video.692    Args:693        unused_input_line: Current line number being processed.694        output_stream: Output Markdown file.695    """696    # Everything was handled on the open side.697    pass698  def HandleText(self, unused_input_line, output_stream, text):699    """Handle the output of raw text.700    Args:701        unused_input_line: Current line number being processed.702        output_stream: Output Markdown file.703        text: The text to output.704    """705    self._Write(text, output_stream)706    self._has_written_text = True707  def HandleEscapedText(self, input_line, output_stream, text):708    """Handle the output of text, which should be escaped for Markdown.709    Args:710        input_line: Current line number being processed.711        output_stream: Output Markdown file.712        text: The text to output.713    """714    # If we're in HTML, Markdown isn't processed anyway.715    if self._in_html:716      self.HandleText(input_line, output_stream, text)717    else:718      self.HandleText(input_line, output_stream, self._Escape(text))719  def _PrintHtmlWarning(self, input_line, kind):720    """Warn about HTML translation being performed.721    Args:722        input_line: Current line number being processed.723        kind: The kind of tag being changed.724    """725    self._warning_method(726        input_line,727        u"{0} markup was used within HTML tags. Because GitHub does not "728        "support this, the tags have been translated to HTML. Please verify "729        "that the formatting is correct.".format(kind))730  def _HandleHtmlListOpen(731      self,732      input_line,733      output_stream,734      indentation_level,735      kind):736    """Handle the output for opening an HTML list.737    Args:738        input_line: Current line number being processed.739        output_stream: Output Markdown file.740        indentation_level: The indentation level for the item.741        kind: The kind of list being opened.742    """743    # Determine if this is a new list, and if a previous list was closed.744    if self._list_tags:745      top_tag = self._list_tags[-1]746      if top_tag["indent"] != indentation_level:747        # Opening a new nested list. Indentation level will always be greater,748        # because for it to have gone down, the list would have been closed.749        new_list = True750        closing = False751      elif top_tag["kind"] != kind:752        # Closed the previous list, started a new one.753        new_list = True754        closing = True755      else:756        # Same list, already opened.757        new_list = False758        closing = False759    else:760      new_list = True761      closing = False762    # If we need to, close the prior list.763    if closing:764      self._HandleHtmlListClose(input_line, output_stream)765    # Grab the tags we'll be using.766    list_tag = self._HTML_LIST_TAGS[kind]["ListTag"]767    item_tag = self._HTML_LIST_TAGS[kind]["ItemTag"]768    # If this is a new list, note it in the stack and open it.769    if new_list:770      new_tag = {771          "indent": indentation_level,772          "kind": kind,773      }774      self._list_tags.append(new_tag)775      self._PrintHtmlWarning(input_line, kind)776      self.HandleHtmlOpen(input_line, output_stream, list_tag, {}, False)777    else:778      # Not a new list, close the previously outputted item.779      if item_tag:780        self.HandleHtmlClose(input_line, output_stream, item_tag)781    # Open up a new list item782    if item_tag:783      self.HandleHtmlOpen(input_line, output_stream, item_tag, {}, False)784  def _HandleHtmlListClose(self, input_line, output_stream):785    """Handle the output for closing an HTML list.786    Args:787        input_line: Current line number being processed.788        output_stream: Output Markdown file.789    """790    # Fix index error if list_tags is empty.791    if len(self._list_tags) == 0:792      self._warning_method(input_line, "HtmlListClose without list_tags?")793      self._list_tags = [ { "indent": 0, "kind": "Bulleted list" } ]794    top_tag = self._list_tags[-1]795    kind = top_tag["kind"]796    self._list_tags.pop()797    # Grab the tags we'll be using.798    list_tag = self._HTML_LIST_TAGS[kind]["ListTag"]799    item_tag = self._HTML_LIST_TAGS[kind]["ItemTag"]800    # Close the previously outputted item and the list.801    if item_tag:802      self.HandleHtmlClose(input_line, output_stream, item_tag)803    self.HandleHtmlClose(input_line, output_stream, list_tag)804  def _HandleFormatClose(self, input_line, output_stream, kind):805    """Handle the output of a closing format tag.806    Args:807        input_line: Current line number being processed.808        output_stream: Output Markdown file.809        kind: The formatting kind.810    """811    if self._format_buffer:812      # End redirection.813      format_buffer = self._format_buffer[-1]814      self._format_buffer.pop()815      # Don't do anything if we didn't buffer, or it was only whitespace.816      format_buffer = format_buffer.strip()817      if not format_buffer:818        return819      if self._in_html:820        tag = self._HTML_FORMAT_TAGS[kind]["HTML"]821        self.HandleHtmlOpen(input_line, output_stream, tag, {}, False)822        self.HandleText(input_line, output_stream, format_buffer)823        self.HandleHtmlClose(input_line, output_stream, tag)824      else:825        tag = self._HTML_FORMAT_TAGS[kind]["Markdown"]826        self._Write(u"{0}{1}{0}".format(tag, format_buffer), output_stream)827    else:828      self._warning_method(input_line, u"Re-closed '{0}', ignoring.".format(tag))829  def _Indent(self, output_stream, indentation_level):830    """Output indentation.831    Args:832        output_stream: Output Markdown file.833        indentation_level: Number of indentations to output.834    """835    self._Write(self._SINGLE_INDENTATION * indentation_level, output_stream)836  def _Escape(self, text):837    """Escape Wiki text to be suitable in Markdown.838    Args:839        text: Input Wiki text.840    Returns:841        Escaped text for Markdown.842    """843    text = text.replace("*", r"\*")844    text = text.replace("_", r"\_")845    # If we find a plugin-like bit of text, escape the angle-brackets.846    for plugin_re in [constants.PLUGIN_RE, constants.PLUGIN_END_RE]:847      while plugin_re.search(text):848        match = plugin_re.search(text)849        before_match = text[:match.start()]850        after_match = text[match.end():]851        escaped_match = match.group(0).replace("<", "<").replace(">", ">")852        text = u"{0}{1}{2}".format(before_match, escaped_match, after_match)853    # In Markdown, if a newline is preceeded by two spaces it breaks the line.854    # For Wiki text, this is not the case, so we strip such endings off.855    while text.endswith("  \n"):856      text = text[:-len("  \n")] + "\n"857    return text858  def _SerializeHtmlParams(self, params):859    """Serialize parameters for an HTML tag.860    Args:861        params: The parameters for the tag.862    Returns:863        Serialized parameters.864    """865    core_params = ""866    for name, value in params.items():867      if "'" not in value:868        quote = "'"869      else:870        quote = "\""871      core_params += u" {0}={1}{2}{1}".format(name, quote, value)872    return core_params873  def _Write(self, text, output_stream):874    """Write text to the output stream, taking into account any redirection.875    Args:876        text: Input raw text.877        output_stream: Output Markdown file.878    """879    if not text:880      return881    if not self._in_comment and self._in_html:882      if self._in_code_block:883        text = cgi.escape(text)884      if self._in_code_block or self._has_written_text:885        text = text.replace("\n", "<br>\n")886    if self._in_comment:887      text = text.replace("'", "\"")888    if self._format_buffer:889      # Buffering is occuring, add to buffer.890      self._format_buffer[-1] += text891    else:892      # No buffering occuring, just output it....Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
