2021-07-26 17:52:05 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2021-09-20 10:11:23 +00:00
|
|
|
def remix_code_url(source_code, language, solidity_version):
|
2021-07-26 17:52:05 +00:00
|
|
|
# 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')
|
2023-04-26 08:37:37 +00:00
|
|
|
return f"https://remix.ethereum.org/?#language={language}&version={solidity_version}&code={base64_encoded_source}"
|
2021-07-26 17:52:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
def build_remix_link_node(url):
|
2023-04-27 10:02:04 +00:00
|
|
|
reference_node = docutils.nodes.reference('', 'open in Remix', internal=False, refuri=url, target='_blank')
|
2021-07-26 17:52:05 +00:00
|
|
|
reference_node.set_class('remix-link')
|
|
|
|
|
|
|
|
paragraph_node = docutils.nodes.paragraph()
|
|
|
|
paragraph_node.set_class('remix-link-container')
|
2023-04-27 10:02:04 +00:00
|
|
|
paragraph_node.append(reference_node)
|
2021-07-26 17:52:05 +00:00
|
|
|
return paragraph_node
|
|
|
|
|
|
|
|
|
2021-09-20 10:11:23 +00:00
|
|
|
def insert_remix_link(app, doctree, solidity_version):
|
2021-07-26 17:52:05 +00:00
|
|
|
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
|
2021-09-20 10:11:23 +00:00
|
|
|
language = literal_block_node.attributes['language'].lower()
|
2023-04-27 10:02:04 +00:00
|
|
|
if language not in ['solidity', 'yul']:
|
|
|
|
continue
|
|
|
|
|
|
|
|
text_nodes = list(literal_block_node.traverse(docutils.nodes.Text))
|
|
|
|
assert len(text_nodes) == 1
|
|
|
|
|
|
|
|
remix_url = remix_code_url(text_nodes[0], language, solidity_version)
|
|
|
|
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))
|
2021-07-26 17:52:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
def setup(app):
|
2021-09-20 10:11:23 +00:00
|
|
|
# NOTE: Need to access _raw_config here because setup() runs before app.config is ready.
|
|
|
|
solidity_version = app.config._raw_config['version'] # pylint: disable=protected-access
|
|
|
|
|
2021-07-26 17:52:05 +00:00
|
|
|
app.connect(
|
|
|
|
'doctree-resolved',
|
2021-09-20 10:11:23 +00:00
|
|
|
lambda app, doctree, docname: insert_remix_link(app, doctree, solidity_version)
|
2021-07-26 17:52:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
return {
|
2021-09-20 10:11:23 +00:00
|
|
|
'version': solidity_version,
|
2021-07-26 17:52:05 +00:00
|
|
|
'parallel_read_safe': True,
|
|
|
|
'parallel_write_safe': True,
|
|
|
|
}
|