Inital Commit
This commit is contained in:
13
Function Source Files/botocore/docs/bcdoc/__init__.py
Normal file
13
Function Source Files/botocore/docs/bcdoc/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
||||
# may not use this file except in compliance with the License. A copy of
|
||||
# the License is located at
|
||||
#
|
||||
# http://aws.amazon.com/apache2.0/
|
||||
#
|
||||
# or in the "license" file accompanying this file. This file is
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
# ANY KIND, either express or implied. See the License for the specific
|
||||
# language governing permissions and limitations under the License.
|
||||
__version__ = '0.16.0'
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
315
Function Source Files/botocore/docs/bcdoc/docstringparser.py
Normal file
315
Function Source Files/botocore/docs/bcdoc/docstringparser.py
Normal file
@@ -0,0 +1,315 @@
|
||||
# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
||||
# may not use this file except in compliance with the License. A copy of
|
||||
# the License is located at
|
||||
#
|
||||
# http://aws.amazon.com/apache2.0/
|
||||
#
|
||||
# or in the "license" file accompanying this file. This file is
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
# ANY KIND, either express or implied. See the License for the specific
|
||||
# language governing permissions and limitations under the License.
|
||||
from html.parser import HTMLParser
|
||||
from itertools import zip_longest
|
||||
|
||||
PRIORITY_PARENT_TAGS = ('code', 'a')
|
||||
OMIT_NESTED_TAGS = ('span', 'i', 'code', 'a')
|
||||
OMIT_SELF_TAGS = ('i', 'b')
|
||||
HTML_BLOCK_DISPLAY_TAGS = ('p', 'note', 'ul', 'li')
|
||||
|
||||
|
||||
class DocStringParser(HTMLParser):
|
||||
"""
|
||||
A simple HTML parser. Focused on converting the subset of HTML
|
||||
that appears in the documentation strings of the JSON models into
|
||||
simple ReST format.
|
||||
"""
|
||||
|
||||
def __init__(self, doc):
|
||||
self.tree = None
|
||||
self.doc = doc
|
||||
super().__init__()
|
||||
|
||||
def reset(self):
|
||||
HTMLParser.reset(self)
|
||||
self.tree = HTMLTree(self.doc)
|
||||
|
||||
def feed(self, data):
|
||||
super().feed(data)
|
||||
self.tree.write()
|
||||
self.tree = HTMLTree(self.doc)
|
||||
|
||||
def close(self):
|
||||
super().close()
|
||||
# Write if there is anything remaining.
|
||||
self.tree.write()
|
||||
self.tree = HTMLTree(self.doc)
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
self.tree.add_tag(tag, attrs=attrs)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
self.tree.add_tag(tag, is_start=False)
|
||||
|
||||
def handle_data(self, data):
|
||||
self.tree.add_data(data)
|
||||
|
||||
|
||||
class HTMLTree:
|
||||
"""
|
||||
A tree which handles HTML nodes. Designed to work with a python HTML parser,
|
||||
meaning that the current_node will be the most recently opened tag. When
|
||||
a tag is closed, the current_node moves up to the parent node.
|
||||
"""
|
||||
|
||||
def __init__(self, doc):
|
||||
self.doc = doc
|
||||
self.head = StemNode()
|
||||
self.current_node = self.head
|
||||
self.unhandled_tags = []
|
||||
|
||||
def add_tag(self, tag, attrs=None, is_start=True):
|
||||
if not self._doc_has_handler(tag, is_start):
|
||||
self.unhandled_tags.append(tag)
|
||||
return
|
||||
|
||||
if is_start:
|
||||
node = TagNode(tag, attrs)
|
||||
self.current_node.add_child(node)
|
||||
self.current_node = node
|
||||
else:
|
||||
self.current_node = self.current_node.parent
|
||||
|
||||
def _doc_has_handler(self, tag, is_start):
|
||||
if is_start:
|
||||
handler_name = 'start_%s' % tag
|
||||
else:
|
||||
handler_name = 'end_%s' % tag
|
||||
|
||||
return hasattr(self.doc.style, handler_name)
|
||||
|
||||
def add_data(self, data):
|
||||
self.current_node.add_child(DataNode(data))
|
||||
|
||||
def write(self):
|
||||
self.head.write(self.doc)
|
||||
|
||||
|
||||
class Node:
|
||||
def __init__(self, parent=None):
|
||||
self.parent = parent
|
||||
|
||||
def write(self, doc):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class StemNode(Node):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.children = []
|
||||
|
||||
def add_child(self, child):
|
||||
child.parent = self
|
||||
self.children.append(child)
|
||||
|
||||
def write(self, doc):
|
||||
self.collapse_whitespace()
|
||||
self._write_children(doc)
|
||||
|
||||
def _write_children(self, doc):
|
||||
for child, next_child in zip_longest(self.children, self.children[1:]):
|
||||
if isinstance(child, TagNode) and next_child is not None:
|
||||
child.write(doc, next_child)
|
||||
else:
|
||||
child.write(doc)
|
||||
|
||||
def is_whitespace(self):
|
||||
return all(child.is_whitespace() for child in self.children)
|
||||
|
||||
def startswith_whitespace(self):
|
||||
return self.children and self.children[0].startswith_whitespace()
|
||||
|
||||
def endswith_whitespace(self):
|
||||
return self.children and self.children[-1].endswith_whitespace()
|
||||
|
||||
def lstrip(self):
|
||||
while self.children and self.children[0].is_whitespace():
|
||||
self.children = self.children[1:]
|
||||
if self.children:
|
||||
self.children[0].lstrip()
|
||||
|
||||
def rstrip(self):
|
||||
while self.children and self.children[-1].is_whitespace():
|
||||
self.children = self.children[:-1]
|
||||
if self.children:
|
||||
self.children[-1].rstrip()
|
||||
|
||||
def collapse_whitespace(self):
|
||||
"""Remove collapsible white-space from HTML.
|
||||
|
||||
HTML in docstrings often contains extraneous white-space around tags,
|
||||
for readability. Browsers would collapse this white-space before
|
||||
rendering. If not removed before conversion to RST where white-space is
|
||||
part of the syntax, for example for indentation, it can result in
|
||||
incorrect output.
|
||||
"""
|
||||
self.lstrip()
|
||||
self.rstrip()
|
||||
for child in self.children:
|
||||
child.collapse_whitespace()
|
||||
|
||||
|
||||
class TagNode(StemNode):
|
||||
"""
|
||||
A generic Tag node. It will verify that handlers exist before writing.
|
||||
"""
|
||||
|
||||
def __init__(self, tag, attrs=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.attrs = attrs
|
||||
self.tag = tag
|
||||
|
||||
def _has_nested_tags(self):
|
||||
# Returns True if any children are TagNodes and False otherwise.
|
||||
return any(isinstance(child, TagNode) for child in self.children)
|
||||
|
||||
def write(self, doc, next_child=None):
|
||||
prioritize_nested_tags = (
|
||||
self.tag in OMIT_SELF_TAGS and self._has_nested_tags()
|
||||
)
|
||||
prioritize_parent_tag = (
|
||||
isinstance(self.parent, TagNode)
|
||||
and self.parent.tag in PRIORITY_PARENT_TAGS
|
||||
and self.tag in OMIT_NESTED_TAGS
|
||||
)
|
||||
if prioritize_nested_tags or prioritize_parent_tag:
|
||||
self._write_children(doc)
|
||||
return
|
||||
|
||||
self._write_start(doc)
|
||||
self._write_children(doc)
|
||||
self._write_end(doc, next_child)
|
||||
|
||||
def collapse_whitespace(self):
|
||||
"""Remove collapsible white-space.
|
||||
|
||||
All tags collapse internal whitespace. Block-display HTML tags also
|
||||
strip all leading and trailing whitespace.
|
||||
|
||||
Approximately follows the specification used in browsers:
|
||||
https://www.w3.org/TR/css-text-3/#white-space-rules
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
|
||||
"""
|
||||
if self.tag in HTML_BLOCK_DISPLAY_TAGS:
|
||||
self.lstrip()
|
||||
self.rstrip()
|
||||
# Collapse whitespace in situations like ``</b> <i> foo</i>`` into
|
||||
# ``</b><i> foo</i>``.
|
||||
for prev, cur in zip(self.children[:-1], self.children[1:]):
|
||||
if (
|
||||
isinstance(prev, DataNode)
|
||||
and prev.endswith_whitespace()
|
||||
and cur.startswith_whitespace()
|
||||
):
|
||||
cur.lstrip()
|
||||
# Same logic, but for situations like ``<b>bar </b> <i>``:
|
||||
for cur, nxt in zip(self.children[:-1], self.children[1:]):
|
||||
if (
|
||||
isinstance(nxt, DataNode)
|
||||
and cur.endswith_whitespace()
|
||||
and nxt.startswith_whitespace()
|
||||
):
|
||||
cur.rstrip()
|
||||
# Recurse into children
|
||||
for child in self.children:
|
||||
child.collapse_whitespace()
|
||||
|
||||
def _write_start(self, doc):
|
||||
handler_name = 'start_%s' % self.tag
|
||||
if hasattr(doc.style, handler_name):
|
||||
getattr(doc.style, handler_name)(self.attrs)
|
||||
|
||||
def _write_end(self, doc, next_child):
|
||||
handler_name = 'end_%s' % self.tag
|
||||
if hasattr(doc.style, handler_name):
|
||||
if handler_name == 'end_a':
|
||||
# We use lookahead to determine if a space is needed after a link node
|
||||
getattr(doc.style, handler_name)(next_child)
|
||||
else:
|
||||
getattr(doc.style, handler_name)()
|
||||
|
||||
|
||||
class DataNode(Node):
|
||||
"""
|
||||
A Node that contains only string data.
|
||||
"""
|
||||
|
||||
def __init__(self, data, parent=None):
|
||||
super().__init__(parent)
|
||||
if not isinstance(data, str):
|
||||
raise ValueError("Expecting string type, %s given." % type(data))
|
||||
self._leading_whitespace = ''
|
||||
self._trailing_whitespace = ''
|
||||
self._stripped_data = ''
|
||||
if data == '':
|
||||
return
|
||||
if data.isspace():
|
||||
self._trailing_whitespace = data
|
||||
return
|
||||
first_non_space = next(
|
||||
idx for idx, ch in enumerate(data) if not ch.isspace()
|
||||
)
|
||||
last_non_space = len(data) - next(
|
||||
idx for idx, ch in enumerate(reversed(data)) if not ch.isspace()
|
||||
)
|
||||
self._leading_whitespace = data[:first_non_space]
|
||||
self._trailing_whitespace = data[last_non_space:]
|
||||
self._stripped_data = data[first_non_space:last_non_space]
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return (
|
||||
f'{self._leading_whitespace}{self._stripped_data}'
|
||||
f'{self._trailing_whitespace}'
|
||||
)
|
||||
|
||||
def is_whitespace(self):
|
||||
return self._stripped_data == '' and (
|
||||
self._leading_whitespace != '' or self._trailing_whitespace != ''
|
||||
)
|
||||
|
||||
def startswith_whitespace(self):
|
||||
return self._leading_whitespace != '' or (
|
||||
self._stripped_data == '' and self._trailing_whitespace != ''
|
||||
)
|
||||
|
||||
def endswith_whitespace(self):
|
||||
return self._trailing_whitespace != '' or (
|
||||
self._stripped_data == '' and self._leading_whitespace != ''
|
||||
)
|
||||
|
||||
def lstrip(self):
|
||||
if self._leading_whitespace != '':
|
||||
self._leading_whitespace = ''
|
||||
elif self._stripped_data == '':
|
||||
self.rstrip()
|
||||
|
||||
def rstrip(self):
|
||||
if self._trailing_whitespace != '':
|
||||
self._trailing_whitespace = ''
|
||||
elif self._stripped_data == '':
|
||||
self.lstrip()
|
||||
|
||||
def collapse_whitespace(self):
|
||||
"""Noop, ``DataNode.write`` always collapses whitespace"""
|
||||
return
|
||||
|
||||
def write(self, doc):
|
||||
words = doc.translate_words(self._stripped_data.split())
|
||||
str_data = (
|
||||
f'{self._leading_whitespace}{" ".join(words)}'
|
||||
f'{self._trailing_whitespace}'
|
||||
)
|
||||
if str_data != '':
|
||||
doc.handle_data(str_data)
|
||||
282
Function Source Files/botocore/docs/bcdoc/restdoc.py
Normal file
282
Function Source Files/botocore/docs/bcdoc/restdoc.py
Normal file
@@ -0,0 +1,282 @@
|
||||
# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
||||
# may not use this file except in compliance with the License. A copy of
|
||||
# the License is located at
|
||||
#
|
||||
# http://aws.amazon.com/apache2.0/
|
||||
#
|
||||
# or in the "license" file accompanying this file. This file is
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
# ANY KIND, either express or implied. See the License for the specific
|
||||
# language governing permissions and limitations under the License.
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from botocore.compat import OrderedDict
|
||||
from botocore.docs.bcdoc.docstringparser import DocStringParser
|
||||
from botocore.docs.bcdoc.style import ReSTStyle
|
||||
|
||||
DEFAULT_AWS_DOCS_LINK = 'https://docs.aws.amazon.com/index.html'
|
||||
DOCUMENTATION_LINK_REGEX = re.compile(
|
||||
r'`AWS API Documentation '
|
||||
r'<https://docs.aws.amazon.com/goto/WebAPI/[a-z0-9-.]*/[a-zA-Z]*>`_'
|
||||
)
|
||||
LARGE_SECTION_MESSAGE = """
|
||||
|
||||
**{}**
|
||||
::
|
||||
|
||||
# This section is too large to render.
|
||||
# Please see the AWS API Documentation linked below.
|
||||
|
||||
{}
|
||||
"""
|
||||
LOG = logging.getLogger('bcdocs')
|
||||
SECTION_LINE_LIMIT_CONFIG = {
|
||||
'response-example': {'name': 'Response Syntax', 'line_limit': 1500},
|
||||
'description': {'name': 'Response Structure', 'line_limit': 5000},
|
||||
'request-example': {'name': 'Request Syntax', 'line_limit': 1500},
|
||||
'request-params': {'name': 'Parameters', 'line_limit': 5000},
|
||||
}
|
||||
SECTION_METHOD_PATH_DEPTH = {
|
||||
'client-api': 4,
|
||||
'paginator-api': 3,
|
||||
'waiter-api': 3,
|
||||
}
|
||||
|
||||
|
||||
class ReSTDocument:
|
||||
def __init__(self, target='man'):
|
||||
self.style = ReSTStyle(self)
|
||||
self.target = target
|
||||
self.parser = DocStringParser(self)
|
||||
self.keep_data = True
|
||||
self.do_translation = False
|
||||
self.translation_map = {}
|
||||
self.hrefs = {}
|
||||
self._writes = []
|
||||
self._last_doc_string = None
|
||||
|
||||
def _write(self, s):
|
||||
if self.keep_data and s is not None:
|
||||
self._writes.append(s)
|
||||
|
||||
def write(self, content):
|
||||
"""
|
||||
Write content into the document.
|
||||
"""
|
||||
self._write(content)
|
||||
|
||||
def writeln(self, content):
|
||||
"""
|
||||
Write content on a newline.
|
||||
"""
|
||||
self._write(f'{self.style.spaces()}{content}\n')
|
||||
|
||||
def peek_write(self):
|
||||
"""
|
||||
Returns the last content written to the document without
|
||||
removing it from the stack.
|
||||
"""
|
||||
return self._writes[-1]
|
||||
|
||||
def pop_write(self):
|
||||
"""
|
||||
Removes and returns the last content written to the stack.
|
||||
"""
|
||||
return self._writes.pop() if len(self._writes) > 0 else None
|
||||
|
||||
def push_write(self, s):
|
||||
"""
|
||||
Places new content on the stack.
|
||||
"""
|
||||
self._writes.append(s)
|
||||
|
||||
def getvalue(self):
|
||||
"""
|
||||
Returns the current content of the document as a string.
|
||||
"""
|
||||
if self.hrefs:
|
||||
self.style.new_paragraph()
|
||||
for refname, link in self.hrefs.items():
|
||||
self.style.link_target_definition(refname, link)
|
||||
return ''.join(self._writes).encode('utf-8')
|
||||
|
||||
def translate_words(self, words):
|
||||
return [self.translation_map.get(w, w) for w in words]
|
||||
|
||||
def handle_data(self, data):
|
||||
if data and self.keep_data:
|
||||
self._write(data)
|
||||
|
||||
def include_doc_string(self, doc_string):
|
||||
if doc_string:
|
||||
try:
|
||||
start = len(self._writes)
|
||||
self.parser.feed(doc_string)
|
||||
self.parser.close()
|
||||
end = len(self._writes)
|
||||
self._last_doc_string = (start, end)
|
||||
except Exception:
|
||||
LOG.debug('Error parsing doc string', exc_info=True)
|
||||
LOG.debug(doc_string)
|
||||
|
||||
def remove_last_doc_string(self):
|
||||
# Removes all writes inserted by last doc string
|
||||
if self._last_doc_string is not None:
|
||||
start, end = self._last_doc_string
|
||||
del self._writes[start:end]
|
||||
|
||||
|
||||
class DocumentStructure(ReSTDocument):
|
||||
def __init__(self, name, section_names=None, target='man', context=None):
|
||||
"""Provides a Hierarichial structure to a ReSTDocument
|
||||
|
||||
You can write to it similiar to as you can to a ReSTDocument but
|
||||
has an innate structure for more orginaztion and abstraction.
|
||||
|
||||
:param name: The name of the document
|
||||
:param section_names: A list of sections to be included
|
||||
in the document.
|
||||
:param target: The target documentation of the Document structure
|
||||
:param context: A dictionary of data to store with the strucuture. These
|
||||
are only stored per section not the entire structure.
|
||||
"""
|
||||
super().__init__(target=target)
|
||||
self._name = name
|
||||
self._structure = OrderedDict()
|
||||
self._path = [self._name]
|
||||
self._context = {}
|
||||
if context is not None:
|
||||
self._context = context
|
||||
if section_names is not None:
|
||||
self._generate_structure(section_names)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the document structure"""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""
|
||||
A list of where to find a particular document structure in the
|
||||
overlying document structure.
|
||||
"""
|
||||
return self._path
|
||||
|
||||
@path.setter
|
||||
def path(self, value):
|
||||
self._path = value
|
||||
|
||||
@property
|
||||
def available_sections(self):
|
||||
return list(self._structure)
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
return self._context
|
||||
|
||||
def _generate_structure(self, section_names):
|
||||
for section_name in section_names:
|
||||
self.add_new_section(section_name)
|
||||
|
||||
def add_new_section(self, name, context=None):
|
||||
"""Adds a new section to the current document structure
|
||||
|
||||
This document structure will be considered a section to the
|
||||
current document structure but will in itself be an entirely
|
||||
new document structure that can be written to and have sections
|
||||
as well
|
||||
|
||||
:param name: The name of the section.
|
||||
:param context: A dictionary of data to store with the strucuture. These
|
||||
are only stored per section not the entire structure.
|
||||
:rtype: DocumentStructure
|
||||
:returns: A new document structure to add to but lives as a section
|
||||
to the document structure it was instantiated from.
|
||||
"""
|
||||
# Add a new section
|
||||
section = self.__class__(
|
||||
name=name, target=self.target, context=context
|
||||
)
|
||||
section.path = self.path + [name]
|
||||
# Indent the section apporpriately as well
|
||||
section.style.indentation = self.style.indentation
|
||||
section.translation_map = self.translation_map
|
||||
section.hrefs = self.hrefs
|
||||
self._structure[name] = section
|
||||
return section
|
||||
|
||||
def get_section(self, name):
|
||||
"""Retrieve a section"""
|
||||
return self._structure[name]
|
||||
|
||||
def delete_section(self, name):
|
||||
"""Delete a section"""
|
||||
del self._structure[name]
|
||||
|
||||
def flush_structure(self, docs_link=None):
|
||||
"""Flushes a doc structure to a ReSTructed string
|
||||
|
||||
The document is flushed out in a DFS style where sections and their
|
||||
subsections' values are added to the string as they are visited.
|
||||
"""
|
||||
# We are at the root flush the links at the beginning of the
|
||||
# document
|
||||
path_length = len(self.path)
|
||||
if path_length == 1:
|
||||
if self.hrefs:
|
||||
self.style.new_paragraph()
|
||||
for refname, link in self.hrefs.items():
|
||||
self.style.link_target_definition(refname, link)
|
||||
# Clear docs_link at the correct depth to prevent passing a non-related link.
|
||||
elif path_length == SECTION_METHOD_PATH_DEPTH.get(self.path[1]):
|
||||
docs_link = None
|
||||
value = self.getvalue()
|
||||
for name, section in self._structure.items():
|
||||
# Checks is the AWS API Documentation link has been generated.
|
||||
# If it has been generated, it gets passed as a the doc_link parameter.
|
||||
match = DOCUMENTATION_LINK_REGEX.search(value.decode())
|
||||
docs_link = (
|
||||
f'{match.group(0)}\n\n'.encode() if match else docs_link
|
||||
)
|
||||
value += section.flush_structure(docs_link)
|
||||
|
||||
# Replace response/request sections if the line number exceeds our limit.
|
||||
# The section is replaced with a message linking to AWS API Documentation.
|
||||
line_count = len(value.splitlines())
|
||||
section_config = SECTION_LINE_LIMIT_CONFIG.get(self.name)
|
||||
aws_docs_link = (
|
||||
docs_link.decode()
|
||||
if docs_link is not None
|
||||
else DEFAULT_AWS_DOCS_LINK
|
||||
)
|
||||
if section_config and line_count > section_config['line_limit']:
|
||||
value = LARGE_SECTION_MESSAGE.format(
|
||||
section_config['name'], aws_docs_link
|
||||
).encode()
|
||||
return value
|
||||
|
||||
def getvalue(self):
|
||||
return ''.join(self._writes).encode('utf-8')
|
||||
|
||||
def remove_all_sections(self):
|
||||
self._structure = OrderedDict()
|
||||
|
||||
def clear_text(self):
|
||||
self._writes = []
|
||||
|
||||
def add_title_section(self, title):
|
||||
title_section = self.add_new_section('title')
|
||||
title_section.style.h1(title)
|
||||
return title_section
|
||||
|
||||
def write_to_file(self, full_path, file_name):
|
||||
if not os.path.exists(full_path):
|
||||
os.makedirs(full_path)
|
||||
sub_resource_file_path = os.path.join(full_path, f'{file_name}.rst')
|
||||
with open(sub_resource_file_path, 'wb') as f:
|
||||
f.write(self.flush_structure())
|
||||
447
Function Source Files/botocore/docs/bcdoc/style.py
Normal file
447
Function Source Files/botocore/docs/bcdoc/style.py
Normal file
@@ -0,0 +1,447 @@
|
||||
# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
||||
# may not use this file except in compliance with the License. A copy of
|
||||
# the License is located at
|
||||
#
|
||||
# http://aws.amazon.com/apache2.0/
|
||||
#
|
||||
# or in the "license" file accompanying this file. This file is
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
# ANY KIND, either express or implied. See the License for the specific
|
||||
# language governing permissions and limitations under the License.
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger('bcdocs')
|
||||
# Terminal punctuation where a space is not needed before.
|
||||
PUNCTUATION_CHARACTERS = ('.', ',', '?', '!', ':', ';')
|
||||
|
||||
|
||||
class BaseStyle:
|
||||
def __init__(self, doc, indent_width=2):
|
||||
self.doc = doc
|
||||
self.indent_width = indent_width
|
||||
self._indent = 0
|
||||
self.keep_data = True
|
||||
|
||||
@property
|
||||
def indentation(self):
|
||||
return self._indent
|
||||
|
||||
@indentation.setter
|
||||
def indentation(self, value):
|
||||
self._indent = value
|
||||
|
||||
def new_paragraph(self):
|
||||
return '\n%s' % self.spaces()
|
||||
|
||||
def indent(self):
|
||||
self._indent += 1
|
||||
|
||||
def dedent(self):
|
||||
if self._indent > 0:
|
||||
self._indent -= 1
|
||||
|
||||
def spaces(self):
|
||||
return ' ' * (self._indent * self.indent_width)
|
||||
|
||||
def bold(self, s):
|
||||
return s
|
||||
|
||||
def ref(self, link, title=None):
|
||||
return link
|
||||
|
||||
def h2(self, s):
|
||||
return s
|
||||
|
||||
def h3(self, s):
|
||||
return s
|
||||
|
||||
def underline(self, s):
|
||||
return s
|
||||
|
||||
def italics(self, s):
|
||||
return s
|
||||
|
||||
def add_trailing_space_to_previous_write(self):
|
||||
# Adds a trailing space if none exists. This is mainly used for
|
||||
# ensuring inline code and links are separated from surrounding text.
|
||||
last_write = self.doc.pop_write()
|
||||
if last_write is None:
|
||||
last_write = ''
|
||||
if last_write != '' and last_write[-1] != ' ':
|
||||
last_write += ' '
|
||||
self.doc.push_write(last_write)
|
||||
|
||||
|
||||
class ReSTStyle(BaseStyle):
|
||||
def __init__(self, doc, indent_width=2):
|
||||
BaseStyle.__init__(self, doc, indent_width)
|
||||
self.do_p = True
|
||||
self.a_href = None
|
||||
self.list_depth = 0
|
||||
|
||||
def new_paragraph(self):
|
||||
self.doc.write('\n\n%s' % self.spaces())
|
||||
|
||||
def new_line(self):
|
||||
self.doc.write('\n%s' % self.spaces())
|
||||
|
||||
def _start_inline(self, markup):
|
||||
# Insert space between any directly adjacent bold and italic inlines to
|
||||
# avoid situations like ``**abc***def*``.
|
||||
try:
|
||||
last_write = self.doc.peek_write()
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if last_write in ('*', '**') and markup in ('*', '**'):
|
||||
self.doc.write(' ')
|
||||
self.doc.write(markup)
|
||||
|
||||
def _end_inline(self, markup):
|
||||
# Remove empty and self-closing tags like ``<b></b>`` and ``<b/>``.
|
||||
# If we simply translate that directly then we end up with something
|
||||
# like ****, which rst will assume is a heading instead of an empty
|
||||
# bold.
|
||||
last_write = self.doc.pop_write()
|
||||
if last_write == markup:
|
||||
return
|
||||
self.doc.push_write(last_write)
|
||||
self.doc.write(markup)
|
||||
|
||||
def start_bold(self, attrs=None):
|
||||
self._start_inline('**')
|
||||
|
||||
def end_bold(self):
|
||||
self._end_inline('**')
|
||||
|
||||
def start_b(self, attrs=None):
|
||||
self.doc.do_translation = True
|
||||
self.start_bold(attrs)
|
||||
|
||||
def end_b(self):
|
||||
self.doc.do_translation = False
|
||||
self.end_bold()
|
||||
|
||||
def bold(self, s):
|
||||
if s:
|
||||
self.start_bold()
|
||||
self.doc.write(s)
|
||||
self.end_bold()
|
||||
|
||||
def ref(self, title, link=None):
|
||||
if link is None:
|
||||
link = title
|
||||
self.doc.write(f':doc:`{title} <{link}>`')
|
||||
|
||||
def _heading(self, s, border_char):
|
||||
border = border_char * len(s)
|
||||
self.new_paragraph()
|
||||
self.doc.write(f'{border}\n{s}\n{border}')
|
||||
self.new_paragraph()
|
||||
|
||||
def h1(self, s):
|
||||
self._heading(s, '*')
|
||||
|
||||
def h2(self, s):
|
||||
self._heading(s, '=')
|
||||
|
||||
def h3(self, s):
|
||||
self._heading(s, '-')
|
||||
|
||||
def start_italics(self, attrs=None):
|
||||
self._start_inline('*')
|
||||
|
||||
def end_italics(self):
|
||||
self._end_inline('*')
|
||||
|
||||
def italics(self, s):
|
||||
if s:
|
||||
self.start_italics()
|
||||
self.doc.write(s)
|
||||
self.end_italics()
|
||||
|
||||
def start_p(self, attrs=None):
|
||||
if self.do_p:
|
||||
self.doc.write('\n\n%s' % self.spaces())
|
||||
|
||||
def end_p(self):
|
||||
if self.do_p:
|
||||
self.doc.write('\n\n%s' % self.spaces())
|
||||
|
||||
def start_code(self, attrs=None):
|
||||
self.doc.do_translation = True
|
||||
self.add_trailing_space_to_previous_write()
|
||||
self._start_inline('``')
|
||||
|
||||
def end_code(self):
|
||||
self.doc.do_translation = False
|
||||
self._end_inline('``')
|
||||
|
||||
def code(self, s):
|
||||
if s:
|
||||
self.start_code()
|
||||
self.doc.write(s)
|
||||
self.end_code()
|
||||
|
||||
def start_note(self, attrs=None):
|
||||
self.new_paragraph()
|
||||
self.doc.write('.. note::')
|
||||
self.indent()
|
||||
self.new_paragraph()
|
||||
|
||||
def end_note(self):
|
||||
self.dedent()
|
||||
self.new_paragraph()
|
||||
|
||||
def start_important(self, attrs=None):
|
||||
self.new_paragraph()
|
||||
self.doc.write('.. warning::')
|
||||
self.indent()
|
||||
self.new_paragraph()
|
||||
|
||||
def end_important(self):
|
||||
self.dedent()
|
||||
self.new_paragraph()
|
||||
|
||||
def start_danger(self, attrs=None):
|
||||
self.new_paragraph()
|
||||
self.doc.write('.. danger::')
|
||||
self.indent()
|
||||
self.new_paragraph()
|
||||
|
||||
def end_danger(self):
|
||||
self.dedent()
|
||||
self.new_paragraph()
|
||||
|
||||
def start_a(self, attrs=None):
|
||||
# Write an empty space to guard against zero whitespace
|
||||
# before an "a" tag. Example: hi<a>Example</a>
|
||||
self.add_trailing_space_to_previous_write()
|
||||
if attrs:
|
||||
for attr_key, attr_value in attrs:
|
||||
if attr_key == 'href':
|
||||
# Removes unnecessary whitespace around the href link.
|
||||
# Example: <a href=" http://example.com ">Example</a>
|
||||
self.a_href = attr_value.strip()
|
||||
self.doc.write('`')
|
||||
else:
|
||||
# There are some model documentation that
|
||||
# looks like this: <a>DescribeInstances</a>.
|
||||
# In this case we just write out an empty
|
||||
# string.
|
||||
self.doc.write(' ')
|
||||
self.doc.do_translation = True
|
||||
|
||||
def link_target_definition(self, refname, link):
|
||||
self.doc.writeln(f'.. _{refname}: {link}')
|
||||
|
||||
def sphinx_reference_label(self, label, text=None):
|
||||
if text is None:
|
||||
text = label
|
||||
if self.doc.target == 'html':
|
||||
self.doc.write(f':ref:`{text} <{label}>`')
|
||||
else:
|
||||
self.doc.write(text)
|
||||
|
||||
def _clean_link_text(self):
|
||||
doc = self.doc
|
||||
# Pop till we reach the link start character to retrieve link text.
|
||||
last_write = doc.pop_write()
|
||||
while not last_write.startswith('`'):
|
||||
last_write = doc.pop_write() + last_write
|
||||
if last_write != '':
|
||||
# Remove whitespace from the start of link text.
|
||||
if last_write.startswith('` '):
|
||||
last_write = f'`{last_write[1:].lstrip(" ")}'
|
||||
doc.push_write(last_write)
|
||||
|
||||
def end_a(self, next_child=None):
|
||||
self.doc.do_translation = False
|
||||
if self.a_href:
|
||||
self._clean_link_text()
|
||||
last_write = self.doc.pop_write()
|
||||
last_write = last_write.rstrip(' ')
|
||||
if last_write and last_write != '`':
|
||||
if ':' in last_write:
|
||||
last_write = last_write.replace(':', r'\:')
|
||||
self.doc.push_write(last_write)
|
||||
self.doc.push_write(' <%s>`__' % self.a_href)
|
||||
elif last_write == '`':
|
||||
# Look at start_a(). It will do a self.doc.write('`')
|
||||
# which is the start of the link title. If that is the
|
||||
# case then there was no link text. We should just
|
||||
# use an inline link. The syntax of this is
|
||||
# `<http://url>`_
|
||||
self.doc.push_write('`<%s>`__' % self.a_href)
|
||||
else:
|
||||
self.doc.push_write(self.a_href)
|
||||
self.doc.hrefs[self.a_href] = self.a_href
|
||||
self.doc.write('`__')
|
||||
self.a_href = None
|
||||
|
||||
def start_i(self, attrs=None):
|
||||
self.doc.do_translation = True
|
||||
self.start_italics()
|
||||
|
||||
def end_i(self):
|
||||
self.doc.do_translation = False
|
||||
self.end_italics()
|
||||
|
||||
def start_li(self, attrs=None):
|
||||
self.new_line()
|
||||
self.do_p = False
|
||||
self.doc.write('* ')
|
||||
|
||||
def end_li(self):
|
||||
self.do_p = True
|
||||
self.new_line()
|
||||
|
||||
def li(self, s):
|
||||
if s:
|
||||
self.start_li()
|
||||
self.doc.writeln(s)
|
||||
self.end_li()
|
||||
|
||||
def start_ul(self, attrs=None):
|
||||
if self.list_depth != 0:
|
||||
self.indent()
|
||||
self.list_depth += 1
|
||||
self.new_paragraph()
|
||||
|
||||
def end_ul(self):
|
||||
self.list_depth -= 1
|
||||
if self.list_depth != 0:
|
||||
self.dedent()
|
||||
self.new_paragraph()
|
||||
|
||||
def start_ol(self, attrs=None):
|
||||
# TODO: Need to control the bullets used for LI items
|
||||
if self.list_depth != 0:
|
||||
self.indent()
|
||||
self.list_depth += 1
|
||||
self.new_paragraph()
|
||||
|
||||
def end_ol(self):
|
||||
self.list_depth -= 1
|
||||
if self.list_depth != 0:
|
||||
self.dedent()
|
||||
self.new_paragraph()
|
||||
|
||||
def start_examples(self, attrs=None):
|
||||
self.doc.keep_data = False
|
||||
|
||||
def end_examples(self):
|
||||
self.doc.keep_data = True
|
||||
|
||||
def start_fullname(self, attrs=None):
|
||||
self.doc.keep_data = False
|
||||
|
||||
def end_fullname(self):
|
||||
self.doc.keep_data = True
|
||||
|
||||
def start_codeblock(self, attrs=None):
|
||||
self.doc.write('::')
|
||||
self.indent()
|
||||
self.new_paragraph()
|
||||
|
||||
def end_codeblock(self):
|
||||
self.dedent()
|
||||
self.new_paragraph()
|
||||
|
||||
def codeblock(self, code):
|
||||
"""
|
||||
Literal code blocks are introduced by ending a paragraph with
|
||||
the special marker ::. The literal block must be indented
|
||||
(and, like all paragraphs, separated from the surrounding
|
||||
ones by blank lines).
|
||||
"""
|
||||
self.start_codeblock()
|
||||
self.doc.writeln(code)
|
||||
self.end_codeblock()
|
||||
|
||||
def toctree(self):
|
||||
if self.doc.target == 'html':
|
||||
self.doc.write('\n.. toctree::\n')
|
||||
self.doc.write(' :maxdepth: 1\n')
|
||||
self.doc.write(' :titlesonly:\n\n')
|
||||
else:
|
||||
self.start_ul()
|
||||
|
||||
def tocitem(self, item, file_name=None):
|
||||
if self.doc.target == 'man':
|
||||
self.li(item)
|
||||
else:
|
||||
if file_name:
|
||||
self.doc.writeln(' %s' % file_name)
|
||||
else:
|
||||
self.doc.writeln(' %s' % item)
|
||||
|
||||
def hidden_toctree(self):
|
||||
if self.doc.target == 'html':
|
||||
self.doc.write('\n.. toctree::\n')
|
||||
self.doc.write(' :maxdepth: 1\n')
|
||||
self.doc.write(' :hidden:\n\n')
|
||||
|
||||
def hidden_tocitem(self, item):
|
||||
if self.doc.target == 'html':
|
||||
self.tocitem(item)
|
||||
|
||||
def table_of_contents(self, title=None, depth=None):
|
||||
self.doc.write('.. contents:: ')
|
||||
if title is not None:
|
||||
self.doc.writeln(title)
|
||||
if depth is not None:
|
||||
self.doc.writeln(' :depth: %s' % depth)
|
||||
|
||||
def start_sphinx_py_class(self, class_name):
|
||||
self.new_paragraph()
|
||||
self.doc.write('.. py:class:: %s' % class_name)
|
||||
self.indent()
|
||||
self.new_paragraph()
|
||||
|
||||
def end_sphinx_py_class(self):
|
||||
self.dedent()
|
||||
self.new_paragraph()
|
||||
|
||||
def start_sphinx_py_method(self, method_name, parameters=None):
|
||||
self.new_paragraph()
|
||||
content = '.. py:method:: %s' % method_name
|
||||
if parameters is not None:
|
||||
content += '(%s)' % parameters
|
||||
self.doc.write(content)
|
||||
self.indent()
|
||||
self.new_paragraph()
|
||||
|
||||
def end_sphinx_py_method(self):
|
||||
self.dedent()
|
||||
self.new_paragraph()
|
||||
|
||||
def start_sphinx_py_attr(self, attr_name):
|
||||
self.new_paragraph()
|
||||
self.doc.write('.. py:attribute:: %s' % attr_name)
|
||||
self.indent()
|
||||
self.new_paragraph()
|
||||
|
||||
def end_sphinx_py_attr(self):
|
||||
self.dedent()
|
||||
self.new_paragraph()
|
||||
|
||||
def write_py_doc_string(self, docstring):
|
||||
docstring_lines = docstring.splitlines()
|
||||
for docstring_line in docstring_lines:
|
||||
self.doc.writeln(docstring_line)
|
||||
|
||||
def external_link(self, title, link):
|
||||
if self.doc.target == 'html':
|
||||
self.doc.write(f'`{title} <{link}>`_')
|
||||
else:
|
||||
self.doc.write(title)
|
||||
|
||||
def internal_link(self, title, page):
|
||||
if self.doc.target == 'html':
|
||||
self.doc.write(f':doc:`{title} <{page}>`')
|
||||
else:
|
||||
self.doc.write(title)
|
||||
Reference in New Issue
Block a user