forked from RobotLocomotion/drake
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
doc: Add direct styleguide publication (RobotLocomotion#14811)
- Loading branch information
1 parent
c8be496
commit 608bab0
Showing
11 changed files
with
411 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
"""Common library to provide a reusable main() routine for all of our | ||
documentation generation tools. | ||
""" | ||
|
||
import argparse | ||
import functools | ||
from http.server import SimpleHTTPRequestHandler | ||
import os.path | ||
from os.path import join | ||
from socketserver import TCPServer | ||
import shlex | ||
import subprocess | ||
from subprocess import PIPE, STDOUT | ||
import tempfile | ||
|
||
from bazel_tools.tools.python.runfiles import runfiles | ||
|
||
# This global variable can be toggled by our main() function. | ||
_verbose = False | ||
|
||
|
||
def verbose(): | ||
"""Returns True iff doc builds should produce detailed console output.""" | ||
return _verbose | ||
|
||
|
||
def symlink_input(filegroup_resource_path, temp_dir, strip_prefix=None): | ||
"""Symlinks a rule's input data into a temporary directory. | ||
This is useful both to create a hermetic set of inputs to pass to a | ||
documentation builder, or also in case we need to adjust the input data | ||
before passing it along. | ||
Args: | ||
filegroup_resource_path: Names a file created by enumerate_filegroup | ||
(in defs.bzl) which contains resource paths. | ||
temp_dir: Destination directory, which must already exist. | ||
strip_prefix: Optional; a list[str] of candidate strings to remove | ||
from the resource path when linking into temp_dir. The first match | ||
wins, and it is valid for no prefixes to match. | ||
""" | ||
assert os.path.isdir(temp_dir) | ||
r = runfiles.Create() | ||
with open(r.Rlocation(filegroup_resource_path)) as f: | ||
input_filenames = f.read().splitlines() | ||
for name in input_filenames: | ||
orig_name = r.Rlocation(name) | ||
assert os.path.exists(orig_name), name | ||
dest_name = name | ||
for prefix in (strip_prefix or []): | ||
if dest_name.startswith(prefix): | ||
dest_name = dest_name[len(prefix):] | ||
break | ||
temp_name = join(temp_dir, dest_name) | ||
os.makedirs(os.path.dirname(temp_name), exist_ok=True) | ||
os.symlink(orig_name, temp_name) | ||
|
||
|
||
def check_call(args, *, cwd=None): | ||
"""Runs a subprocess command, raising an exception iff the process fails. | ||
Obeys the command-line verbosity flag for console output: | ||
- when in non-verbose mode, shows output only in case of an error; | ||
- when in verbose mode, shows the command-line and live output. | ||
Args: | ||
args: Passed to subprocess.run(args=...). | ||
""" | ||
echo = "+ " + " ".join([shlex.quote(x) for x in args]) | ||
if verbose(): | ||
print(echo, flush=True) | ||
proc = subprocess.run(args, cwd=cwd, stderr=STDOUT) | ||
else: | ||
proc = subprocess.run(args, cwd=cwd, stderr=STDOUT, stdout=PIPE, | ||
encoding='utf-8') | ||
if proc.returncode != 0: | ||
print(echo, flush=True) | ||
print(proc.stdout, end='', flush=True) | ||
proc.check_returncode() | ||
|
||
|
||
def _call_build(*, build, out_dir): | ||
"""Calls build() into out_dir, while also supplying a temp_dir.""" | ||
with tempfile.TemporaryDirectory( | ||
dir=os.environ.get("TEST_TMPDIR"), | ||
prefix="doc_builder_temp_") as temp_dir: | ||
return build(out_dir=out_dir, temp_dir=temp_dir) | ||
|
||
|
||
class _HttpHandler(SimpleHTTPRequestHandler): | ||
"""An HTTP handler without logging.""" | ||
|
||
def log_request(*_): | ||
pass | ||
|
||
|
||
def _do_preview(*, build, subdir, port): | ||
"""Implements the "serve" (http) mode of main(). | ||
Args: | ||
build: Same as per main(). | ||
subdir: Same as per main(). | ||
port: Local port number to serve on, per the command line. | ||
""" | ||
print("Generating documentation preview ...") | ||
with tempfile.TemporaryDirectory(prefix="doc_builder_preview_") as scratch: | ||
if subdir: | ||
out_dir = join(scratch, subdir) | ||
os.mkdir(out_dir) | ||
else: | ||
out_dir = scratch | ||
pages = _call_build(build=build, out_dir=out_dir) | ||
assert len(pages) > 0 | ||
os.chdir(scratch) | ||
print(f"The files have temporarily been generated into {scratch}") | ||
print() | ||
print("Serving at the following URLs for local preview:") | ||
print() | ||
for page in pages: | ||
print(f" http://127.0.0.1:{port}/{join(subdir, page)}") | ||
print() | ||
print("Use Ctrl-C to exit.") | ||
TCPServer.allow_reuse_address = True | ||
server = TCPServer(("127.0.0.1", port), _HttpHandler) | ||
try: | ||
server.serve_forever() | ||
except KeyboardInterrupt: | ||
print() | ||
return | ||
|
||
|
||
def _do_generate(*, build, out_dir, on_error): | ||
"""Implements the "generate" (file output) mode of main(). | ||
Args: | ||
build: Same as per main(). | ||
out_dir: Directory to generate into, per the command line. | ||
on_error: Callback function to report problems with out_dir. | ||
""" | ||
if out_dir == "<test>": | ||
out_dir = join(os.environ["TEST_TMPDIR"], "_builder_out") | ||
if not os.path.isabs(out_dir): | ||
on_error(f"--out_dir={out_dir} is not an absolute path") | ||
if os.path.exists(out_dir): | ||
if len(os.listdir(out_dir)) > 0: | ||
on_error(f"--out_dir={out_dir} is not empty") | ||
else: | ||
if verbose(): | ||
print(f"+ mkdir -p {out_dir}", flush=True) | ||
os.makedirs(out_dir) | ||
print("Generating HTML ...") | ||
pages = _call_build(build=build, out_dir=out_dir) | ||
assert len(pages) > 0 | ||
print("... done") | ||
|
||
|
||
def main(*, build, subdir, description): | ||
"""Reusable main() function for documentation binaries; processes | ||
command-line arguments and generates documentation. | ||
Args: | ||
build: Callback function to compile the documentation. | ||
subdir: A subdirectory to use when offering preview mode on a local web | ||
server; this does NOT affect the --out_dir path. | ||
description: Main help str for argparse; typically the caller's __doc__. | ||
""" | ||
parser = argparse.ArgumentParser(description=description) | ||
group = parser.add_mutually_exclusive_group() | ||
group.add_argument( | ||
"--serve", action='store_true', | ||
help="Serve the documentation on the given PORT for easy preview.") | ||
group.add_argument( | ||
"--out_dir", type=str, metavar="DIR", | ||
help="Generate the documentation to the given output directory." | ||
" The DIR must be an absolute path." | ||
" If DIR already exists, then it must be empty." | ||
" (For regression testing, the DIR can be the magic value <test>," | ||
" in which case a $TEST_TMPDIR subdir will be used.)") | ||
parser.add_argument( | ||
"--port", type=int, metavar="PORT", default=8000, | ||
help="Use a non-default PORT when serving for preview.") | ||
parser.add_argument( | ||
"--verbose", action="store_true", | ||
help="Echo detailed commands, progress, etc. to the console") | ||
args = parser.parse_args() | ||
if args.verbose: | ||
global _verbose | ||
_verbose = True | ||
if args.out_dir is None: | ||
assert args.serve | ||
_do_preview(build=build, subdir=subdir, port=args.port) | ||
else: | ||
_do_generate(build=build, out_dir=args.out_dir, | ||
on_error=parser.error) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# -*- python -*- | ||
|
||
package(default_visibility = ["//visibility:private"]) | ||
|
||
load( | ||
"@drake//tools/skylark:drake_py.bzl", | ||
"drake_py_binary", | ||
) | ||
load( | ||
"//doc:defs.bzl", | ||
"DEFAULT_BINARY_TAGS", | ||
"DEFAULT_TEST_TAGS", | ||
"enumerate_filegroup", | ||
) | ||
load("//tools/lint:lint.bzl", "add_lint_tests") | ||
|
||
filegroup( | ||
name = "jekyll_input", | ||
srcs = [ | ||
":_config.yml", | ||
":_layouts/default.html", | ||
"@styleguide//:cppguide.html", | ||
"@styleguide//:include/link.png", | ||
"@styleguide//:include/styleguide.css", | ||
"@styleguide//:include/styleguide.js", | ||
"@styleguide//:pyguide.md", | ||
], | ||
) | ||
|
||
enumerate_filegroup( | ||
name = "jekyll_input.txt", | ||
data = [":jekyll_input"], | ||
) | ||
|
||
drake_py_binary( | ||
name = "build", | ||
srcs = ["build.py"], | ||
add_test_rule = 1, | ||
data = [ | ||
":jekyll_input", | ||
":jekyll_input.txt", | ||
], | ||
tags = DEFAULT_BINARY_TAGS, | ||
test_rule_args = ["--out_dir=<test>"], | ||
test_rule_size = "medium", | ||
test_rule_tags = DEFAULT_TEST_TAGS, | ||
test_rule_timeout = "short", | ||
visibility = ["//doc:__pkg__"], | ||
deps = [ | ||
"//doc:defs", | ||
], | ||
) | ||
|
||
add_lint_tests() |
Oops, something went wrong.