Break tools out into their own dirs
This commit is contained in:
		
					parent
					
						
							
								b56030804c
							
						
					
				
			
			
				commit
				
					
						3b9e4076a5
					
				
			
		
					 15 changed files with 59 additions and 66 deletions
				
			
		tools/sphinx
							
								
								
									
										12
									
								
								tools/sphinx/BUILD
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tools/sphinx/BUILD
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| py_binary( | ||||
|     name = "sphinx", | ||||
|     main = "__main__.py", | ||||
|     deps = [ | ||||
|         py_requirement("click"), | ||||
|         py_requirement("recommonmark"), | ||||
|         py_requirement("sphinx"), | ||||
|         py_requirement("sphinxcontrib-openapi"), | ||||
|         py_requirement("sphinxcontrib-programoutput"), | ||||
|         py_requirement("livereload"), | ||||
|     ] | ||||
| ) | ||||
							
								
								
									
										218
									
								
								tools/sphinx/__main__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								tools/sphinx/__main__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,218 @@ | |||
| #!/usr/bin/env python3 | ||||
| 
 | ||||
| """A documentation generator. | ||||
| 
 | ||||
| This is a shim tool which wraps up a whole bunch of Sphinx internals in a single "convenient" | ||||
| entrypoint. Former tweeps may recognize some parallels to the `docbird` tool developed by Twitter's | ||||
| techdocs team. | ||||
| 
 | ||||
| """ | ||||
| 
 | ||||
| import builtins | ||||
| from functools import wraps | ||||
| import io | ||||
| import os | ||||
| import sys | ||||
| 
 | ||||
| import click | ||||
| import livereload | ||||
| from sphinx.application import Sphinx | ||||
| from sphinx.cmd.build import main as build | ||||
| from sphinx.cmd.quickstart import main as new | ||||
| from sphinx.ext.apidoc import main as apidoc | ||||
| from sphinx.ext.autosummary.generate import main as autosummary | ||||
| from sphinx.util.docutils import docutils_namespace, patch_docutils | ||||
| 
 | ||||
| 
 | ||||
| @click.group() | ||||
| def cli(): | ||||
|     """A documentation generator. | ||||
| 
 | ||||
|     Just a shim to a variety of upstream Sphinx commands typically distributed as separate binaries | ||||
|     for some dang reason. | ||||
| 
 | ||||
|     Note that due to subcommand argument parsing '-- --help' is likely required. | ||||
| 
 | ||||
|     Subcommands have not been renamed (or customized, yet) from their Sphinx equivalents. | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
| 
 | ||||
| @cli.group() | ||||
| def generate(): | ||||
|     """Subcommands for doing RST header generation.""" | ||||
| 
 | ||||
| 
 | ||||
| @generate.command( | ||||
|     "apidoc", | ||||
|     context_settings=dict( | ||||
|         ignore_unknown_options=True, | ||||
|     ), | ||||
| ) | ||||
| @click.argument("argv", nargs=-1, type=click.UNPROCESSED) | ||||
| def do_apidoc(argv): | ||||
|     """Use sphinx.ext.apidoc to generate API documentation.""" | ||||
|     return apidoc(argv) | ||||
| 
 | ||||
| 
 | ||||
| @generate.command( | ||||
|     "summary", | ||||
|     context_settings=dict( | ||||
|         ignore_unknown_options=True, | ||||
|     ), | ||||
| ) | ||||
| @click.argument("argv", nargs=-1, type=click.UNPROCESSED) | ||||
| def do_summary(argv): | ||||
|     """Use sphinx.ext.autosummary to generate module summaries.""" | ||||
|     return autosummary(argv) | ||||
| 
 | ||||
| 
 | ||||
| @cli.command( | ||||
|     "new", | ||||
|     context_settings=dict( | ||||
|         ignore_unknown_options=True, | ||||
|     ), | ||||
| ) | ||||
| @click.argument("argv", nargs=-1, type=click.UNPROCESSED) | ||||
| def do_new(argv): | ||||
|     """Create a new Sphinx in the current directory.""" | ||||
|     return new(argv) | ||||
| 
 | ||||
| 
 | ||||
| @cli.command( | ||||
|     "build", | ||||
|     context_settings=dict( | ||||
|         ignore_unknown_options=True, | ||||
|     ), | ||||
| ) | ||||
| @click.argument("sourcedir") | ||||
| @click.argument("outputdir") | ||||
| @click.option("-c", "--confdir") | ||||
| @click.option("-d", "--doctreedir") | ||||
| @click.option("-b", "--builder", default="html") | ||||
| @click.option("--freshenv/--no-freshenv", default=False) | ||||
| @click.option("-W", "--warning-is-error", "werror", is_flag=True, flag_value=True) | ||||
| @click.option("-t", "--tag", "tags", multiple=True) | ||||
| def do_build( | ||||
|     sourcedir, outputdir, confdir, doctreedir, builder, freshenv, werror, tags | ||||
| ): | ||||
|     """Build a single Sphinx project.""" | ||||
| 
 | ||||
|     if not confdir: | ||||
|         confdir = sourcedir | ||||
| 
 | ||||
|     if not doctreedir: | ||||
|         doctreedir = os.path.join(outputdir, ".doctrees") | ||||
| 
 | ||||
|     status = sys.stdout | ||||
|     warning = sys.stderr | ||||
|     error = sys.stderr | ||||
| 
 | ||||
|     confdir = confdir or sourcedir | ||||
|     confoverrides = {}  # FIXME: support these | ||||
|     with patch_docutils(confdir), docutils_namespace(): | ||||
|         app = Sphinx( | ||||
|             sourcedir, | ||||
|             confdir, | ||||
|             outputdir, | ||||
|             doctreedir, | ||||
|             builder, | ||||
|             confoverrides, | ||||
|             status, | ||||
|             warning, | ||||
|             freshenv, | ||||
|             werror, | ||||
|             tags, | ||||
|             1, | ||||
|             4, | ||||
|             False, | ||||
|         ) | ||||
|         app.build(True, []) | ||||
|         return app.statuscode | ||||
| 
 | ||||
| 
 | ||||
| @cli.command( | ||||
|     "serve", | ||||
|     context_settings=dict( | ||||
|         ignore_unknown_options=True, | ||||
|     ), | ||||
| ) | ||||
| @click.option("-h", "--host", default="localhost") | ||||
| @click.option("-p", "--port", type=int, default=8080) | ||||
| @click.argument("sourcedir") | ||||
| @click.argument("outputdir") | ||||
| def do_serve(host, port, sourcedir, outputdir): | ||||
|     """Build and then serve a Sphinx tree.""" | ||||
| 
 | ||||
|     sourcedir = os.path.realpath(sourcedir) | ||||
|     outputdir = os.path.realpath(outputdir) | ||||
| 
 | ||||
|     server = livereload.Server() | ||||
| 
 | ||||
|     # HACK (arrdem 2020-10-31): | ||||
|     #   Okay. This is an elder hack, and I'm proud of it. | ||||
|     # | ||||
|     #   The naive implementation of the watching server is to watch the input files, which is | ||||
|     #   obviously correct. However, Sphinx has a BUNCH of operators like include and mdinclude and | ||||
|     #   soforth which can cause a Sphinx doctree to have file dependencies OUTSIDE of the "trivial" | ||||
|     #   source path dependency set. | ||||
|     # | ||||
|     #   In order to make sure that rebuilding does what the user intends, we trace calls to the | ||||
|     #   open() function and attempt to dynamically discover the dependency set of the site. This | ||||
|     #   allows us to trigger strictly correct rebuilds unlike other Sphinx implementations which | ||||
|     #   need to be restarted under some circumstances. | ||||
|     def opener(old_open): | ||||
|         @wraps(old_open) | ||||
|         def tracking_open(path, mode="r", *args, **kw): | ||||
|             file = old_open(path, mode, *args, **kw) | ||||
|             if isinstance(path, int): | ||||
|                 # If you're doing something weird with file pointers, ignore it. | ||||
|                 pass | ||||
|             else: | ||||
|                 path = os.path.realpath(path) | ||||
| 
 | ||||
|                 if "w" in mode: | ||||
|                     # If we're writing a file, it's an output for sure. Ignore it. | ||||
|                     ignorelist.add(path) | ||||
|                 elif ( | ||||
|                     not path.startswith(outputdir) | ||||
|                     and path not in ignorelist | ||||
|                     and not path in watchlist | ||||
|                 ): | ||||
|                     # Watch any source file (file we open for reading) | ||||
|                     server.watch(path, build) | ||||
|                     watchlist.add(path) | ||||
|             return file | ||||
| 
 | ||||
|         return tracking_open | ||||
| 
 | ||||
|     ignorelist = set() | ||||
|     watchlist = set() | ||||
| 
 | ||||
|     def build(): | ||||
|         try: | ||||
|             old_open = open | ||||
|             builtins.open = opener(old_open) | ||||
|             io.open = opener(old_open) | ||||
|             do_build([sourcedir, outputdir]) | ||||
|         except SystemExit: | ||||
|             pass | ||||
|         finally: | ||||
|             builtins.open = old_open | ||||
|             io.open = old_open | ||||
| 
 | ||||
|     build() | ||||
|     server.watch( | ||||
|         "conf.py", build | ||||
|     )  # Not sure why this isn't picked up, but it doesn't seem to be. | ||||
| 
 | ||||
|     server.serve(port=port, host=host, root=outputdir) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     # Hack in a -- delimeter to bypass click arg parsing | ||||
|     if not (sys.argv + [""])[1].startswith("-"): | ||||
|         sys.argv = [sys.argv[0], "--"] + sys.argv[1:] | ||||
| 
 | ||||
|     # Use click subcommands for everything else | ||||
|     exit(cli()) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue