From cd7db7faebdffc66ed3730662094b0927eaf84e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Mon, 20 Sep 2021 12:22:05 +0200 Subject: [PATCH 1/3] Add an arrow icon for Remix link, with proper attribution --- docs/_static/img/share-solid.svg | 1 + docs/_templates/footer.html | 6 ++++++ docs/credits-and-attribution.rst | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 docs/_static/img/share-solid.svg create mode 100644 docs/_templates/footer.html create mode 100644 docs/credits-and-attribution.rst diff --git a/docs/_static/img/share-solid.svg b/docs/_static/img/share-solid.svg new file mode 100644 index 000000000..1a1e61004 --- /dev/null +++ b/docs/_static/img/share-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/_templates/footer.html b/docs/_templates/footer.html new file mode 100644 index 000000000..28a09e8b9 --- /dev/null +++ b/docs/_templates/footer.html @@ -0,0 +1,6 @@ +{% extends "!footer.html" %} + +{% block extrafooter %} +
+ Credits and attribution. +{% endblock %} diff --git a/docs/credits-and-attribution.rst b/docs/credits-and-attribution.rst new file mode 100644 index 000000000..1a8067c8e --- /dev/null +++ b/docs/credits-and-attribution.rst @@ -0,0 +1,21 @@ +.. This page is meant to be linked to from the footer. + +:orphan: + +####################### +Credits and Attribution +####################### + +Website icons +============= + +.. |icon-share-solid| image:: _static/img/share-solid.svg +.. _share icon: https://fontawesome.com/v5.15/icons/share?style=solid +.. _Font Awesome Free License: https://fontawesome.com/license/free + ++-------------------------+-----------------------------------------------------------------------+ +| Icon | Attribution | ++=========================+=======================================================================+ +| |icon-share-solid| | - Source: `share icon`_ from Font Awesome 5.15.0. | +| | - License: `Font Awesome Free License`_ (CC BY 4.0). | ++-------------------------+-----------------------------------------------------------------------+ From 416b13850fb4ac89513242971cbf184cdf798696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Mon, 26 Jul 2021 19:52:05 +0200 Subject: [PATCH 2/3] Sphinx extension for adding Remix links to code snippets --- docs/_static/css/custom.css | 36 ++++++++++++++++ docs/_static/css/dark.css | 6 +++ docs/conf.py | 1 + docs/ext/remix_code_links.py | 80 ++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+) create mode 100644 docs/ext/remix_code_links.py diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css index 7fbda74b7..02dea07c8 100644 --- a/docs/_static/css/custom.css +++ b/docs/_static/css/custom.css @@ -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; diff --git a/docs/_static/css/dark.css b/docs/_static/css/dark.css index f93f1aa2d..a87ff09eb 100644 --- a/docs/_static/css/dark.css +++ b/docs/_static/css/dark.css @@ -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. */ +} diff --git a/docs/conf.py b/docs/conf.py index a46bac343..b75eb966d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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' diff --git a/docs/ext/remix_code_links.py b/docs/ext/remix_code_links.py new file mode 100644 index 000000000..7e7e558ea --- /dev/null +++ b/docs/ext/remix_code_links.py @@ -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, + } From b7942219dfc479d01825ec22cccbb4540d7a0d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Mon, 20 Sep 2021 12:11:23 +0200 Subject: [PATCH 3/3] Include language and compiler version in Remix code links --- docs/ext/remix_code_links.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/ext/remix_code_links.py b/docs/ext/remix_code_links.py index 7e7e558ea..2fc15ddda 100644 --- a/docs/ext/remix_code_links.py +++ b/docs/ext/remix_code_links.py @@ -18,11 +18,11 @@ def insert_node_before(child, new_sibling): break -def remix_code_url(source_code): +def remix_code_url(source_code, language, solidity_version): # 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}" + return f"https://remix.ethereum.org/?language={language}&version={solidity_version}&code={base64_encoded_source}" def build_remix_link_node(url): @@ -42,17 +42,18 @@ def build_remix_link_node(url): return paragraph_node -def insert_remix_link(app, doctree): +def insert_remix_link(app, doctree, solidity_version): 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': + language = literal_block_node.attributes['language'].lower() + if language in ['solidity', 'yul']: text_nodes = list(literal_block_node.traverse(docutils.nodes.Text)) assert len(text_nodes) == 1 - remix_url = remix_code_url(text_nodes[0]) + 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( @@ -67,14 +68,16 @@ def insert_remix_link(app, doctree): def setup(app): + # 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 + app.connect( 'doctree-resolved', - lambda app, doctree, docname: insert_remix_link(app, doctree) + lambda app, doctree, docname: insert_remix_link(app, doctree, solidity_version) ) 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 + 'version': solidity_version, 'parallel_read_safe': True, 'parallel_write_safe': True, }