milieux.cli.doc
1from dataclasses import dataclass, field 2from pathlib import Path 3import time 4from typing import Literal, Union 5 6from fancy_dataclass import ArgparseDataclass, CLIDataclass 7import pdoc.render 8import pdoc.web 9 10from milieux import logger 11from milieux.distro import get_packages 12 13 14DocFormat = Literal['markdown', 'google', 'numpy', 'restructuredtext'] 15 16 17@dataclass 18class PkgArgs(ArgparseDataclass): 19 """Specify which packages to build docs for.""" 20 packages: list[str] = field( 21 default_factory=list, 22 metadata={'nargs': '+', 'args': ['-p', '--packages'], 'help': 'list of packages'} 23 ) 24 requirements: list[str] = field( 25 default_factory=list, 26 metadata={'nargs': '+', 'args': ['-r', '--requirements'], 'help': 'requirements file(s) containing packages'} 27 ) 28 distros: list[str] = field( 29 default_factory=list, 30 metadata={'nargs': '+', 'args': ['-d', '--distros'], 'help': 'existing distro name(s) to include'} 31 ) 32 33 @property 34 def all_packages(self) -> list[str]: 35 """Gets a list of all packages.""" 36 return get_packages(self.packages, self.requirements, self.distros) 37 38 39@dataclass 40class RenderArgs(ArgparseDataclass): 41 """Customize rendering of docs.""" 42 # TODO: enable a way to vary the arguments per package 43 docformat: DocFormat = field( 44 default='markdown', 45 metadata={'help': 'docstring format', 'default_help': True} 46 ) 47 48 def configure(self) -> None: 49 """Configures the global pdoc render settings.""" 50 pdoc.render.configure(docformat=self.docformat) 51 52 53@dataclass 54class DocBuild(CLIDataclass, command_name='build'): 55 """Build API documentation.""" 56 output_dir: Path = field( 57 metadata={ 58 'args': ['-o', '--output-dir'], 59 'help': 'save output documentation to this directory' 60 } 61 ) 62 pkg_args: PkgArgs = field( 63 default_factory=PkgArgs, 64 metadata={'group': 'package arguments'} 65 ) 66 render_args: RenderArgs = field( 67 default_factory=RenderArgs, 68 metadata={'group': 'rendering arguments'} 69 ) 70 71 def run(self) -> None: 72 self.render_args.configure() 73 start = time.perf_counter() 74 logger.info(f'Building documentation to {self.output_dir}...') 75 pdoc.pdoc(*self.pkg_args.all_packages, output_directory=self.output_dir) 76 elapsed = time.perf_counter() - start 77 logger.info(f'Build docs in {elapsed:.3g} sec') 78 79 80@dataclass 81class DocServe(CLIDataclass, command_name='serve'): 82 """Serve API documentation.""" 83 host: str = field( 84 default='localhost', 85 metadata={'help': 'host on which to run HTTP server', 'default_help': True} 86 ) 87 port: int = field( 88 default=8080, 89 metadata={'help': 'port on which to run HTTP serve', 'default_help': True} 90 ) 91 no_browser: bool = field( 92 default=False, 93 metadata={'help': 'do not open a browser after web server has started'} 94 ) 95 pkg_args: PkgArgs = field( 96 default_factory=PkgArgs, 97 metadata={'group': 'package arguments'} 98 ) 99 render_args: RenderArgs = field( 100 default_factory=RenderArgs, 101 metadata={'group': 'rendering arguments'} 102 ) 103 104 def run(self) -> None: 105 self.render_args.configure() 106 logger.info('Serving documentation...') 107 try: 108 httpd = pdoc.web.DocServer((self.host, self.port), self.pkg_args.all_packages) 109 except OSError as e: 110 raise OSError(f'Cannot start web server on {self.host}:{self.port}: {e}') from e 111 with httpd: 112 url = f'http://{self.host}:{httpd.server_port}' 113 logger.info(f'Server ready at {url}') 114 if not self.no_browser: 115 pdoc.web.open_browser(url) 116 try: 117 httpd.serve_forever() 118 except KeyboardInterrupt: 119 httpd.server_close() 120 return 121 122 123@dataclass 124class DocCmd(CLIDataclass, command_name='doc'): 125 """Generate API documentation.""" 126 subcommand: Union[ 127 DocBuild, 128 DocServe, 129 ] = field(metadata={'subcommand': True})
DocFormat =
typing.Literal['markdown', 'google', 'numpy', 'restructuredtext']
@dataclass
class
PkgArgs18@dataclass 19class PkgArgs(ArgparseDataclass): 20 """Specify which packages to build docs for.""" 21 packages: list[str] = field( 22 default_factory=list, 23 metadata={'nargs': '+', 'args': ['-p', '--packages'], 'help': 'list of packages'} 24 ) 25 requirements: list[str] = field( 26 default_factory=list, 27 metadata={'nargs': '+', 'args': ['-r', '--requirements'], 'help': 'requirements file(s) containing packages'} 28 ) 29 distros: list[str] = field( 30 default_factory=list, 31 metadata={'nargs': '+', 'args': ['-d', '--distros'], 'help': 'existing distro name(s) to include'} 32 ) 33 34 @property 35 def all_packages(self) -> list[str]: 36 """Gets a list of all packages.""" 37 return get_packages(self.packages, self.requirements, self.distros)
Specify which packages to build docs for.
PkgArgs( packages: list[str] = <factory>, requirements: list[str] = <factory>, distros: list[str] = <factory>)
@dataclass
class
RenderArgs40@dataclass 41class RenderArgs(ArgparseDataclass): 42 """Customize rendering of docs.""" 43 # TODO: enable a way to vary the arguments per package 44 docformat: DocFormat = field( 45 default='markdown', 46 metadata={'help': 'docstring format', 'default_help': True} 47 ) 48 49 def configure(self) -> None: 50 """Configures the global pdoc render settings.""" 51 pdoc.render.configure(docformat=self.docformat)
Customize rendering of docs.
@dataclass
class
DocBuild54@dataclass 55class DocBuild(CLIDataclass, command_name='build'): 56 """Build API documentation.""" 57 output_dir: Path = field( 58 metadata={ 59 'args': ['-o', '--output-dir'], 60 'help': 'save output documentation to this directory' 61 } 62 ) 63 pkg_args: PkgArgs = field( 64 default_factory=PkgArgs, 65 metadata={'group': 'package arguments'} 66 ) 67 render_args: RenderArgs = field( 68 default_factory=RenderArgs, 69 metadata={'group': 'rendering arguments'} 70 ) 71 72 def run(self) -> None: 73 self.render_args.configure() 74 start = time.perf_counter() 75 logger.info(f'Building documentation to {self.output_dir}...') 76 pdoc.pdoc(*self.pkg_args.all_packages, output_directory=self.output_dir) 77 elapsed = time.perf_counter() - start 78 logger.info(f'Build docs in {elapsed:.3g} sec')
Build API documentation.
DocBuild( output_dir: pathlib.Path, pkg_args: PkgArgs = <factory>, render_args: RenderArgs = <factory>)
pkg_args: PkgArgs
render_args: RenderArgs
def
run(self) -> None:
72 def run(self) -> None: 73 self.render_args.configure() 74 start = time.perf_counter() 75 logger.info(f'Building documentation to {self.output_dir}...') 76 pdoc.pdoc(*self.pkg_args.all_packages, output_directory=self.output_dir) 77 elapsed = time.perf_counter() - start 78 logger.info(f'Build docs in {elapsed:.3g} sec')
Runs the main body of the program.
Subclasses should implement this to provide custom behavior.
If the class has a subcommand defined, and it is an instance of CLIDataclass
, the default implementation of run
will be to call the subcommand's own implementation.
@dataclass
class
DocServe81@dataclass 82class DocServe(CLIDataclass, command_name='serve'): 83 """Serve API documentation.""" 84 host: str = field( 85 default='localhost', 86 metadata={'help': 'host on which to run HTTP server', 'default_help': True} 87 ) 88 port: int = field( 89 default=8080, 90 metadata={'help': 'port on which to run HTTP serve', 'default_help': True} 91 ) 92 no_browser: bool = field( 93 default=False, 94 metadata={'help': 'do not open a browser after web server has started'} 95 ) 96 pkg_args: PkgArgs = field( 97 default_factory=PkgArgs, 98 metadata={'group': 'package arguments'} 99 ) 100 render_args: RenderArgs = field( 101 default_factory=RenderArgs, 102 metadata={'group': 'rendering arguments'} 103 ) 104 105 def run(self) -> None: 106 self.render_args.configure() 107 logger.info('Serving documentation...') 108 try: 109 httpd = pdoc.web.DocServer((self.host, self.port), self.pkg_args.all_packages) 110 except OSError as e: 111 raise OSError(f'Cannot start web server on {self.host}:{self.port}: {e}') from e 112 with httpd: 113 url = f'http://{self.host}:{httpd.server_port}' 114 logger.info(f'Server ready at {url}') 115 if not self.no_browser: 116 pdoc.web.open_browser(url) 117 try: 118 httpd.serve_forever() 119 except KeyboardInterrupt: 120 httpd.server_close() 121 return
Serve API documentation.
DocServe( host: str = 'localhost', port: int = 8080, no_browser: bool = False, pkg_args: PkgArgs = <factory>, render_args: RenderArgs = <factory>)
pkg_args: PkgArgs
render_args: RenderArgs
def
run(self) -> None:
105 def run(self) -> None: 106 self.render_args.configure() 107 logger.info('Serving documentation...') 108 try: 109 httpd = pdoc.web.DocServer((self.host, self.port), self.pkg_args.all_packages) 110 except OSError as e: 111 raise OSError(f'Cannot start web server on {self.host}:{self.port}: {e}') from e 112 with httpd: 113 url = f'http://{self.host}:{httpd.server_port}' 114 logger.info(f'Server ready at {url}') 115 if not self.no_browser: 116 pdoc.web.open_browser(url) 117 try: 118 httpd.serve_forever() 119 except KeyboardInterrupt: 120 httpd.server_close() 121 return
Runs the main body of the program.
Subclasses should implement this to provide custom behavior.
If the class has a subcommand defined, and it is an instance of CLIDataclass
, the default implementation of run
will be to call the subcommand's own implementation.
@dataclass
class
DocCmd124@dataclass 125class DocCmd(CLIDataclass, command_name='doc'): 126 """Generate API documentation.""" 127 subcommand: Union[ 128 DocBuild, 129 DocServe, 130 ] = field(metadata={'subcommand': True})
Generate API documentation.