1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
|
# 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 has_section(self, name):
return name in self._structure
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())
|