Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2@file 

3@brief Helpers to convert docstring to various format. 

4""" 

5import logging 

6import warnings 

7from docutils.parsers.rst.directives import directive as rst_directive 

8from .sphinxm_convert_doc_sphinx_helper import ( 

9 HTMLWriterWithCustomDirectives, _CustomSphinx, 

10 MDWriterWithCustomDirectives, RSTWriterWithCustomDirectives, 

11 LatexWriterWithCustomDirectives, DocTreeWriterWithCustomDirectives 

12) 

13from ..sphinxext import get_default_extensions 

14 

15 

16class MockSphinxApp: 

17 """ 

18 Mocks :epkg:`Sphinx` application. 

19 In memory :epkg:`Sphinx` application. 

20 """ 

21 

22 def __init__(self, writer, app, confoverrides, new_extensions=None): 

23 """ 

24 @param writer see static method create 

25 @param app see static method create 

26 @param confoverrides default options 

27 @param new_extensions additional extensions 

28 """ 

29 from sphinx.registry import SphinxComponentRegistry 

30 if confoverrides is None: 

31 confoverrides = {} 

32 self.app = app 

33 self.env = app.env 

34 self.new_options = {} 

35 self.writer = writer 

36 self.registry = SphinxComponentRegistry() 

37 self.mapping = {"<class 'sphinx.ext.todo.todo_node'>": "todo", 

38 "<class 'sphinx.ext.graphviz.graphviz'>": "graphviz", 

39 "<class 'sphinx.ext.mathbase.math'>": "math", 

40 "<class 'sphinx.ext.mathbase.displaymath'>": "displaymath", 

41 "<class 'sphinx.ext.mathbase.eqref'>": "eqref", 

42 } 

43 

44 # delayed import to speed up import time 

45 from sphinx.config import Config 

46 

47 self.mapping_connect = {} 

48 with warnings.catch_warnings(): 

49 warnings.simplefilter( 

50 "ignore", (DeprecationWarning, PendingDeprecationWarning)) 

51 try: 

52 self.config = Config( # pylint: disable=E1121 

53 None, None, confoverrides, None) # pylint: disable=E1121 

54 except TypeError: 

55 # Sphinx>=3.0.0 

56 self.config = Config({}, confoverrides) 

57 self.confdir = "." 

58 self.doctreedir = "." 

59 self.srcdir = "." 

60 self.builder = writer.builder 

61 self._new_extensions = new_extensions 

62 if id(self.app) != id(self.writer.app): 

63 raise RuntimeError( # pragma: no cover 

64 "Different application in the writer is not allowed.") 

65 

66 @property 

67 def extensions(self): 

68 return self.app.extensions 

69 

70 def add_directive(self, name, cl, *args, **options): 

71 """ 

72 See :epkg:`class Sphinx`. 

73 """ 

74 # doc_directives.register_directive(name, cl) 

75 self.mapping[str(cl)] = name 

76 self.app.add_directive(name, cl, *args, **options) 

77 

78 def add_role(self, name, cl): 

79 """ 

80 See :epkg:`class Sphinx`. 

81 """ 

82 # doc_roles.register_canonical_role(name, cl) 

83 self.mapping[str(cl)] = name 

84 self.app.add_role(name, cl) 

85 

86 def add_builder(self, name, cl): 

87 """ 

88 See :epkg:`class Sphinx`. 

89 """ 

90 self.mapping[str(cl)] = name 

91 self.app.add_builder(name, cl) 

92 

93 def add_mapping(self, name, cl): 

94 """ 

95 See :epkg:`class Sphinx`. 

96 """ 

97 self.mapping[str(cl)] = name 

98 

99 def add_config_value(self, name, default, rebuild, types=()): 

100 """ 

101 See :epkg:`class Sphinx`. 

102 """ 

103 if name in self.config.values: 

104 # We do not add it a second time. 

105 return 

106 if rebuild in (False, True): 

107 rebuild = 'env' if rebuild else '' 

108 self.new_options[name] = (default, rebuild, types) 

109 self.config.values[name] = (default, rebuild, types) 

110 

111 def get_default_values(self): 

112 """ 

113 See :epkg:`class Sphinx`. 

114 """ 

115 return {k: v[0] for k, v in self.new_options.items()} 

116 

117 def add_node(self, node, **kwds): 

118 """ 

119 See :epkg:`class Sphinx`. 

120 """ 

121 self.app.add_node(node, **kwds) 

122 

123 def finalize(self, doctree, external_docnames=None): 

124 """ 

125 Finalizes the documentation after it was parsed. 

126 

127 @param doctree doctree (or pub.document), available after publication 

128 @param external_docnames other docnames the doctree references 

129 """ 

130 self.app.finalize(doctree, external_docnames=external_docnames) 

131 

132 def setup_extension(self, extname): 

133 """ 

134 See :epkg:`class Sphinx`. 

135 """ 

136 self.app.setup_extension(extname) 

137 

138 def emit(self, event, *args): 

139 """ 

140 See :epkg:`class Sphinx`. 

141 """ 

142 return self.app.events.emit(event, *args) 

143 

144 def emit_firstresult(self, event, *args): 

145 """ 

146 See :epkg:`class Sphinx`. 

147 """ 

148 return self.app.events.emit_firstresult(event, self, *args) 

149 

150 def add_autodocumenter(self, cls): 

151 """ 

152 See :epkg:`class Sphinx`. 

153 """ 

154 from sphinx.ext.autodoc.directive import AutodocDirective 

155 self.registry.add_documenter(cls.objtype, cls) 

156 self.add_directive('auto' + cls.objtype, AutodocDirective) 

157 

158 def connect(self, node, func): 

159 """ 

160 See :epkg:`class Sphinx`. 

161 """ 

162 self.mapping_connect[node] = func 

163 self.app.connect(node, func) 

164 

165 def add_domain(self, domain): 

166 """ 

167 See :epkg:`class Sphinx`. 

168 """ 

169 if domain.name in self.app.domains: 

170 # We do not register it a second time. 

171 return 

172 self.app.domains[domain.name] = domain 

173 

174 def require_sphinx(self, version): 

175 # check the Sphinx version if requested 

176 # delayed import to speed up import time 

177 from sphinx import __display_version__ as sphinx__display_version__ 

178 from sphinx.application import VersionRequirementError 

179 if version > sphinx__display_version__[:3]: 

180 raise VersionRequirementError(version) 

181 

182 def add_event(self, name): 

183 """ 

184 See :epkg:`class Sphinx`. 

185 """ 

186 if name in self.app._events: 

187 # We do not raise an exception if already present. 

188 return 

189 self.app._events[name] = '' 

190 

191 def add_env_collector(self, collector): 

192 """ 

193 See :epkg:`class Sphinx`. 

194 """ 

195 self.app.add_env_collector(collector) 

196 

197 def add_js_file(self, jsfile): 

198 """ 

199 See :epkg:`class Sphinx`. 

200 """ 

201 try: 

202 # Sphinx >= 1.8 

203 self.app.add_js_file(jsfile) 

204 except AttributeError: # pragma: no cover 

205 # Sphinx < 1.8 

206 self.app.add_javascript(jsfile) 

207 

208 def add_css_file(self, css): 

209 """ 

210 See :epkg:`class Sphinx`. 

211 """ 

212 try: 

213 # Sphinx >= 1.8 

214 self.app.add_css_file(css) 

215 except AttributeError: # pragma: no cover 

216 # Sphinx < 1.8 

217 self.app.add_stylesheet(css) 

218 

219 def add_source_parser(self, ext, parser, exc=False): 

220 """ 

221 Registers a parser for a specific file extension. 

222 

223 @param ext file extension 

224 @param parser parser 

225 @param exc raises an exception if already done 

226 

227 Example: 

228 

229 :: 

230 

231 app.add_source_parser(self, ext, parser) 

232 """ 

233 # delayed import to speed up import time 

234 from sphinx.errors import ExtensionError 

235 with warnings.catch_warnings(): 

236 warnings.simplefilter("ignore", ImportWarning) 

237 

238 try: 

239 self.app.add_source_parser(ext, parser) 

240 except TypeError: # pragma: no cover 

241 # Sphinx==3.0.0 

242 self.app.add_source_parser(parser) 

243 except ExtensionError as e: # pragma: no cover 

244 if exc: 

245 raise 

246 logger = logging.getLogger("MockSphinxApp") 

247 logger.warning('[MockSphinxApp] {0}'.format(e)) 

248 

249 def disconnect_env_collector(self, clname): 

250 """ 

251 Disconnects a collector given its class name. 

252 

253 @param cl name 

254 @return found collector 

255 """ 

256 self.app.disconnect_env_collector(clname) 

257 

258 @staticmethod 

259 def create(writer="html", directives=None, confoverrides=None, 

260 new_extensions=None, load_bokeh=False, 

261 destination_path=None, fLOG=None): 

262 """ 

263 Creates a @see cl MockSphinxApp for :epkg:`Sphinx`. 

264 

265 @param writer ``'sphinx'`` is the only allowed value 

266 @param directives new directives to add (see below) 

267 @param confoverrides initial options 

268 @param new_extensions additional extensions to setup 

269 @param load_bokeh load :epkg:`bokeh` extension, 

270 disabled by default as it is slow 

271 @param destination_path some extension requires it 

272 @param fLOG logging function 

273 @return mockapp, writer, list of added nodes 

274 

275 *directives* is None or a list of 2 or 5-uple: 

276 

277 * a directive name (mandatory) 

278 * a directive class: see `Sphinx Directive 

279 <https://www.sphinx-doc.org/en/master/development/tutorials/helloworld.html>`_, 

280 see also @see cl RunPythonDirective as an example (mandatory) 

281 * a docutils node: see @see cl runpython_node as an example 

282 * two functions: see @see fn visit_runpython_node, 

283 @see fn depart_runpython_node as an example 

284 """ 

285 logger = logging.getLogger('gdot') 

286 if not logger.disabled: 

287 logger.disabled = True 

288 restore = True 

289 else: 

290 restore = False 

291 

292 with warnings.catch_warnings(): 

293 warnings.simplefilter( 

294 "ignore", (DeprecationWarning, PendingDeprecationWarning)) 

295 if confoverrides is None: 

296 confoverrides = {} 

297 if "extensions" not in confoverrides: 

298 confoverrides["extensions"] = get_default_extensions( 

299 load_bokeh=load_bokeh) 

300 

301 if writer in ("sphinx", "custom", "HTMLWriterWithCustomDirectives", "html"): 

302 app = _CustomSphinx(srcdir=None, confdir=None, outdir=destination_path, 

303 doctreedir=None, 

304 buildername='memoryhtml', confoverrides=confoverrides, 

305 new_extensions=new_extensions) 

306 writer = HTMLWriterWithCustomDirectives( 

307 builder=app.builder, app=app) 

308 mockapp = MockSphinxApp(writer, writer.app, confoverrides=confoverrides, 

309 new_extensions=new_extensions) 

310 elif writer == "rst": 

311 app = _CustomSphinx(srcdir=None, confdir=None, outdir=destination_path, 

312 doctreedir=None, 

313 buildername='memoryrst', confoverrides=confoverrides, 

314 new_extensions=new_extensions) 

315 writer = RSTWriterWithCustomDirectives( 

316 builder=app.builder, app=app) 

317 mockapp = MockSphinxApp(writer, writer.app, confoverrides=confoverrides, 

318 new_extensions=new_extensions) 

319 elif writer == "md": 

320 app = _CustomSphinx(srcdir=None, confdir=None, outdir=destination_path, 

321 doctreedir=None, 

322 buildername='memorymd', confoverrides=confoverrides, 

323 new_extensions=new_extensions) 

324 writer = MDWriterWithCustomDirectives( 

325 builder=app.builder, app=app) 

326 mockapp = MockSphinxApp(writer, writer.app, confoverrides=confoverrides, 

327 new_extensions=new_extensions) 

328 elif writer == "elatex": 

329 app = _CustomSphinx(srcdir=None, confdir=None, outdir=None, doctreedir=None, 

330 buildername='memorylatex', confoverrides=confoverrides, 

331 new_extensions=new_extensions) 

332 writer = LatexWriterWithCustomDirectives( 

333 builder=app.builder, app=app) 

334 mockapp = MockSphinxApp(writer, writer.app, confoverrides=confoverrides, 

335 new_extensions=new_extensions) 

336 elif writer == "doctree": 

337 app = _CustomSphinx(srcdir=None, confdir=None, outdir=destination_path, 

338 doctreedir=None, 

339 buildername='memorydoctree', confoverrides=confoverrides, 

340 new_extensions=new_extensions) 

341 writer = DocTreeWriterWithCustomDirectives( 

342 builder=app.builder, app=app) 

343 mockapp = MockSphinxApp(writer, writer.app, confoverrides=confoverrides, 

344 new_extensions=new_extensions) 

345 elif isinstance(writer, tuple): 

346 # We expect ("builder_name", builder_class) 

347 app = _CustomSphinx(srcdir=None, confdir=None, outdir=destination_path, 

348 doctreedir=None, 

349 buildername=writer, confoverrides=confoverrides, 

350 new_extensions=new_extensions) 

351 if not hasattr(writer[1], "_writer_class"): 

352 raise AttributeError( # pragma: no cover 

353 "Class '{0}' does not have any attribute '_writer_class'." 

354 "".format(writer[1])) 

355 writer = writer[1]._writer_class( # pylint: disable=E1101 

356 builder=app.builder, app=app) # pylint: disable=E1101 

357 mockapp = MockSphinxApp(writer, app, confoverrides=confoverrides, 

358 new_extensions=new_extensions) 

359 else: 

360 raise ValueError( 

361 "Writer must be 'html', 'rst', 'md', 'elatex', not '{0}'.".format(writer)) 

362 

363 if restore: 

364 logger.disabled = False 

365 

366 # titles 

367 title_names = [] 

368 title_names.append("todoext_node") 

369 title_names.append("todo_node") 

370 title_names.append("mathdef_node") 

371 title_names.append("blocref_node") 

372 title_names.append("faqref_node") 

373 title_names.append("nbref_node") 

374 title_names.append("exref_node") 

375 

376 if directives is not None: 

377 for tu in directives: 

378 if len(tu) < 2: 

379 raise ValueError( 

380 "directives is a list of tuple with at least two elements, check the documentation") 

381 if len(tu) == 5: 

382 name, cl, node, f1, f2 = tu 

383 mockapp.add_node(node, html=(f1, f2)) 

384 # not necessary 

385 # nodes._add_node_class_names([node.__name__]) 

386 writer.connect_directive_node(node.__name__, f1, f2) 

387 elif len(tu) != 2: 

388 raise ValueError( 

389 "directives is a list of tuple with 2 or 5 elements, check the documentation") 

390 name, cl = tu[:2] 

391 mockapp.add_directive(name, cl) 

392 

393 if fLOG: 

394 apps = [mockapp] 

395 if hasattr(writer, "app"): 

396 apps.append(writer.app) 

397 for app in apps: 

398 if hasattr(app, "_added_objects"): 

399 fLOG("[MockSphinxApp] list of added objects") 

400 for el in app._added_objects: 

401 fLOG("[MockSphinxApp]", el) 

402 if el[0] == "domain": 

403 fLOG("[MockSphinxApp] NAME", el[1].name) 

404 for ro in el[1].roles: 

405 fLOG("[MockSphinxApp] ROLES", ro) 

406 for ro in el[1].directives: 

407 fLOG("[MockSphinxApp] DIREC", ro) 

408 from docutils.parsers.rst.directives import _directives 

409 for res in sorted(_directives): 

410 fLOG("[MockSphinxApp] RST DIREC", res) 

411 

412 class bb: 

413 def info(*args, line=0): # pylint: disable=E0211 

414 fLOG("[MockSphinxApp] -- ", *args) 

415 

416 class aa: 

417 def __init__(self): 

418 self.reporter = bb() 

419 self.current_line = 0 

420 from docutils.parsers.rst.languages import en 

421 for dir_check in ['py:function']: 

422 res = rst_directive(dir_check, en, aa()) 

423 

424 return mockapp, writer, title_names