codekingpro/portable-devtools
114k
1# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License"). You4# may not use this file except in compliance with the License. A copy of5# the License is located at6#7# http://aws.amazon.com/apache2.0/8#9# or in the "license" file accompanying this file. This file is10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF11# ANY KIND, either express or implied. See the License for the specific12# language governing permissions and limitations under the License.13 14import logging15 16logger = logging.getLogger('bcdocs')17# Terminal punctuation where a space is not needed before.18PUNCTUATION_CHARACTERS = ('.', ',', '?', '!', ':', ';')19 20 21class BaseStyle:22 def __init__(self, doc, indent_width=2):23 self.doc = doc24 self.indent_width = indent_width25 self._indent = 026 self.keep_data = True27 28 @property29 def indentation(self):30 return self._indent31 32 @indentation.setter33 def indentation(self, value):34 self._indent = value35 36 def new_paragraph(self):37 return '\n%s' % self.spaces()38 39 def indent(self):40 self._indent += 141 42 def dedent(self):43 if self._indent > 0:44 self._indent -= 145 46 def spaces(self):47 return ' ' * (self._indent * self.indent_width)48 49 def bold(self, s):50 return s51 52 def ref(self, link, title=None):53 return link54 55 def h2(self, s):56 return s57 58 def h3(self, s):59 return s60 61 def underline(self, s):62 return s63 64 def italics(self, s):65 return s66 67 def add_trailing_space_to_previous_write(self):68 # Adds a trailing space if none exists. This is mainly used for69 # ensuring inline code and links are separated from surrounding text.70 last_write = self.doc.pop_write()71 if last_write is None:72 last_write = ''73 if last_write != '' and last_write[-1] != ' ':74 last_write += ' '75 self.doc.push_write(last_write)76 77 78class ReSTStyle(BaseStyle):79 def __init__(self, doc, indent_width=2):80 BaseStyle.__init__(self, doc, indent_width)81 self.do_p = True82 self.a_href = None83 self.list_depth = 084 85 def new_paragraph(self):86 self.doc.write('\n\n%s' % self.spaces())87 88 def new_line(self):89 self.doc.write('\n%s' % self.spaces())90 91 def _start_inline(self, markup):92 # Insert space between any directly adjacent bold and italic inlines to93 # avoid situations like ``**abc***def*``.94 try:95 last_write = self.doc.peek_write()96 except IndexError:97 pass98 else:99 if last_write in ('*', '**') and markup in ('*', '**'):100 self.doc.write(' ')101 self.doc.write(markup)102 103 def _end_inline(self, markup):104 # Remove empty and self-closing tags like ``<b></b>`` and ``<b/>``.105 # If we simply translate that directly then we end up with something106 # like ****, which rst will assume is a heading instead of an empty107 # bold.108 last_write = self.doc.pop_write()109 if last_write == markup:110 return111 self.doc.push_write(last_write)112 self.doc.write(markup)113 114 def start_bold(self, attrs=None):115 self._start_inline('**')116 117 def end_bold(self):118 self._end_inline('**')119 120 def start_b(self, attrs=None):121 self.doc.do_translation = True122 self.start_bold(attrs)123 124 def end_b(self):125 self.doc.do_translation = False126 self.end_bold()127 128 def bold(self, s):129 if s:130 self.start_bold()131 self.doc.write(s)132 self.end_bold()133 134 def ref(self, title, link=None):135 if link is None:136 link = title137 self.doc.write(f':doc:`{title} <{link}>`')138 139 def _heading(self, s, border_char):140 border = border_char * len(s)141 self.new_paragraph()142 self.doc.write(f'{border}\n{s}\n{border}')143 self.new_paragraph()144 145 def h1(self, s):146 self._heading(s, '*')147 148 def h2(self, s):149 self._heading(s, '=')150 151 def h3(self, s):152 self._heading(s, '-')153 154 def start_italics(self, attrs=None):155 self._start_inline('*')156 157 def end_italics(self):158 self._end_inline('*')159 160 def italics(self, s):161 if s:162 self.start_italics()163 self.doc.write(s)164 self.end_italics()165 166 def start_p(self, attrs=None):167 if self.do_p:168 self.doc.write('\n\n%s' % self.spaces())169 170 def end_p(self):171 if self.do_p:172 self.doc.write('\n\n%s' % self.spaces())173 174 def start_code(self, attrs=None):175 self.doc.do_translation = True176 self.add_trailing_space_to_previous_write()177 self._start_inline('``')178 179 def end_code(self):180 self.doc.do_translation = False181 self._end_inline('``')182 183 def code(self, s):184 if s:185 self.start_code()186 self.doc.write(s)187 self.end_code()188 189 def start_note(self, attrs=None):190 self.new_paragraph()191 self.doc.write('.. note::')192 self.indent()193 self.new_paragraph()194 195 def end_note(self):196 self.dedent()197 self.new_paragraph()198 199 def start_important(self, attrs=None):200 self.new_paragraph()201 self.doc.write('.. warning::')202 self.indent()203 self.new_paragraph()204 205 def end_important(self):206 self.dedent()207 self.new_paragraph()208 209 def start_danger(self, attrs=None):210 self.new_paragraph()211 self.doc.write('.. danger::')212 self.indent()213 self.new_paragraph()214 215 def end_danger(self):216 self.dedent()217 self.new_paragraph()218 219 def start_a(self, attrs=None):220 # Write an empty space to guard against zero whitespace221 # before an "a" tag. Example: hi<a>Example</a>222 self.add_trailing_space_to_previous_write()223 if attrs:224 for attr_key, attr_value in attrs:225 if attr_key == 'href':226 # Removes unnecessary whitespace around the href link.227 # Example: <a href=" http://example.com ">Example</a>228 self.a_href = attr_value.strip()229 self.doc.write('`')230 else:231 # There are some model documentation that232 # looks like this: <a>DescribeInstances</a>.233 # In this case we just write out an empty234 # string.235 self.doc.write(' ')236 self.doc.do_translation = True237 238 def link_target_definition(self, refname, link):239 self.doc.writeln(f'.. _{refname}: {link}')240 241 def sphinx_reference_label(self, label, text=None):242 if text is None:243 text = label244 if self.doc.target == 'html':245 self.doc.write(f':ref:`{text} <{label}>`')246 else:247 self.doc.write(text)248 249 def _clean_link_text(self):250 doc = self.doc251 # Pop till we reach the link start character to retrieve link text.252 last_write = doc.pop_write()253 while not last_write.startswith('`'):254 last_write = doc.pop_write() + last_write255 if last_write != '':256 # Remove whitespace from the start of link text.257 if last_write.startswith('` '):258 last_write = f'`{last_write[1:].lstrip(" ")}'259 doc.push_write(last_write)260 261 def end_a(self, next_child=None):262 self.doc.do_translation = False263 if self.a_href:264 self._clean_link_text()265 last_write = self.doc.pop_write()266 last_write = last_write.rstrip(' ')267 if last_write and last_write != '`':268 if ':' in last_write:269 last_write = last_write.replace(':', r'\:')270 self.doc.push_write(last_write)271 self.doc.push_write(' <%s>`__' % self.a_href)272 elif last_write == '`':273 # Look at start_a(). It will do a self.doc.write('`')274 # which is the start of the link title. If that is the275 # case then there was no link text. We should just276 # use an inline link. The syntax of this is277 # `<http://url>`_278 self.doc.push_write('`<%s>`__' % self.a_href)279 else:280 self.doc.push_write(self.a_href)281 self.doc.hrefs[self.a_href] = self.a_href282 self.doc.write('`__')283 self.a_href = None284 285 def start_i(self, attrs=None):286 self.doc.do_translation = True287 self.start_italics()288 289 def end_i(self):290 self.doc.do_translation = False291 self.end_italics()292 293 def start_li(self, attrs=None):294 self.new_line()295 self.do_p = False296 self.doc.write('* ')297 298 def end_li(self):299 self.do_p = True300 self.new_line()301 302 def li(self, s):303 if s:304 self.start_li()305 self.doc.writeln(s)306 self.end_li()307 308 def start_ul(self, attrs=None):309 if self.list_depth != 0:310 self.indent()311 self.list_depth += 1312 self.new_paragraph()313 314 def end_ul(self):315 self.list_depth -= 1316 if self.list_depth != 0:317 self.dedent()318 self.new_paragraph()319 320 def start_ol(self, attrs=None):321 # TODO: Need to control the bullets used for LI items322 if self.list_depth != 0:323 self.indent()324 self.list_depth += 1325 self.new_paragraph()326 327 def end_ol(self):328 self.list_depth -= 1329 if self.list_depth != 0:330 self.dedent()331 self.new_paragraph()332 333 def start_examples(self, attrs=None):334 self.doc.keep_data = False335 336 def end_examples(self):337 self.doc.keep_data = True338 339 def start_fullname(self, attrs=None):340 self.doc.keep_data = False341 342 def end_fullname(self):343 self.doc.keep_data = True344 345 def start_codeblock(self, attrs=None):346 self.doc.write('::')347 self.indent()348 self.new_paragraph()349 350 def end_codeblock(self):351 self.dedent()352 self.new_paragraph()353 354 def codeblock(self, code):355 """356 Literal code blocks are introduced by ending a paragraph with357 the special marker ::. The literal block must be indented358 (and, like all paragraphs, separated from the surrounding359 ones by blank lines).360 """361 self.start_codeblock()362 self.doc.writeln(code)363 self.end_codeblock()364 365 def toctree(self):366 if self.doc.target == 'html':367 self.doc.write('\n.. toctree::\n')368 self.doc.write(' :maxdepth: 1\n')369 self.doc.write(' :titlesonly:\n\n')370 else:371 self.start_ul()372 373 def tocitem(self, item, file_name=None):374 if self.doc.target == 'man':375 self.li(item)376 else:377 if file_name:378 self.doc.writeln(' %s' % file_name)379 else:380 self.doc.writeln(' %s' % item)381 382 def hidden_toctree(self):383 if self.doc.target == 'html':384 self.doc.write('\n.. toctree::\n')385 self.doc.write(' :maxdepth: 1\n')386 self.doc.write(' :hidden:\n\n')387 388 def hidden_tocitem(self, item):389 if self.doc.target == 'html':390 self.tocitem(item)391 392 def table_of_contents(self, title=None, depth=None):393 self.doc.write('.. contents:: ')394 if title is not None:395 self.doc.writeln(title)396 if depth is not None:397 self.doc.writeln(' :depth: %s' % depth)398 399 def start_sphinx_py_class(self, class_name):400 self.new_paragraph()401 self.doc.write('.. py:class:: %s' % class_name)402 self.indent()403 self.new_paragraph()404 405 def end_sphinx_py_class(self):406 self.dedent()407 self.new_paragraph()408 409 def start_sphinx_py_method(self, method_name, parameters=None):410 self.new_paragraph()411 content = '.. py:method:: %s' % method_name412 if parameters is not None:413 content += '(%s)' % parameters414 self.doc.write(content)415 self.indent()416 self.new_paragraph()417 418 def end_sphinx_py_method(self):419 self.dedent()420 self.new_paragraph()421 422 def start_sphinx_py_attr(self, attr_name):423 self.new_paragraph()424 self.doc.write('.. py:attribute:: %s' % attr_name)425 self.indent()426 self.new_paragraph()427 428 def end_sphinx_py_attr(self):429 self.dedent()430 self.new_paragraph()431 432 def write_py_doc_string(self, docstring):433 docstring_lines = docstring.splitlines()434 for docstring_line in docstring_lines:435 self.doc.writeln(docstring_line)436 437 def external_link(self, title, link):438 if self.doc.target == 'html':439 self.doc.write(f'`{title} <{link}>`_')440 else:441 self.doc.write(title)442 443 def internal_link(self, title, page):444 if self.doc.target == 'html':445 self.doc.write(f':doc:`{title} <{page}>`')446 else:447 self.doc.write(title)448 