Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/botocore/docs/bcdoc/restdoc.py: 34%

139 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

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"). You 

4# may not use this file except in compliance with the License. A copy of 

5# the License is located at 

6# 

7# http://aws.amazon.com/apache2.0/ 

8# 

9# or in the "license" file accompanying this file. This file is 

10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 

11# ANY KIND, either express or implied. See the License for the specific 

12# language governing permissions and limitations under the License. 

13import logging 

14import os 

15import re 

16 

17from botocore.compat import OrderedDict 

18from botocore.docs.bcdoc.docstringparser import DocStringParser 

19from botocore.docs.bcdoc.style import ReSTStyle 

20 

21DEFAULT_AWS_DOCS_LINK = 'https://docs.aws.amazon.com/index.html' 

22DOCUMENTATION_LINK_REGEX = re.compile( 

23 r'`AWS API Documentation ' 

24 r'<https://docs.aws.amazon.com/goto/WebAPI/[a-z0-9-.]*/[a-zA-Z]*>`_' 

25) 

26LARGE_SECTION_MESSAGE = """ 

27 

28 **{}** 

29 :: 

30 

31 # This section is too large to render. 

32 # Please see the AWS API Documentation linked below. 

33 

34 {} 

35 """ 

36LOG = logging.getLogger('bcdocs') 

37SECTION_LINE_LIMIT_CONFIG = { 

38 'response-example': {'name': 'Response Syntax', 'line_limit': 1500}, 

39 'description': {'name': 'Response Structure', 'line_limit': 5000}, 

40 'request-example': {'name': 'Request Syntax', 'line_limit': 1500}, 

41 'request-params': {'name': 'Parameters', 'line_limit': 5000}, 

42} 

43SECTION_METHOD_PATH_DEPTH = { 

44 'client-api': 4, 

45 'paginator-api': 3, 

46 'waiter-api': 3, 

47} 

48 

49 

50class ReSTDocument: 

51 def __init__(self, target='man'): 

52 self.style = ReSTStyle(self) 

53 self.target = target 

54 self.parser = DocStringParser(self) 

55 self.keep_data = True 

56 self.do_translation = False 

57 self.translation_map = {} 

58 self.hrefs = {} 

59 self._writes = [] 

60 self._last_doc_string = None 

61 

62 def _write(self, s): 

63 if self.keep_data and s is not None: 

64 self._writes.append(s) 

65 

66 def write(self, content): 

67 """ 

68 Write content into the document. 

69 """ 

70 self._write(content) 

71 

72 def writeln(self, content): 

73 """ 

74 Write content on a newline. 

75 """ 

76 self._write(f'{self.style.spaces()}{content}\n') 

77 

78 def peek_write(self): 

79 """ 

80 Returns the last content written to the document without 

81 removing it from the stack. 

82 """ 

83 return self._writes[-1] 

84 

85 def pop_write(self): 

86 """ 

87 Removes and returns the last content written to the stack. 

88 """ 

89 return self._writes.pop() if len(self._writes) > 0 else None 

90 

91 def push_write(self, s): 

92 """ 

93 Places new content on the stack. 

94 """ 

95 self._writes.append(s) 

96 

97 def getvalue(self): 

98 """ 

99 Returns the current content of the document as a string. 

100 """ 

101 if self.hrefs: 

102 self.style.new_paragraph() 

103 for refname, link in self.hrefs.items(): 

104 self.style.link_target_definition(refname, link) 

105 return ''.join(self._writes).encode('utf-8') 

106 

107 def translate_words(self, words): 

108 return [self.translation_map.get(w, w) for w in words] 

109 

110 def handle_data(self, data): 

111 if data and self.keep_data: 

112 self._write(data) 

113 

114 def include_doc_string(self, doc_string): 

115 if doc_string: 

116 try: 

117 start = len(self._writes) 

118 self.parser.feed(doc_string) 

119 self.parser.close() 

120 end = len(self._writes) 

121 self._last_doc_string = (start, end) 

122 except Exception: 

123 LOG.debug('Error parsing doc string', exc_info=True) 

124 LOG.debug(doc_string) 

125 

126 def remove_last_doc_string(self): 

127 # Removes all writes inserted by last doc string 

128 if self._last_doc_string is not None: 

129 start, end = self._last_doc_string 

130 del self._writes[start:end] 

131 

132 

133class DocumentStructure(ReSTDocument): 

134 def __init__(self, name, section_names=None, target='man', context=None): 

135 """Provides a Hierarichial structure to a ReSTDocument 

136 

137 You can write to it similiar to as you can to a ReSTDocument but 

138 has an innate structure for more orginaztion and abstraction. 

139 

140 :param name: The name of the document 

141 :param section_names: A list of sections to be included 

142 in the document. 

143 :param target: The target documentation of the Document structure 

144 :param context: A dictionary of data to store with the strucuture. These 

145 are only stored per section not the entire structure. 

146 """ 

147 super().__init__(target=target) 

148 self._name = name 

149 self._structure = OrderedDict() 

150 self._path = [self._name] 

151 self._context = {} 

152 if context is not None: 

153 self._context = context 

154 if section_names is not None: 

155 self._generate_structure(section_names) 

156 

157 @property 

158 def name(self): 

159 """The name of the document structure""" 

160 return self._name 

161 

162 @property 

163 def path(self): 

164 """ 

165 A list of where to find a particular document structure in the 

166 overlying document structure. 

167 """ 

168 return self._path 

169 

170 @path.setter 

171 def path(self, value): 

172 self._path = value 

173 

174 @property 

175 def available_sections(self): 

176 return list(self._structure) 

177 

178 @property 

179 def context(self): 

180 return self._context 

181 

182 def _generate_structure(self, section_names): 

183 for section_name in section_names: 

184 self.add_new_section(section_name) 

185 

186 def add_new_section(self, name, context=None): 

187 """Adds a new section to the current document structure 

188 

189 This document structure will be considered a section to the 

190 current document structure but will in itself be an entirely 

191 new document structure that can be written to and have sections 

192 as well 

193 

194 :param name: The name of the section. 

195 :param context: A dictionary of data to store with the strucuture. These 

196 are only stored per section not the entire structure. 

197 :rtype: DocumentStructure 

198 :returns: A new document structure to add to but lives as a section 

199 to the document structure it was instantiated from. 

200 """ 

201 # Add a new section 

202 section = self.__class__( 

203 name=name, target=self.target, context=context 

204 ) 

205 section.path = self.path + [name] 

206 # Indent the section apporpriately as well 

207 section.style.indentation = self.style.indentation 

208 section.translation_map = self.translation_map 

209 section.hrefs = self.hrefs 

210 self._structure[name] = section 

211 return section 

212 

213 def get_section(self, name): 

214 """Retrieve a section""" 

215 return self._structure[name] 

216 

217 def delete_section(self, name): 

218 """Delete a section""" 

219 del self._structure[name] 

220 

221 def flush_structure(self, docs_link=None): 

222 """Flushes a doc structure to a ReSTructed string 

223 

224 The document is flushed out in a DFS style where sections and their 

225 subsections' values are added to the string as they are visited. 

226 """ 

227 # We are at the root flush the links at the beginning of the 

228 # document 

229 path_length = len(self.path) 

230 if path_length == 1: 

231 if self.hrefs: 

232 self.style.new_paragraph() 

233 for refname, link in self.hrefs.items(): 

234 self.style.link_target_definition(refname, link) 

235 # Clear docs_link at the correct depth to prevent passing a non-related link. 

236 elif path_length == SECTION_METHOD_PATH_DEPTH.get(self.path[1]): 

237 docs_link = None 

238 value = self.getvalue() 

239 for name, section in self._structure.items(): 

240 # Checks is the AWS API Documentation link has been generated. 

241 # If it has been generated, it gets passed as a the doc_link parameter. 

242 match = DOCUMENTATION_LINK_REGEX.search(value.decode()) 

243 docs_link = ( 

244 f'{match.group(0)}\n\n'.encode() if match else docs_link 

245 ) 

246 value += section.flush_structure(docs_link) 

247 

248 # Replace response/request sections if the line number exceeds our limit. 

249 # The section is replaced with a message linking to AWS API Documentation. 

250 line_count = len(value.splitlines()) 

251 section_config = SECTION_LINE_LIMIT_CONFIG.get(self.name) 

252 aws_docs_link = ( 

253 docs_link.decode() 

254 if docs_link is not None 

255 else DEFAULT_AWS_DOCS_LINK 

256 ) 

257 if section_config and line_count > section_config['line_limit']: 

258 value = LARGE_SECTION_MESSAGE.format( 

259 section_config['name'], aws_docs_link 

260 ).encode() 

261 return value 

262 

263 def getvalue(self): 

264 return ''.join(self._writes).encode('utf-8') 

265 

266 def remove_all_sections(self): 

267 self._structure = OrderedDict() 

268 

269 def clear_text(self): 

270 self._writes = [] 

271 

272 def add_title_section(self, title): 

273 title_section = self.add_new_section('title') 

274 title_section.style.h1(title) 

275 return title_section 

276 

277 def write_to_file(self, full_path, file_name): 

278 if not os.path.exists(full_path): 

279 os.makedirs(full_path) 

280 sub_resource_file_path = os.path.join(full_path, f'{file_name}.rst') 

281 with open(sub_resource_file_path, 'wb') as f: 

282 f.write(self.flush_structure())