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"![]({0})".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!!