You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
716 lines
25 KiB
716 lines
25 KiB
4 years ago
|
# -*- coding: utf-8 -*-
|
||
|
"""Simple Markdown writer for reStructuredText.
|
||
|
|
||
|
See:
|
||
|
|
||
|
* https://daringfireball.net/projects/markdown/syntax
|
||
|
* https://guides.github.com/pdfs/markdown-cheatsheet-online.pdf
|
||
|
|
||
|
**********************
|
||
|
Copyright and Licenses
|
||
|
**********************
|
||
|
|
||
|
nb2plots
|
||
|
========
|
||
|
|
||
|
The nb2plots package, including all examples, code snippets and attached
|
||
|
documentation is covered by the 2-clause BSD license.
|
||
|
|
||
|
Copyright (c) 2015-2018, Matthew Brett
|
||
|
All rights reserved.
|
||
|
|
||
|
Redistribution and use in source and binary forms, with or without
|
||
|
modification, are permitted provided that the following conditions are
|
||
|
met:
|
||
|
|
||
|
1. Redistributions of source code must retain the above copyright notice,
|
||
|
this list of conditions and the following disclaimer.
|
||
|
|
||
|
2. Redistributions in binary form must reproduce the above copyright
|
||
|
notice, this list of conditions and the following disclaimer in the
|
||
|
documentation and/or other materials provided with the distribution.
|
||
|
|
||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||
|
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||
|
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
||
|
3rd party code and data
|
||
|
=======================
|
||
|
|
||
|
Some code distributed within the nb2plots sources was developed by other
|
||
|
projects. This code is distributed under their respective licenses that are
|
||
|
listed below.
|
||
|
|
||
|
Matplotlib plot extension
|
||
|
-------------------------
|
||
|
|
||
|
The ``nbplots`` module is an edited version of
|
||
|
``matplotlib/lib/matplotlib/sphinxext/plot_directive.py``.
|
||
|
|
||
|
Among the major changes are the following:
|
||
|
|
||
|
* rename of 'plot' directive to 'nbplot';
|
||
|
* rename of 'plot_*' configuration options to 'nbplot_*' equivalent;
|
||
|
* nbplot removes the option to point to Python script file rather than
|
||
|
using the code in the directive;
|
||
|
* nbplot keeps the namespace across directives in the same page, by default;
|
||
|
* include-source is True by default;
|
||
|
* nbplot can include multiple "parts" that can be selected via the
|
||
|
"render-parts" and "run-parts" options;
|
||
|
* do not use cached output when rebuilding page to avoid errors from stale
|
||
|
output;
|
||
|
* fix bug including empty parens when ``nofigs`` not specified (also fixed
|
||
|
upstream);
|
||
|
* empty ``setup_module`` to avoid breakage of nose doctests;
|
||
|
* fix of plot doctest.
|
||
|
|
||
|
The following license covers the plot directive code:
|
||
|
|
||
|
LICENSE AGREEMENT FOR MATPLOTLIB 1.2.0
|
||
|
--------------------------------------
|
||
|
|
||
|
1. This LICENSE AGREEMENT is between John D. Hunter ("JDH"), and the
|
||
|
Individual or Organization ("Licensee") accessing and otherwise using
|
||
|
matplotlib software in source or binary form and its associated
|
||
|
documentation.
|
||
|
|
||
|
2. Subject to the terms and conditions of this License Agreement, JDH
|
||
|
hereby grants Licensee a nonexclusive, royalty-free, world-wide license
|
||
|
to reproduce, analyze, test, perform and/or display publicly, prepare
|
||
|
derivative works, distribute, and otherwise use matplotlib 1.2.0
|
||
|
alone or in any derivative version, provided, however, that JDH's
|
||
|
License Agreement and JDH's notice of copyright, i.e., "Copyright (c)
|
||
|
2002-2011 John D. Hunter; All Rights Reserved" are retained in
|
||
|
matplotlib 1.2.0 alone or in any derivative version prepared by
|
||
|
Licensee.
|
||
|
|
||
|
3. In the event Licensee prepares a derivative work that is based on or
|
||
|
incorporates matplotlib 1.2.0 or any part thereof, and wants to
|
||
|
make the derivative work available to others as provided herein, then
|
||
|
Licensee hereby agrees to include in any such work a brief summary of
|
||
|
the changes made to matplotlib 1.2.0.
|
||
|
|
||
|
4. JDH is making matplotlib 1.2.0 available to Licensee on an "AS
|
||
|
IS" basis. JDH MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||
|
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, JDH MAKES NO AND
|
||
|
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||
|
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF MATPLOTLIB 1.2.0
|
||
|
WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
|
||
|
|
||
|
5. JDH SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF MATPLOTLIB
|
||
|
1.2.0 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR
|
||
|
LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING
|
||
|
MATPLOTLIB 1.2.0, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF
|
||
|
THE POSSIBILITY THEREOF.
|
||
|
|
||
|
6. This License Agreement will automatically terminate upon a material
|
||
|
breach of its terms and conditions.
|
||
|
|
||
|
7. Nothing in this License Agreement shall be deemed to create any
|
||
|
relationship of agency, partnership, or joint venture between JDH and
|
||
|
Licensee. This License Agreement does not grant permission to use JDH
|
||
|
trademarks or trade name in a trademark sense to endorse or promote
|
||
|
products or services of Licensee, or any third party.
|
||
|
|
||
|
8. By copying, installing or otherwise using matplotlib 1.2.0,
|
||
|
Licensee agrees to be bound by the terms and conditions of this License
|
||
|
Agreement.
|
||
|
|
||
|
rst2md
|
||
|
------
|
||
|
|
||
|
The files nb2plots/doctree2md.py and scripts/rst2md derive from the
|
||
|
"markdown.py" and "rst2md.py" files from "rst2md" project by Chris Wrench:
|
||
|
https://github.com/cgwrench/rst2md
|
||
|
|
||
|
``rst2md`` has the following license:
|
||
|
|
||
|
Copyright (c) 2012, C. G. Wrench <c.g.wrench@gmail.com>
|
||
|
All rights reserved.
|
||
|
|
||
|
Redistribution and use in source and binary forms, with or without
|
||
|
modification, are permitted provided that the following conditions are
|
||
|
met:
|
||
|
|
||
|
1. Redistributions of source code must retain the above copyright notice,
|
||
|
this list of conditions and the following disclaimer.
|
||
|
2. Redistributions in binary form must reproduce the above copyright
|
||
|
notice, this list of conditions and the following disclaimer in the
|
||
|
documentation and/or other materials provided with the distribution.
|
||
|
|
||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||
|
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||
|
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
"""
|
||
|
|
||
|
from __future__ import unicode_literals
|
||
|
|
||
|
import re
|
||
|
from textwrap import dedent
|
||
|
import posixpath
|
||
|
|
||
|
__docformat__ = 'reStructuredText'
|
||
|
|
||
|
from docutils import frontend, nodes, writers, languages
|
||
|
from collections import OrderedDict
|
||
|
|
||
|
class IndentLevel(object):
|
||
|
"""Class to hold text being written for a certain indentation level.
|
||
|
|
||
|
For example, all text in list_elements need to be indented. A list_element
|
||
|
creates one of these indentation levels, and all text contained in the
|
||
|
list_element gets written to this IndentLevel. When we leave the
|
||
|
list_element, we ``write`` the text with suitable prefixes to the next
|
||
|
level down, which might be the base of the document (document body) or
|
||
|
another indentation level, if this is - for example - a nested list.
|
||
|
|
||
|
In most respects, IndentLevel behaves like a list.
|
||
|
"""
|
||
|
def __init__(self, base, prefix, first_prefix=None):
|
||
|
self.base = base # The list to which we eventually write
|
||
|
self.prefix = prefix # Text prepended to lines
|
||
|
# Text prepended to first list
|
||
|
self.first_prefix = prefix if first_prefix is None else first_prefix
|
||
|
# Our own list to which we append before doing a ``write``
|
||
|
self.content = []
|
||
|
|
||
|
def append(self, new):
|
||
|
self.content.append(new)
|
||
|
|
||
|
def __getitem__(self, index):
|
||
|
return self.content[index]
|
||
|
|
||
|
def __len__(self):
|
||
|
return len(self.content)
|
||
|
|
||
|
def __bool__(self):
|
||
|
return len(self) != 0
|
||
|
|
||
|
def write(self):
|
||
|
"""Add ``self.contents`` with current ``prefix`` and ``first_prefix``
|
||
|
|
||
|
Add processed ``self.contents`` to ``self.base``. The first line has
|
||
|
``first_prefix`` prepended, further lines have ``prefix`` prepended.
|
||
|
|
||
|
Empty (all whitespace) lines get written as bare carriage returns, to
|
||
|
avoid ugly extra whitespace.
|
||
|
"""
|
||
|
string = ''.join(self.content)
|
||
|
lines = string.splitlines(True)
|
||
|
if len(lines) == 0:
|
||
|
return
|
||
|
texts = [self.first_prefix + lines[0]]
|
||
|
for line in lines[1:]:
|
||
|
if line.strip() == '': # avoid prefix for empty lines
|
||
|
texts.append('\n')
|
||
|
else:
|
||
|
texts.append(self.prefix + line)
|
||
|
self.base.append(''.join(texts))
|
||
|
|
||
|
def _make_method(to_add):
|
||
|
"""Make a method that adds `to_add`
|
||
|
|
||
|
We need this function so that `to_add` is a fresh and unique
|
||
|
variable at the time the method is defined.
|
||
|
"""
|
||
|
def method(self, node):
|
||
|
self.add(to_add)
|
||
|
|
||
|
return method
|
||
|
|
||
|
def add_pref_suff(pref_suff_map):
|
||
|
"""Decorator adds visit, depart methods for prefix/suffix pairs."""
|
||
|
def dec(cls):
|
||
|
# Need _make_method to ensure new variable picked up for each iteration
|
||
|
# of the loop. The defined method picks up this new variable in its
|
||
|
# scope.
|
||
|
for key, (prefix, suffix) in pref_suff_map.items():
|
||
|
setattr(cls, 'visit_' + key, _make_method(prefix))
|
||
|
setattr(cls, 'depart_' + key, _make_method(suffix))
|
||
|
return cls
|
||
|
|
||
|
return dec
|
||
|
|
||
|
def add_pass_thru(pass_thrus):
|
||
|
"""Decorator adds explicit pass-through visit and depart methods."""
|
||
|
def meth(self, node):
|
||
|
pass
|
||
|
|
||
|
def dec(cls):
|
||
|
for element_name in pass_thrus:
|
||
|
for meth_prefix in ('visit_', 'depart_'):
|
||
|
meth_name = meth_prefix + element_name
|
||
|
if hasattr(cls, meth_name):
|
||
|
raise ValueError(
|
||
|
'method name {} already defined'.format(meth_name)
|
||
|
)
|
||
|
setattr(cls, meth_name, meth)
|
||
|
return cls
|
||
|
|
||
|
return dec
|
||
|
|
||
|
# Characters that should be escaped in Markdown
|
||
|
ESCAPE_RE = re.compile(r'([\\*`])')
|
||
|
|
||
|
# Doctree elements for which Markdown element is <prefix><content><suffix>
|
||
|
PREF_SUFF_ELEMENTS = {
|
||
|
'emphasis': ('*', '*'), # Could also use ('_', '_')
|
||
|
'strong': ('**', '**'), # Could also use ('__', '__')
|
||
|
'literal': ('`', '`'),
|
||
|
'subscript': ('<sub>', '</sub>'),
|
||
|
'superscript': ('<sup>', '</sup>'),
|
||
|
}
|
||
|
|
||
|
# Doctree elements explicitly passed through without extra markup
|
||
|
PASS_THRU_ELEMENTS = (
|
||
|
'document',
|
||
|
'container',
|
||
|
'target',
|
||
|
'inline',
|
||
|
'definition_list',
|
||
|
'definition_list_item',
|
||
|
'term',
|
||
|
'field_list',
|
||
|
'field_list_item',
|
||
|
'field',
|
||
|
'field_name',
|
||
|
'mpl_hint',
|
||
|
'pending_xref',
|
||
|
'compound',
|
||
|
'line',
|
||
|
'line_block',
|
||
|
)
|
||
|
|
||
|
@add_pass_thru(PASS_THRU_ELEMENTS)
|
||
|
@add_pref_suff(PREF_SUFF_ELEMENTS)
|
||
|
class Translator(nodes.NodeVisitor):
|
||
|
|
||
|
std_indent = ' '
|
||
|
|
||
|
def __init__(self, document, builder=None):
|
||
|
nodes.NodeVisitor.__init__(self, document)
|
||
|
self.builder = builder
|
||
|
self.settings = settings = document.settings
|
||
|
lcode = settings.language_code
|
||
|
self.language = languages.get_language(lcode, document.reporter)
|
||
|
# Not-None here indicates Markdown should use HTTP for internal and
|
||
|
# download links.
|
||
|
self.markdown_http_base = (
|
||
|
builder.markdown_http_base if builder else None
|
||
|
)
|
||
|
# Warn only once per writer about unsupported elements
|
||
|
self._warned = set()
|
||
|
# Lookup table to get section list from name
|
||
|
self._lists = OrderedDict((('head', []), ('body', []), ('foot', [])))
|
||
|
# Reset attributes modified by reading
|
||
|
self.reset()
|
||
|
# Attribute shortcuts
|
||
|
self.head, self.body, self.foot = self._lists.values()
|
||
|
|
||
|
def reset(self):
|
||
|
"""Initialize object for fresh read."""
|
||
|
for part in self._lists.values():
|
||
|
part[:] = []
|
||
|
|
||
|
# Current section heading level during writing
|
||
|
self.section_level = 0
|
||
|
|
||
|
# FIFO list of list prefixes, while writing nested lists. Each element
|
||
|
# corresponds to one level of nesting. Thus ['1. ', '1. ', '* '] would
|
||
|
# occur when writing items of an unordered list, that is nested within
|
||
|
# an ordered list, that in turn is nested in another ordered list.
|
||
|
self.list_prefixes = []
|
||
|
|
||
|
# FIFO list of indentation levels. When we are writing a block of text
|
||
|
# that should be indented, we create a new indentation level. We only
|
||
|
# write the text when we leave the indentation level, so we can insert
|
||
|
# the correct prefix for every line.
|
||
|
self.indent_levels = []
|
||
|
|
||
|
# Flag indicating we are processing docinfo items
|
||
|
self._in_docinfo = False
|
||
|
|
||
|
# Flag for whether to escape characters
|
||
|
self._escape_text = True
|
||
|
|
||
|
def astext(self):
|
||
|
"""Return the final formatted document as a string."""
|
||
|
parts = [''.join(lines).strip() for lines in self._lists.values()]
|
||
|
parts = [part + '\n\n' for part in parts if part]
|
||
|
return ''.join(parts).strip() + '\n'
|
||
|
|
||
|
def ensure_eol(self):
|
||
|
"""Ensure the last line in current base is terminated by new line."""
|
||
|
out = self.get_current_output()
|
||
|
if out and out[-1] and out[-1][-1] != '\n':
|
||
|
out.append('\n')
|
||
|
|
||
|
def get_current_output(self, section='body'):
|
||
|
"""Get list or IndentLevel to which we are currently writing."""
|
||
|
return (
|
||
|
self.indent_levels[-1]
|
||
|
if self.indent_levels else self._lists[section]
|
||
|
)
|
||
|
|
||
|
def add(self, string, section='body'):
|
||
|
"""Add `string` to `section` or current output.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
string : str
|
||
|
String to add to output document
|
||
|
section : {'body', 'head', 'foot'}, optional
|
||
|
Section of document that generated text should be appended to, if
|
||
|
not already appending to an indent level. If already appending to
|
||
|
an indent level, method will ignore `section`. Use
|
||
|
:meth:`add_section` to append to a section unconditionally.
|
||
|
"""
|
||
|
self.get_current_output(section).append(string)
|
||
|
|
||
|
def add_section(self, string, section='body'):
|
||
|
"""Add `string` to `section` regardless of current output.
|
||
|
|
||
|
Can be useful when forcing write to header or footer.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
string : str
|
||
|
String to add to output document
|
||
|
section : {'body', 'head', 'foot'}, optional
|
||
|
Section of document that generated text should be appended to.
|
||
|
"""
|
||
|
self._lists[section].append(string)
|
||
|
|
||
|
def start_level(self, prefix, first_prefix=None, section='body'):
|
||
|
"""Create a new IndentLevel with `prefix` and `first_prefix`"""
|
||
|
base = (
|
||
|
self.indent_levels[-1].content
|
||
|
if self.indent_levels else self._lists[section]
|
||
|
)
|
||
|
level = IndentLevel(base, prefix, first_prefix)
|
||
|
self.indent_levels.append(level)
|
||
|
|
||
|
def finish_level(self):
|
||
|
"""Remove most recent IndentLevel and write contents."""
|
||
|
level = self.indent_levels.pop()
|
||
|
level.write()
|
||
|
|
||
|
def escape_chars(self, txt):
|
||
|
# Escape (some) characters with special meaning for Markdown
|
||
|
return ESCAPE_RE.sub(r'\\\1', txt)
|
||
|
|
||
|
def visit_Text(self, node):
|
||
|
text = node.astext().replace('\r\n', '\n')
|
||
|
if self._escape_text:
|
||
|
text = self.escape_chars(text)
|
||
|
self.add(text)
|
||
|
|
||
|
def depart_Text(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_comment(self, node):
|
||
|
txt = node.astext()
|
||
|
if txt.strip():
|
||
|
self.add('<!-- ' + node.astext() + ' -->\n')
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def visit_docinfo(self, node):
|
||
|
self._in_docinfo = True
|
||
|
|
||
|
def depart_docinfo(self, node):
|
||
|
self._in_docinfo = False
|
||
|
|
||
|
def process_docinfo_item(self, node):
|
||
|
"""Called explicitly from methods in this class."""
|
||
|
self.add_section('% {}\n'.format(node.astext()), section='head')
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def visit_definition(self, node):
|
||
|
self.add('\n\n')
|
||
|
self.start_level(' ')
|
||
|
|
||
|
def depart_definition(self, node):
|
||
|
self.finish_level()
|
||
|
|
||
|
visit_field_body = visit_definition
|
||
|
|
||
|
depart_field_body = depart_definition
|
||
|
|
||
|
def visit_paragraph(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_paragraph(self, node):
|
||
|
self.ensure_eol()
|
||
|
self.add('\n')
|
||
|
|
||
|
def visit_math_block(self, node):
|
||
|
# docutils math block
|
||
|
self._escape_text = False
|
||
|
self.add('$$\n')
|
||
|
|
||
|
def depart_math_block(self, node):
|
||
|
self._escape_text = True
|
||
|
self.ensure_eol()
|
||
|
self.add('$$\n\n')
|
||
|
|
||
|
def visit_displaymath(self, node):
|
||
|
# sphinx math blocks become displaymath
|
||
|
self.add('$$\n{}\n$$\n\n'.format(node['latex']))
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def visit_math(self, node):
|
||
|
# sphinx math node has 'latex' attribute, docutils does not
|
||
|
if 'latex' in node: # sphinx math node
|
||
|
self.add('${}$'.format(node['latex']))
|
||
|
raise nodes.SkipNode
|
||
|
# docutils math node
|
||
|
self._escape_text = False
|
||
|
self.add('$')
|
||
|
|
||
|
def depart_math(self, node):
|
||
|
# sphinx node skipped in visit, only docutils gets here
|
||
|
self._escape_text = True
|
||
|
self.add('$')
|
||
|
|
||
|
def visit_literal_block(self, node):
|
||
|
self._escape_text = False
|
||
|
code_type = node['classes'][1] if 'code' in node['classes'] else ''
|
||
|
if node.get('force_highlighting', False) and 'language' in node:
|
||
|
code_type = node['language']
|
||
|
self.add('```' + code_type + '\n')
|
||
|
|
||
|
def depart_literal_block(self, node):
|
||
|
self._escape_text = True
|
||
|
self.ensure_eol()
|
||
|
self.add('```\n\n')
|
||
|
|
||
|
def visit_doctest_block(self, node):
|
||
|
self._escape_text = False
|
||
|
self.add('```python\n')
|
||
|
|
||
|
depart_doctest_block = depart_literal_block
|
||
|
|
||
|
def visit_block_quote(self, node):
|
||
|
self.start_level('> ')
|
||
|
|
||
|
def depart_block_quote(self, node):
|
||
|
self.finish_level()
|
||
|
|
||
|
def visit_section(self, node):
|
||
|
self.section_level += 1
|
||
|
|
||
|
def depart_section(self, node):
|
||
|
self.section_level -= 1
|
||
|
|
||
|
def visit_enumerated_list(self, node):
|
||
|
self.list_prefixes.append('1. ')
|
||
|
|
||
|
def depart_enumerated_list(self, node):
|
||
|
self.list_prefixes.pop()
|
||
|
|
||
|
def visit_bullet_list(self, node):
|
||
|
self.list_prefixes.append('* ')
|
||
|
|
||
|
depart_bullet_list = depart_enumerated_list
|
||
|
|
||
|
def visit_list_item(self, node):
|
||
|
first_prefix = self.list_prefixes[-1]
|
||
|
prefix = ' ' * len(first_prefix)
|
||
|
self.start_level(prefix, first_prefix)
|
||
|
|
||
|
def depart_list_item(self, node):
|
||
|
self.finish_level()
|
||
|
|
||
|
def visit_problematic(self, node):
|
||
|
self.add('\n\n```\n{}\n```\n\n'.format(node.astext()))
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def visit_system_message(self, node):
|
||
|
if node['level'] < self.document.reporter.report_level:
|
||
|
# Level is too low to display
|
||
|
raise nodes.SkipNode
|
||
|
line = ', line %s' % node['line'] if node.hasattr('line') else ''
|
||
|
self.add(
|
||
|
'```\nSystem Message: {}:{}\n\n{}\n```\n\n'.format(
|
||
|
node['source'], line, node.astext()
|
||
|
)
|
||
|
)
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def visit_title(self, node):
|
||
|
self.add((self.section_level + 1) * '#' + ' ')
|
||
|
|
||
|
def depart_title(self, node):
|
||
|
self.ensure_eol()
|
||
|
self.add('\n')
|
||
|
|
||
|
def visit_subtitle(self, node):
|
||
|
self.add((self.section_level + 2) * '#' + ' ')
|
||
|
|
||
|
depart_subtitle = depart_title
|
||
|
|
||
|
def visit_transition(self, node):
|
||
|
# Simply replace a transition by a horizontal rule.
|
||
|
# Could use three or more '*', '_' or '-'.
|
||
|
self.add('\n---\n\n')
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def _refuri2http(self, node):
|
||
|
# Replace 'refuri' in reference with HTTP address, if possible
|
||
|
# None for no possible address
|
||
|
url = node.get('refuri')
|
||
|
if not node.get('internal'):
|
||
|
return url
|
||
|
# If HTTP page build URL known, make link relative to that.
|
||
|
if not self.markdown_http_base:
|
||
|
return None
|
||
|
this_doc = self.builder.current_docname
|
||
|
if url in (None, ''): # Reference to this doc
|
||
|
url = self.builder.get_target_uri(this_doc)
|
||
|
else: # URL is relative to the current docname.
|
||
|
this_dir = posixpath.dirname(this_doc)
|
||
|
if this_dir:
|
||
|
url = posixpath.normpath('{}/{}'.format(this_dir, url))
|
||
|
url = '{}/{}'.format(self.markdown_http_base, url)
|
||
|
if 'refid' in node:
|
||
|
url += '#' + node['refid']
|
||
|
return url
|
||
|
|
||
|
def visit_reference(self, node):
|
||
|
# If no target possible, pass through.
|
||
|
url = self._refuri2http(node)
|
||
|
if url is None:
|
||
|
return
|
||
|
self.add('[')
|
||
|
for child in node.children:
|
||
|
child.walkabout(self)
|
||
|
self.add(']({})'.format(url))
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def depart_reference(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_nbplot_epilogue(self, node):
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def visit_nbplot_not_rendered(self, node):
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def visit_code_links(self, node):
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def visit_index(self, node):
|
||
|
# Drop index entries
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def visit_substitution_definition(self, node):
|
||
|
# Drop substitution definitions - the doctree already contains the text
|
||
|
# with substitutions applied.
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def visit_only(self, node):
|
||
|
if node['expr'] == 'markdown':
|
||
|
self.add(dedent(node.astext()) + '\n')
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def visit_runrole_reference(self, node):
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def visit_download_reference(self, node):
|
||
|
# If not resolving internal links, or there is no filename specified,
|
||
|
# pass through.
|
||
|
filename = node.get('filename')
|
||
|
if None in (self.markdown_http_base, filename):
|
||
|
return
|
||
|
target_url = '{}/_downloads/{}'.format(
|
||
|
self.markdown_http_base, filename
|
||
|
)
|
||
|
self.add('[{}]({})'.format(node.astext(), target_url))
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
def depart_download_reference(self, node):
|
||
|
pass
|
||
|
|
||
|
visit_compact_paragraph = visit_paragraph
|
||
|
|
||
|
depart_compact_paragraph = depart_paragraph
|
||
|
|
||
|
def visit_nbplot_container(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_nbplot_container(self, node):
|
||
|
pass
|
||
|
|
||
|
def unknown_visit(self, node):
|
||
|
"""Warn once per instance for unsupported nodes.
|
||
|
|
||
|
Intercept docinfo items if in docinfo block.
|
||
|
"""
|
||
|
if self._in_docinfo:
|
||
|
self.process_docinfo_item(node)
|
||
|
return
|
||
|
# We really don't know this node type, warn once per node type
|
||
|
node_type = node.__class__.__name__
|
||
|
if node_type not in self._warned:
|
||
|
self.document.reporter.warning(
|
||
|
'The ' + node_type + ' element not yet supported in Markdown.'
|
||
|
)
|
||
|
self._warned.add(node_type)
|
||
|
raise nodes.SkipNode
|
||
|
|
||
|
class Writer(writers.Writer):
|
||
|
|
||
|
supported = ('markdown', )
|
||
|
"""Formats this writer supports."""
|
||
|
|
||
|
output = None
|
||
|
"""Final translated form of `document`."""
|
||
|
|
||
|
# Add configuration settings for additional Markdown flavours here.
|
||
|
settings_spec = (
|
||
|
'Markdown-Specific Options', None, (
|
||
|
(
|
||
|
'Extended Markdown syntax.', ['--extended-markdown'], {
|
||
|
'default': 0,
|
||
|
'action': 'store_true',
|
||
|
'validator': frontend.validate_boolean
|
||
|
}
|
||
|
),
|
||
|
(
|
||
|
'Strict Markdown syntax. Default: true', ['--strict-markdown'],
|
||
|
{
|
||
|
'default': 1,
|
||
|
'action': 'store_true',
|
||
|
'validator': frontend.validate_boolean
|
||
|
}
|
||
|
),
|
||
|
)
|
||
|
)
|
||
|
|
||
|
translator_class = Translator
|
||
|
|
||
|
def __init__(self, builder=None):
|
||
|
writers.Writer.__init__(self)
|
||
|
self.builder = builder
|
||
|
|
||
|
def translate(self):
|
||
|
visitor = self.translator_class(self.document, self.builder)
|
||
|
self.document.walkabout(visitor)
|
||
|
self.output = visitor.astext()
|