Skip to content

Commit

Permalink
docs: add command reference (#4267)
Browse files Browse the repository at this point in the history
  • Loading branch information
dboddie authored Jul 12, 2023
1 parent e57f979 commit f06e12e
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 18 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ demos/*/stage/
dist
docs/**.html
docs/reference.md
docs/reference/commands
docs/.sphinx
docs/_build
*.egg-info
.eggs/
.envrc
Expand Down
3 changes: 3 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ build:
os: ubuntu-20.04
tools:
python: "3.8"
apt_packages:
- libapt-pkg-dev

# Build documentation in the docs/ directory with Sphinx
sphinx:
Expand All @@ -29,3 +31,4 @@ sphinx:
python:
install:
- requirements: docs/.sphinx/requirements.txt
- requirements: docs/requirements.txt
1 change: 0 additions & 1 deletion docs/.sphinx/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@ sphinx-reredirects
pyspelling
sphinx-copybutton
sphinx-pydantic
sphinx-toolbox
3 changes: 2 additions & 1 deletion docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ help:
install:
@echo "... setting up virtualenv"
python3 -m venv .sphinx/venv
. $(VENV); pip install --upgrade -r .sphinx/requirements.txt
. $(VENV); pip install --upgrade -r .sphinx/requirements.txt -r requirements.txt

@echo "\n" \
"--------------------------------------------------------------- \n" \
Expand All @@ -39,6 +39,7 @@ serve:

clean: clean-doc
rm -rf .sphinx/venv
rm -rf reference/commands

clean-doc:
git clean -fx "$(BUILDDIR)"
Expand Down
43 changes: 27 additions & 16 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
import datetime
import os
import pathlib
import sys

project_dir = pathlib.Path("..").resolve()
sys.path.insert(0, str(project_dir.absolute()))

import snapcraft

# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
Expand All @@ -7,11 +17,9 @@
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = "Snapcraft"
copyright = "2023, Canonical"
author = "Canonical"

# region General configuration
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
author = "Canonical Group Ltd"
copyright = "%s, %s" % (datetime.date.today().year, author)
release = snapcraft.__version__

extensions = [
"sphinx.ext.intersphinx",
Expand All @@ -21,8 +29,6 @@
"sphinx_design",
"sphinx_copybutton",
"sphinx-pydantic",
"sphinx_toolbox",
"sphinx_toolbox.more_autodoc",
"sphinx.ext.autodoc", # Must be loaded after more_autodoc
]

Expand All @@ -31,21 +37,19 @@

show_authors = False

# endregion
# region Options for HTML output
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
rst_epilog = """
.. include:: /reuse/links.txt
"""

# Links to ignore when checking links
linkcheck_ignore = ["http://127.0.0.1:8000"]

html_theme = "furo"
html_static_path = ["_static"]
html_css_files = [
"css/custom.css",
]

# endregion
# region Options for extensions
# Intersphinx extension
# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration

intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
}
Expand All @@ -59,4 +63,11 @@
github_username = "snapcore"
github_repository = "snapcraft"

# endregion

def generate_cli_docs(nil):
gen_cli_docs_path = (project_dir / "tools" / "docs" / "gen_cli_docs.py").resolve()
os.system("%s %s" % (gen_cli_docs_path, project_dir / "docs"))


def setup(app):
app.connect("builder-inited", generate_cli_docs)
54 changes: 54 additions & 0 deletions docs/reference/commands.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
Snapcraft commands
==================

.. Use a hidden table of contents to ensure that documentation is read.
.. include:: commands/toc.rst

Lifecycle
---------
Lifecycle commands can take an optional parameter ``<part-name>``. When a part
name is provided, the command applies to the specific part. When no part name is
provided, the command applies to all parts.

.. include:: commands/lifecycle-commands.rst

Extensions
----------

.. include:: commands/extensions-commands.rst

Other
-----

.. include:: commands/other-commands.rst

Store account
-------------

.. include:: commands/store-account-commands.rst

Store key management
--------------------

.. include:: commands/store-key-management-commands.rst

Store snap names
----------------

.. include:: commands/store-snap-names-commands.rst

Store snap release management
-----------------------------

.. include:: commands/store-snap-release-management-commands.rst

Store snap tracks
-----------------

.. include:: commands/store-snap-tracks-commands.rst

Store validation sets
---------------------

.. include:: commands/store-validation-sets-commands.rst
2 changes: 2 additions & 0 deletions docs/reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Reference
.. toctree::
:maxdepth: 1

commands

Indices and tables
==================

Expand Down
47 changes: 47 additions & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
attrs==23.1.0
catkin-pkg==0.5.2
click==8.1.3
craft-archives==1.1.0
craft-cli==2.0.0
craft-grammar==1.1.1
craft-parts==1.22.0
craft-providers==1.13.0
craft-store==2.4.0
Deprecated==1.2.13
distro==1.8.0
gnupg==2.3.1
httplib2==0.22.0
jsonpointer==2.3
jsonschema==2.5.1
launchpadlib==1.11.0
lazr.restfulclient==0.14.5
lazr.uri==1.0.6
mypy-extensions==1.0.0
oauthlib==3.2.2
overrides==7.3.1
platformdirs==2.6.0
polib==1.1.1
progressbar==2.5
psutil==5.9.4
pydantic>=1.9.0
pydantic-yaml==0.8.1
pyelftools==0.29
pylxd==2.3.1
python-debian==0.1.49
pyxdg==0.28
PyYAML==6.0
raven==6.10.0
regex==2022.10.31
requests-unixsocket==0.3.0
semver==2.13.0
simplejson==3.19.1
snap-helpers==0.3.2
spdx==2.5.1
spdx-lookup==0.3.3
tabulate==0.8.10
types-Deprecated==1.2.9
typing_extensions
wadllib==1.3.6
wrapt==1.14.1
pyspelling==2.8.2
python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux"
7 changes: 7 additions & 0 deletions docs/reuse/links.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.. _Canonical website: https://canonical.com/
.. _reStructuredText style guide: https://canonical-documentation-with-sphinx-and-readthedocscom.readthedocs-hosted.com/style-guide/
.. _Sphinx reStructuredText Primer: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
.. _Canonical Documentation Style Guide: https://docs.ubuntu.com/styleguide/en
.. _Read the Docs at Canonical: https://library.canonical.com/documentation/read-the-docs
.. _How to publish documentation on Read the Docs: https://library.canonical.com/documentation/publish-on-read-the-docs
.. _Example product documentation: https://canonical-example-product-documentation.readthedocs-hosted.com/
147 changes: 147 additions & 0 deletions tools/docs/gen_cli_docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env python3

import argparse
import os
import pathlib
import sys

from craft_cli.dispatcher import Dispatcher, _CustomArgumentParser

this_dir = pathlib.Path(os.path.split(__file__)[0])
sys.path.insert(0, str((this_dir / ".." / "..").absolute()))

from snapcraft import cli


def command_page_header(cmd, options_str, required_str):
underline = "=" * len(cmd.name)
return f"""
.. _ref_commands_{cmd.name}:
{cmd.name}
{underline}
{cmd.overview}
Usage
-----
:command:`snapcraft {cmd.name}{options_str}{required_str}`
"""


def argname(dest, metavars):
# There should be only one metavar name. If None, use the dest instead.
if metavars[0]:
return metavars[0]
else:
return dest


def make_sentence(t):
if not t:
t = ""
return t[:1].upper() + t[1:].rstrip(".") + "."


def not_none(*args):
return [x for x in args if x != None]


def make_section(title, items):
s = ""
if items:
underline = "-" * len(title)
s = f"{title}\n{underline}\n\n"

for dest, (names, help_str) in items:
if help_str != "==SUPPRESS==":
if names == [None]:
s += f"``{dest}``\n"
else:
s += " or ".join([f"``{name}``" for name in names]) + "\n"
if help_str:
s += " %s\n" % make_sentence(help_str)

s += "\n"
return s


def remove_spaces(t):
return t.replace(" ", "-")


def main(docs_dir):
"""Generate reference documentation for the command line interface,
creating pages in the docs/reference/commands directory and creating the
directory itself if necessary."""

# Create the directory for the commands reference.
commands_ref_dir = docs_dir / "reference" / "commands"
if not commands_ref_dir.exists():
commands_ref_dir.mkdir()

# Create a dispatcher like Snapcraft does to get access to the same options.
dispatcher = cli.get_dispatcher()

help_builder = dispatcher._help_builder

global_options = {}
for arg in dispatcher.global_arguments:
opts = not_none(arg.short_option, arg.long_option)
global_options[arg.name] = (opts, arg.help_message)

toc = []

for group in cli.COMMAND_GROUPS:
group_name = remove_spaces(group.name.lower()) + "-commands" + os.extsep + "rst"
group_path = commands_ref_dir / group_name
g = group_path.open("w")

for cmd_class in sorted(group.commands, key=lambda c: c.name):
cmd = cmd_class({})
p = _CustomArgumentParser(help_builder)
cmd.fill_parser(p)

options = {}
required = []
options_str = " "

# Separate the options from required arguments, discarding global options.
for action in p._actions:
if action.option_strings and action.dest not in global_options:
options[action.dest] = (action.option_strings, action.help)
elif action.required or not action.option_strings:
required.append((action.dest, ([action.metavar], action.help)))

cmd_path = commands_ref_dir / (cmd.name + os.extsep + "rst")

if options or global_options:
options_str += "[options]"
required_str = "".join(
[(" <%s>" % argname(d, vl)) for d, (vl, h) in required]
)

f = cmd_path.open("w")
f.write(command_page_header(cmd, options_str, required_str))
f.write(make_section("Required", required))
f.write(make_section("Options", sorted(options.items())))
f.write(make_section("Global options", sorted(global_options.items())))

# Add a section for the command to be included in the group reference.
g.write(f":ref:`ref_commands_{cmd.name}`\n")
g.write(" " + make_sentence(cmd.help_msg).replace("\n", "\n ") + "\n\n")

# Add an entry in the table of contents.
toc.append(cmd.name)

toc_path = commands_ref_dir / "toc.rst"
f = toc_path.open("w")
f.write(".. toctree::\n :hidden:\n\n")
for name in sorted(toc):
f.write(f" /reference/commands/{name}\n")


if __name__ == "__main__":
if len(sys.argv) == 2:
main(pathlib.Path(sys.argv[1]))

0 comments on commit f06e12e

Please sign in to comment.