commands: Add --header and --update options to spack commands
The Spack documentation currently hard-codes some functionality in `conf.py`, which makes the doc build less "pluggable" for things like localized doc builds. In particular, we unconditionally generate an index of commands and a package list as part of the docs, but those should really only be done if things are not up to date. This commit does the following: - Add `--header` option to `spack commands` so that it can do the work of prepending text to its output. - Add `--update FILE` option to `spack commands` that makes it generate a new command index *only* if FILE is out of date w.r.t. commands in the Spack source. - Simplify code in `conf.py` to use these options and only update the command index when needed.
This commit is contained in:
parent
43aaf8c404
commit
6380f1917a
@ -51,36 +51,18 @@
|
|||||||
# Set an environment variable so that colify will print output like it would to
|
# Set an environment variable so that colify will print output like it would to
|
||||||
# a terminal.
|
# a terminal.
|
||||||
os.environ['COLIFY_SIZE'] = '25x120'
|
os.environ['COLIFY_SIZE'] = '25x120'
|
||||||
|
|
||||||
#
|
|
||||||
# Generate package list using spack command
|
|
||||||
#
|
|
||||||
with open('package_list.html', 'w') as plist_file:
|
|
||||||
subprocess.Popen(
|
|
||||||
[spack_root + '/bin/spack', 'list', '--format=html'],
|
|
||||||
stdout=plist_file)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Find all the `cmd-spack-*` references and add them to a command index
|
|
||||||
#
|
|
||||||
import spack
|
|
||||||
import spack.cmd
|
|
||||||
command_names = spack.cmd.all_commands()
|
|
||||||
documented_commands = set()
|
|
||||||
for filename in glob('*rst'):
|
|
||||||
with open(filename) as f:
|
|
||||||
for line in f:
|
|
||||||
match = re.match('.. _cmd-(spack-.*):', line)
|
|
||||||
if match:
|
|
||||||
documented_commands.add(match.group(1).strip())
|
|
||||||
|
|
||||||
os.environ['COLUMNS'] = '120'
|
os.environ['COLUMNS'] = '120'
|
||||||
shutil.copy('command_index.in', 'command_index.rst')
|
|
||||||
with open('command_index.rst', 'a') as index:
|
# Generate full package list if needed
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
[spack_root + '/bin/spack', 'commands', '--format=rst'] + list(
|
['spack', 'list', '--format=html', '--update=package_list.html'])
|
||||||
documented_commands),
|
|
||||||
stdout=index)
|
# Generate a command index if an update is needed
|
||||||
|
subprocess.call([
|
||||||
|
'spack', 'commands',
|
||||||
|
'--format=rst',
|
||||||
|
'--header=command_index.in',
|
||||||
|
'--update=command_index.rst'] + glob('*rst'))
|
||||||
|
|
||||||
#
|
#
|
||||||
# Run sphinx-apidoc
|
# Run sphinx-apidoc
|
||||||
@ -158,6 +140,7 @@ def setup(sphinx):
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
|
import spack
|
||||||
version = '.'.join(str(s) for s in spack.spack_version_info[:2])
|
version = '.'.join(str(s) for s in spack.spack_version_info[:2])
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = spack.spack_version
|
release = spack.spack_version
|
||||||
|
@ -12,8 +12,9 @@
|
|||||||
|
|
||||||
class ArgparseWriter(object):
|
class ArgparseWriter(object):
|
||||||
"""Analyzes an argparse ArgumentParser for easy generation of help."""
|
"""Analyzes an argparse ArgumentParser for easy generation of help."""
|
||||||
def __init__(self):
|
def __init__(self, out=sys.stdout):
|
||||||
self.level = 0
|
self.level = 0
|
||||||
|
self.out = out
|
||||||
|
|
||||||
def _write(self, parser, root=True, level=0):
|
def _write(self, parser, root=True, level=0):
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
@ -148,8 +149,7 @@ def __init__(self, out=sys.stdout, rst_levels=_rst_levels,
|
|||||||
strip_root_prog (bool): if ``True``, strip the base command name
|
strip_root_prog (bool): if ``True``, strip the base command name
|
||||||
from subcommands in output
|
from subcommands in output
|
||||||
"""
|
"""
|
||||||
super(ArgparseWriter, self).__init__()
|
super(ArgparseRstWriter, self).__init__(out)
|
||||||
self.out = out
|
|
||||||
self.rst_levels = rst_levels
|
self.rst_levels = rst_levels
|
||||||
self.strip_root_prog = strip_root_prog
|
self.strip_root_prog = strip_root_prog
|
||||||
|
|
||||||
|
@ -6,11 +6,15 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import llnl.util.tty as tty
|
||||||
from llnl.util.argparsewriter import ArgparseWriter, ArgparseRstWriter
|
from llnl.util.argparsewriter import ArgparseWriter, ArgparseRstWriter
|
||||||
|
from llnl.util.tty.colify import colify
|
||||||
|
|
||||||
|
import spack.cmd
|
||||||
import spack.main
|
import spack.main
|
||||||
from spack.main import section_descriptions
|
from spack.main import section_descriptions
|
||||||
|
|
||||||
@ -35,8 +39,14 @@ def setup_parser(subparser):
|
|||||||
'--format', default='names', choices=formatters,
|
'--format', default='names', choices=formatters,
|
||||||
help='format to be used to print the output (default: names)')
|
help='format to be used to print the output (default: names)')
|
||||||
subparser.add_argument(
|
subparser.add_argument(
|
||||||
'documented_commands', nargs=argparse.REMAINDER,
|
'--header', metavar='FILE', default=None, action='store',
|
||||||
help='list of documented commands to cross-references')
|
help='prepend contents of FILE to the output (useful for rst format)')
|
||||||
|
subparser.add_argument(
|
||||||
|
'--update', metavar='FILE', default=None, action='store',
|
||||||
|
help='write output to the specified file, if any command is newer')
|
||||||
|
subparser.add_argument(
|
||||||
|
'rst_files', nargs=argparse.REMAINDER,
|
||||||
|
help='list of rst files to search for `_cmd-spack-<cmd>` cross-refs')
|
||||||
|
|
||||||
|
|
||||||
class SpackArgparseRstWriter(ArgparseRstWriter):
|
class SpackArgparseRstWriter(ArgparseRstWriter):
|
||||||
@ -56,17 +66,18 @@ def usage(self, *args):
|
|||||||
|
|
||||||
class SubcommandWriter(ArgparseWriter):
|
class SubcommandWriter(ArgparseWriter):
|
||||||
def begin_command(self, prog):
|
def begin_command(self, prog):
|
||||||
print(' ' * self.level + prog)
|
self.out.write(' ' * self.level + prog)
|
||||||
|
self.out.write('\n')
|
||||||
|
|
||||||
|
|
||||||
@formatter
|
@formatter
|
||||||
def subcommands(args):
|
def subcommands(args, out):
|
||||||
parser = spack.main.make_argument_parser()
|
parser = spack.main.make_argument_parser()
|
||||||
spack.main.add_all_commands(parser)
|
spack.main.add_all_commands(parser)
|
||||||
SubcommandWriter().write(parser)
|
SubcommandWriter(out).write(parser)
|
||||||
|
|
||||||
|
|
||||||
def rst_index(out=sys.stdout):
|
def rst_index(out):
|
||||||
out.write('\n')
|
out.write('\n')
|
||||||
|
|
||||||
index = spack.main.index_commands()
|
index = spack.main.index_commands()
|
||||||
@ -94,30 +105,65 @@ def rst_index(out=sys.stdout):
|
|||||||
|
|
||||||
|
|
||||||
@formatter
|
@formatter
|
||||||
def rst(args):
|
def rst(args, out):
|
||||||
# print an index to each command
|
|
||||||
rst_index()
|
|
||||||
print()
|
|
||||||
|
|
||||||
# create a parser with all commands
|
# create a parser with all commands
|
||||||
parser = spack.main.make_argument_parser()
|
parser = spack.main.make_argument_parser()
|
||||||
spack.main.add_all_commands(parser)
|
spack.main.add_all_commands(parser)
|
||||||
|
|
||||||
# get documented commands from the command line
|
# extract cross-refs of the form `_cmd-spack-<cmd>:` from rst files
|
||||||
documented_commands = set(args.documented_commands)
|
documented_commands = set()
|
||||||
|
for filename in args.rst_files:
|
||||||
|
with open(filename) as f:
|
||||||
|
for line in f:
|
||||||
|
match = re.match(r'\.\. _cmd-(spack-.*):', line)
|
||||||
|
if match:
|
||||||
|
documented_commands.add(match.group(1).strip())
|
||||||
|
|
||||||
|
# print an index to each command
|
||||||
|
rst_index(out)
|
||||||
|
out.write('\n')
|
||||||
|
|
||||||
# print sections for each command and subcommand
|
# print sections for each command and subcommand
|
||||||
SpackArgparseRstWriter(documented_commands).write(parser, root=1)
|
SpackArgparseRstWriter(documented_commands, out).write(parser, root=1)
|
||||||
|
|
||||||
|
|
||||||
@formatter
|
@formatter
|
||||||
def names(args):
|
def names(args, out):
|
||||||
for cmd in spack.cmd.all_commands():
|
colify(spack.cmd.all_commands(), output=out)
|
||||||
print(cmd)
|
|
||||||
|
|
||||||
|
def prepend_header(args, out):
|
||||||
|
if not args.header:
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(args.header) as header:
|
||||||
|
out.write(header.read())
|
||||||
|
|
||||||
|
|
||||||
def commands(parser, args):
|
def commands(parser, args):
|
||||||
|
formatter = formatters[args.format]
|
||||||
|
|
||||||
# Print to stdout
|
# check header first so we don't open out files unnecessarily
|
||||||
formatters[args.format](args)
|
if args.header and not os.path.exists(args.header):
|
||||||
return
|
tty.die("No such file: '%s'" % args.header)
|
||||||
|
|
||||||
|
# if we're updating an existing file, only write output if a command
|
||||||
|
# is newer than the file.
|
||||||
|
if args.update:
|
||||||
|
if os.path.exists(args.update):
|
||||||
|
files = [
|
||||||
|
spack.cmd.get_module(command).__file__.rstrip('c') # pyc -> py
|
||||||
|
for command in spack.cmd.all_commands()]
|
||||||
|
last_update = os.path.getmtime(args.update)
|
||||||
|
if not any(os.path.getmtime(f) > last_update for f in files):
|
||||||
|
tty.msg('File is up to date: %s' % args.update)
|
||||||
|
return
|
||||||
|
|
||||||
|
tty.msg('Updating file: %s' % args.update)
|
||||||
|
with open(args.update, 'w') as f:
|
||||||
|
prepend_header(args, f)
|
||||||
|
formatter(args, f)
|
||||||
|
|
||||||
|
else:
|
||||||
|
prepend_header(args, sys.stdout)
|
||||||
|
formatter(args, sys.stdout)
|
||||||
|
@ -4,14 +4,14 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import pytest
|
||||||
|
|
||||||
from llnl.util.argparsewriter import ArgparseWriter
|
from llnl.util.argparsewriter import ArgparseWriter
|
||||||
|
|
||||||
import spack.cmd
|
import spack.cmd
|
||||||
import spack.main
|
import spack.main
|
||||||
from spack.main import SpackCommand
|
|
||||||
|
|
||||||
commands = SpackCommand('commands')
|
commands = spack.main.SpackCommand('commands')
|
||||||
|
|
||||||
parser = spack.main.make_argument_parser()
|
parser = spack.main.make_argument_parser()
|
||||||
spack.main.add_all_commands(parser)
|
spack.main.add_all_commands(parser)
|
||||||
@ -49,3 +49,63 @@ def begin_command(self, prog):
|
|||||||
assert prog in out
|
assert prog in out
|
||||||
assert re.sub(r' ', '-', prog) in out
|
assert re.sub(r' ', '-', prog) in out
|
||||||
Subcommands().write(parser)
|
Subcommands().write(parser)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rst_with_input_files(tmpdir):
|
||||||
|
filename = tmpdir.join('file.rst')
|
||||||
|
with filename.open('w') as f:
|
||||||
|
f.write('''
|
||||||
|
.. _cmd-spack-fetch:
|
||||||
|
cmd-spack-list:
|
||||||
|
.. _cmd-spack-stage:
|
||||||
|
_cmd-spack-install:
|
||||||
|
.. _cmd-spack-patch:
|
||||||
|
''')
|
||||||
|
|
||||||
|
out = commands('--format=rst', str(filename))
|
||||||
|
for name in ['fetch', 'stage', 'patch']:
|
||||||
|
assert (':ref:`More documentation <cmd-spack-%s>`' % name) in out
|
||||||
|
|
||||||
|
for name in ['list', 'install']:
|
||||||
|
assert (':ref:`More documentation <cmd-spack-%s>`' % name) not in out
|
||||||
|
|
||||||
|
|
||||||
|
def test_rst_with_header(tmpdir):
|
||||||
|
fake_header = 'this is a header!\n\n'
|
||||||
|
|
||||||
|
filename = tmpdir.join('header.txt')
|
||||||
|
with filename.open('w') as f:
|
||||||
|
f.write(fake_header)
|
||||||
|
|
||||||
|
out = commands('--format=rst', '--header', str(filename))
|
||||||
|
assert out.startswith(fake_header)
|
||||||
|
|
||||||
|
with pytest.raises(spack.main.SpackCommandError):
|
||||||
|
commands('--format=rst', '--header', 'asdfjhkf')
|
||||||
|
|
||||||
|
|
||||||
|
def test_rst_update(tmpdir):
|
||||||
|
update_file = tmpdir.join('output')
|
||||||
|
|
||||||
|
# not yet created when commands is run
|
||||||
|
commands('--update', str(update_file))
|
||||||
|
assert update_file.exists()
|
||||||
|
with update_file.open() as f:
|
||||||
|
assert f.read()
|
||||||
|
|
||||||
|
# created but older than commands
|
||||||
|
with update_file.open('w') as f:
|
||||||
|
f.write('empty\n')
|
||||||
|
update_file.setmtime(0)
|
||||||
|
commands('--update', str(update_file))
|
||||||
|
assert update_file.exists()
|
||||||
|
with update_file.open() as f:
|
||||||
|
assert f.read() != 'empty\n'
|
||||||
|
|
||||||
|
# newer than commands
|
||||||
|
with update_file.open('w') as f:
|
||||||
|
f.write('empty\n')
|
||||||
|
commands('--update', str(update_file))
|
||||||
|
assert update_file.exists()
|
||||||
|
with update_file.open() as f:
|
||||||
|
assert f.read() == 'empty\n'
|
||||||
|
Loading…
Reference in New Issue
Block a user