Skip to content

Commit

Permalink
docs: refactor; add parameterisation
Browse files Browse the repository at this point in the history
  • Loading branch information
dboddie committed Aug 1, 2023
1 parent 1d453c4 commit 61a3ad1
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 37 deletions.
195 changes: 195 additions & 0 deletions docs/_extensions/include_par.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
"""
These classes provide extended versions of the `literalinclude` and `code`
directives that apply substitutions to the input text. This makes it possible
to define substitutions for a particular document then include a template that
uses these extensions to generate output specific to that document.
For example, defining `project` as
.. |project| replace:: my-project
then including a file containing
.. code-par::
mkdir |project|; cd |project|
will produce the equivalent output to
.. code::
mkdir my-project; cd my-project
The `litinclude-par` directive extends the `literalinclude` directive in a
different way, making substitutions in the arguments to the directive before
passing them to the underlying `literalinclude` implementation. This allows
file input to be parameterised so that a template document can quote and refer
to files defined by the different documents that include it.
"""

from docutils.parsers.rst import directives
import re
from sphinx.directives.code import CodeBlock, LiteralInclude


# Define a regular expression for locating placeholders.
placeholder_re = re.compile("\\|([^|]+)\\|")


class LiteralIncludeParam(LiteralInclude):
"""
Like `literalinclude`, but expects a replacement/substitution to be
used as the file name.
"""

def run(self):
# type: () -> List[nodes.Node]
document = self.state.document

# Replace placeholders in options.
for option, value in self.options.items():
# Find all the placeholders and produce a set of unique matches.
matches = placeholder_re.findall(value)
unique = set(matches)

for match in unique:
try:
# For each match, find the substitution(s) to use, starting from
# the definition and reading all the arguments as plain text.
node = document.substitution_defs[match].next_node()
subs = str(node).split()

# Substitute new text for the placeholders.
for text in subs:
value = value.replace("|" + match + "|", text)

except KeyError:
### We should probably warn about undefined placeholders.
pass

self.options[option] = value

subst_text = self.arguments[0]
if subst_text[:1] != "|" or subst_text[-1:] != "|":
return [
document.reporter.warning(
"Argument should be a replacement/subsitution with the format "
"|<text>|",
line=self.lineno,
)
]
else:
# Dereference the substitution text to produce a filename.
subst_name = subst_text[1:-1]
try:
filename = str(document.substitution_defs[subst_name].next_node())
except KeyError:
return [
document.reporter.warning(
"Replacement/substitution {0} not defined".format(subst_name),
line=self.lineno,
)
]

self.arguments[0] = filename
return LiteralInclude.run(self)


class CodeParam(CodeBlock):
"""
Like `code-block`, but replaces |<text>| with the corresponding text
defined in a replacement/substitution.
"""

required_arguments = 0
optional_arguments = 1

# Define a regular expression for locating placeholders.
placeholder_re = re.compile("\\|([^|]+)\\|")

def run(self):
# type: () -> List[nodes.Node]
document = self.state.document

# Find placeholders in the text and replace them with replacements
# defined for the document.

for i, line in enumerate(self.content):
# Find all the placeholders on the line and reduce it to a set of
# unique matches.
matches = placeholder_re.findall(line)
unique = set(matches)

# Start with the original line and accumulate more if multiple
# substitutions occur.
lines = [line]

for match in unique:
try:
# For each match, find any substitutions to use, starting from
# the definition and reading all the arguments as plain text.
node = document.substitution_defs[match].next_node()
subs = self.parse_args(str(node))

# Substitute new text for the placeholders on each line.
# If there are multiple substitutions for a placeholder then
# insert new lines for those.
new_lines = []

for old_line in lines:
for text in subs:
new_lines.append(old_line.replace("|" + match + "|", text))

# Update the list of lines with substitutions and any new
# lines created.
lines = new_lines

except KeyError:
### We should probably warn about undefined placeholders.
pass

# Replace the original line with the processed list of new lines.
self.content.data[i : i + 1] = lines
self.content.items[i : i + 1] = [self.content.items[i]] * len(lines)

# In Sphinx 2.0 the language argument to code-block is optional, but
# in earlier versions we need to add one if no argument is given.
if len(self.arguments) < 1:
self.arguments = ["text"]

return CodeBlock.run(self)

def parse_args(self, text):
# Parse the text, treating spaces as argument separators unless
# the spaces are part of a quoted string.
### TODO: Add support for escaped quote characters if required.
if not text:
return []

in_quote = False
args = [""]

for c in text:
if in_quote:
if c == '"':
args.append("")
in_quote = False
else:
args[-1] += c
elif c == '"':
in_quote = True
elif c == " ":
args.append("")
else:
args[-1] += c

if args[-1] == "":
args.pop()

return args


def setup(app):
directives.register_directive("litinclude-par", LiteralIncludeParam)
directives.register_directive("code-par", CodeParam)
return {"version": "0.2"}
16 changes: 2 additions & 14 deletions docs/c-c-applications.rst
Original file line number Diff line number Diff line change
Expand Up @@ -305,20 +305,8 @@ You can then try it out:
.. figure:: https://forum-snapcraft-io.s3.dualstack.us-east-1.amazonaws.com/optimized/2X/5/5e4a99e71254372ac1c2da5b758fe488029b9d0a_2_690x495.png
:alt: Screenshot_20190613_152721|690x495


Removing the snap is simple too:

.. code:: bash
$ sudo snap remove dosbox
You can clean up the build environment with the following command:

.. code:: bash
$ snapcraft clean
By default, when you make a change to snapcraft.yaml, snapcraft only builds the parts that have changed. Cleaning a build, however, forces your snap to be rebuilt in a clean environment and will take longer.
.. |execname| replace:: dosbox
.. include:: common/removing-cleaning-snap.rst

.. Potentially just refer the reader to another tutorial.
.. include:: common/publishing-snap.rst
Expand Down
18 changes: 18 additions & 0 deletions docs/common/removing-cleaning-snap.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Removing the snap
-----------------

Removing the snap is simple too:

.. code-par:: bash

$ sudo snap remove |execname|

You can clean up the build environment with the following command:

.. code:: bash
$ snapcraft clean
When you make a change to :file:`snapcraft.yaml` and rebuild the snap,
Snapcraft only builds the parts that have changed. Cleaning a build, however, forces your snap to be rebuilt in a clean environment and will take longer.

4 changes: 4 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
project_dir = pathlib.Path("..").resolve()
sys.path.insert(0, str(project_dir.absolute()))

extensions_dir = pathlib.Path(os.curdir) / "_extensions"
sys.path.append(str(extensions_dir.absolute()))

import snapcraft

# Configuration file for the Sphinx documentation builder.
Expand Down Expand Up @@ -56,6 +59,7 @@
"sphinx_copybutton",
"sphinxext.opengraph",
"myst_parser",
"include_par",
]

myst_enable_extensions = ["substitution", "deflist"]
Expand Down
13 changes: 6 additions & 7 deletions docs/flutter-applications.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ available on users' systems.
command: my_flutter_app
extensions: [gnome]
The ``command`` specifies the path to the binary to be run. This is resolved
relative to the root of the snap contents.
The ``command`` specifies the path to the application binary that the user
can run. This is resolved relative to the root of the snap contents.

If your command name matches the snap ``name``, users will be able run the command directly.

Expand Down Expand Up @@ -210,12 +210,11 @@ You can then try it out:
.. figure:: images/my-flutter-app.png
:alt: Running the example Flutter application

.. |execname| replace:: my-flutter-app
.. include:: common/removing-cleaning-snap.rst

Removing the snap is simple too:

::

sudo snap remove my-flutter-app
.. Potentially just refer the reader to another tutorial.
.. include:: common/publishing-snap.rst

You now have a snap you can deploy and upload to the `Snap Store <https://snapcraft.io/store>`__. See :ref:`Releasing your app <releasing-your-app>` for more details, and to get a deeper insight into the snap building process, start with the :ref:`Snapcraft checklist <snapcraft-checklist>`.

Expand Down
18 changes: 2 additions & 16 deletions docs/python-apps.rst
Original file line number Diff line number Diff line change
Expand Up @@ -217,22 +217,8 @@ You can then try it out:
yt-dlp -h
Removing the snap is simple too:

.. code:: bash
sudo snap remove yt-dlp
You can also clean up the build environment, although this will slow down the
next initial build:

.. code:: bash
snapcraft clean
By default, when you make a change to :file:`snapcraft.yaml`, snapcraft only
builds the parts that have changed. Cleaning a build, however, forces your snap
to be rebuilt in a clean environment and will take longer.
.. |execname| replace:: yt-dlp
.. include:: common/removing-cleaning-snap.rst

.. Potentially just refer the reader to another tutorial.
.. include:: common/publishing-snap.rst
Expand Down

0 comments on commit 61a3ad1

Please sign in to comment.