Sphinx extension for adding Remix links to code snippets

This commit is contained in:
Kamil Śliwak 2021-07-26 19:52:05 +02:00
parent cd7db7faeb
commit 416b13850f
4 changed files with 123 additions and 0 deletions

View File

@ -45,4 +45,40 @@ pre {
/* menu section headers */
.wy-menu .caption {
color: #65afff !important;
/* Link to Remix IDE shown next to code snippets */
p.remix-link-container {
position: relative;
right: -100%; /* Positioned next to the the top-right corner of the code block following it. */
}
a.remix-link {
position: absolute; /* Remove it from normal flow not to affect the original position of the snippet. */
top: 0.5em;
width: 3.236em; /* Size of the margin (= right-side padding in .wy-nav-content in the current theme). */
}
a.remix-link .link-icon {
background: url("../img/share-solid.svg") no-repeat;
display: block;
width: 1.5em;
height: 1.5em;
margin: auto;
}
a.remix-link .link-text {
display: none; /* Visible only on hover. */
width: 3.3em; /* Narrow enough to get two lines of text. */
margin: auto;
text-align: center;
font-size: 0.8em;
line-height: normal;
color: black;
}
a.remix-link:hover {
opacity: 0.5;
}
a.remix-link:hover .link-text {
display: block;

View File

@ -627,3 +627,9 @@ code.docutils.literal.notranslate {
/* Literal.Number.Integer.Long */
/* Link to Remix IDE shown over code snippets */
a.remix-link {
filter: invert(1); /* The icon is black. In dark mode we want it white. */
}

View File

@ -44,6 +44,7 @@ def setup(sphinx):
extensions = [
'sphinx_a4doc',
'html_extra_template_renderer',
'remix_code_links',
]
a4_base_path = os.path.dirname(__file__) + '/grammar'

View File

@ -0,0 +1,80 @@
import base64
import docutils # pragma pylint: disable=import-error
from sphinx.util import logging # pragma pylint: disable=import-error
# NOTE: 2000 should generally be safe for all browsers, while 8000 for most of them.
MAX_SAFE_URL_LENGTH = 10000
logger = logging.getLogger(__name__)
def insert_node_before(child, new_sibling):
assert child in child.parent.children
for position, node in enumerate(child.parent.children):
if node == child:
child.parent.insert(position, new_sibling)
break
def remix_code_url(source_code):
# NOTE: base64 encoded data may contain +, = and / characters. Remix seems to handle them just
# fine without any escaping.
base64_encoded_source = base64.b64encode(source_code.encode('utf-8')).decode('ascii')
return f"https://remix.ethereum.org/?code={base64_encoded_source}"
def build_remix_link_node(url):
link_icon_node = docutils.nodes.inline()
link_icon_node.set_class('link-icon')
link_text_node = docutils.nodes.inline(text="open in Remix")
link_text_node.set_class('link-text')
reference_node = docutils.nodes.reference('', '', internal=False, refuri=url)
reference_node.set_class('remix-link')
reference_node += [link_icon_node, link_text_node]
paragraph_node = docutils.nodes.paragraph()
paragraph_node.set_class('remix-link-container')
paragraph_node += reference_node
return paragraph_node
def insert_remix_link(app, doctree):
if app.builder.format != 'html' or app.builder.name == 'epub':
return
for literal_block_node in doctree.traverse(docutils.nodes.literal_block):
assert 'language' in literal_block_node.attributes
if literal_block_node.attributes['language'].lower() == 'solidity':
text_nodes = list(literal_block_node.traverse(docutils.nodes.Text))
assert len(text_nodes) == 1
remix_url = remix_code_url(text_nodes[0])
url_length = len(remix_url.encode('utf-8'))
if url_length > MAX_SAFE_URL_LENGTH:
logger.warning(
"Remix URL generated from the code snippet exceeds the maximum safe URL length "
" (%d > %d bytes).",
url_length,
MAX_SAFE_URL_LENGTH,
location=(literal_block_node.source, literal_block_node.line),
)
insert_node_before(literal_block_node, build_remix_link_node(remix_url))
def setup(app):
app.connect(
'doctree-resolved',
lambda app, doctree, docname: insert_remix_link(app, doctree)
)
return {
# NOTE: Need to access _raw_config here because setup() runs before app.config is ready.
'version': app.config._raw_config['version'], # pylint: disable=protected-access
'parallel_read_safe': True,
'parallel_write_safe': True,
}