def sphinx_env(app_env: SphinxAppEnv): """This context enters the standard sphinx contexts, then registers the roles, directives and nodes saved in the app_env. The standard sphinx contexts: - Patch docutils.languages.get_language(), to suppress reporter warnings - Temporarily sets `os.environ['DOCUTILSCONFIG']` to the sphinx confdir - Saves copies of roles._roles and directives._directives & resets them on exit - Un-registers additional nodes (set via `register_node`) on exit (by deleting `GenericNodeVisitor` visit/depart methods) - Patches roles.roles and directives.directives functions to also look in domains """ with patch_docutils( app_env.app.confdir), docutils_namespace(), sphinx_domains( app_env.app.env): from docutils.parsers.rst import directives, roles from sphinx.util.docutils import register_node if app_env.roles: roles._roles.update(app_env.roles) if app_env.directives: directives._directives.update(app_env.directives) for node in app_env.additional_nodes: register_node(node) # TODO how to make `unregister_node` thread safe yield
def test_html_with_docutilsconf(app, status, warning): with patch_docutils(app.confdir): app.build() doctree = app.env.get_doctree('index') assert_node(doctree[0][1], [nodes.paragraph, ("Sphinx", [nodes.footnote_reference, "1"])])
def render_sphinx(content): with tempfile.TemporaryDirectory() as tmp_dir: src_path = pathlib.Path(tmp_dir, "src/contents.rst") src_path.parent.mkdir() with src_path.open("w") as src: src.write(content) build_path = pathlib.Path(tmp_dir, "build/contents.fjson") source_dir = str(src_path.parent) doctree_dir = os.path.join(source_dir, ".doctrees") confoverrides = { "extensions": ["zuul_sphinx"], "master_doc": "contents" } status_log = io.StringIO() # NOTE (fschmidt): This part needs to be in sync with the used version # of Sphinx. Current version is: # https://github.com/sphinx-doc/sphinx/blob/v1.8.1/sphinx/cmd/build.py#L299 with patch_docutils(source_dir), docutils_namespace(): # Remove the color from the Sphinx' console output. Otherwise # the lines cannot be parsed properly as some \n are not set properly. nocolor() app = Sphinx( srcdir=source_dir, confdir=None, outdir=str(build_path.parent), doctreedir=doctree_dir, buildername="json", confoverrides=confoverrides, status=status_log, warning=sys.stderr, ) # Add the mocked SupportedOS directive to get the os information # without rendering it into the resulting HTML page app.add_directive("supported_os", SupportedOS) # Start the Sphinx build app.build(force_all=True, filenames=[]) if app.statuscode: raise SphinxBuildError # Extract the platforms from the logger output platforms = [] status_log.seek(0) for line in status_log.readlines(): prefix, _, platform = line.partition(":") if prefix == SUPPORTED_OS_LOG_PREFIX: platforms.append(platform.strip().lower()) with build_path.open() as build: html_parts = json.load(build) return {"html": html_parts["body"], "platforms": platforms}
def run(self) -> None: warnings.warn('setup.py build_sphinx is deprecated.', RemovedInSphinx70Warning, stacklevel=2) if not color_terminal(): nocolor() if not self.verbose: # type: ignore status_stream = StringIO() else: status_stream = sys.stdout # type: ignore confoverrides: Dict[str, Any] = {} if self.project: confoverrides['project'] = self.project if self.version: confoverrides['version'] = self.version if self.release: confoverrides['release'] = self.release if self.today: confoverrides['today'] = self.today if self.copyright: confoverrides['copyright'] = self.copyright if self.nitpicky: confoverrides['nitpicky'] = self.nitpicky for builder, builder_target_dir in self.builder_target_dirs: app = None try: confdir = self.config_dir or self.source_dir with patch_docutils(confdir), docutils_namespace(): app = Sphinx(self.source_dir, self.config_dir, builder_target_dir, self.doctree_dir, builder, confoverrides, status_stream, freshenv=self.fresh_env, warningiserror=self.warning_is_error, verbosity=self.verbosity, keep_going=self.keep_going) app.build(force_all=self.all_files) if app.statuscode: raise DistutilsExecError('caused by %s builder.' % app.builder.name) except Exception as exc: handle_exception(app, self, exc, sys.stderr) if not self.pdb: raise SystemExit(1) from exc if not self.link_index: continue src = app.config.root_doc + app.builder.out_suffix # type: ignore dst = app.builder.get_outfilename('index') # type: ignore os.symlink(src, dst)
def test_html_with_docutilsconf(app, status, warning): with patch_docutils(app.confdir): app.builder.build(['contents']) result = (app.outdir / 'index.html').text(encoding='utf-8') assert regex_count(r'<th class="field-name">', result) == 0 assert regex_count(r'<th class="field-name" colspan="2">', result) == 2 assert regex_count(r'<td class="option-group">', result) == 0 assert regex_count(r'<td class="option-group" colspan="2">', result) == 2
def test_html_with_docutilsconf(app, status, warning): with patch_docutils(app.confdir): app.builder.build(['contents']) result = (app.outdir / 'contents.html').text(encoding='utf-8') assert regex_count(r'<th class="field-name">', result) == 0 assert regex_count(r'<th class="field-name" colspan="2">', result) == 2 assert regex_count(r'<td class="option-group">', result) == 0 assert regex_count(r'<td class="option-group" colspan="2">', result) == 2
def test_docutils_source_link_with_nonascii_file(app, status, warning): srcdir = path(app.srcdir) mb_name = u'\u65e5\u672c\u8a9e' try: (srcdir / (mb_name + '.txt')).write_text('') except UnicodeEncodeError: from sphinx.testing.path import FILESYSTEMENCODING raise pytest.skip.Exception( 'nonascii filename not supported on this filesystem encoding: ' '%s', FILESYSTEMENCODING) with patch_docutils(app.confdir): app.builder.build_all()
def test_docutils_source_link_with_nonascii_file(app, status, warning): srcdir = path(app.srcdir) mb_name = '\u65e5\u672c\u8a9e' try: (srcdir / (mb_name + '.txt')).write_text('') except UnicodeEncodeError: from sphinx.testing.path import FILESYSTEMENCODING raise pytest.skip.Exception( 'nonascii filename not supported on this filesystem encoding: ' '%s', FILESYSTEMENCODING) with patch_docutils(app.confdir): app.builder.build_all()
def run(self): # type: () -> None if not color_terminal(): nocolor() if not self.verbose: # type: ignore status_stream = StringIO() else: status_stream = sys.stdout # type: ignore confoverrides = {} if self.project: confoverrides['project'] = self.project if self.version: confoverrides['version'] = self.version if self.release: confoverrides['release'] = self.release if self.today: confoverrides['today'] = self.today if self.copyright: confoverrides['copyright'] = self.copyright for builder, builder_target_dir in self.builder_target_dirs: app = None try: confdir = self.config_dir or self.source_dir with patch_docutils(confdir), docutils_namespace(): app = Sphinx(self.source_dir, self.config_dir, builder_target_dir, self.doctree_dir, builder, confoverrides, status_stream, freshenv=self.fresh_env, warningiserror=self.warning_is_error) app.build(force_all=self.all_files) if app.statuscode: raise DistutilsExecError('caused by %s builder.' % app.builder.name) except Exception as exc: handle_exception(app, self, exc, sys.stderr) if not self.pdb: raise SystemExit(1) if not self.link_index: continue src = app.config.master_doc + app.builder.out_suffix # type: ignore dst = app.builder.get_outfilename('index') # type: ignore os.symlink(src, dst)
def run(self): # type: () -> None if not color_terminal(): nocolor() if not self.verbose: # type: ignore status_stream = StringIO() else: status_stream = sys.stdout # type: ignore confoverrides = {} # type: Dict[str, Any] if self.project: confoverrides['project'] = self.project if self.version: confoverrides['version'] = self.version if self.release: confoverrides['release'] = self.release if self.today: confoverrides['today'] = self.today if self.copyright: confoverrides['copyright'] = self.copyright if self.nitpicky: confoverrides['nitpicky'] = self.nitpicky for builder, builder_target_dir in self.builder_target_dirs: app = None try: confdir = self.config_dir or self.source_dir with patch_docutils(confdir), docutils_namespace(): app = Sphinx(self.source_dir, self.config_dir, builder_target_dir, self.doctree_dir, builder, confoverrides, status_stream, freshenv=self.fresh_env, warningiserror=self.warning_is_error) app.build(force_all=self.all_files) if app.statuscode: raise DistutilsExecError( 'caused by %s builder.' % app.builder.name) except Exception as exc: handle_exception(app, self, exc, sys.stderr) if not self.pdb: raise SystemExit(1) if not self.link_index: continue src = app.config.master_doc + app.builder.out_suffix # type: ignore dst = app.builder.get_outfilename('index') # type: ignore os.symlink(src, dst)
def test_man(app, status, warning): with patch_docutils(app.confdir): app.builder.build(['contents']) assert warning.getvalue() == ''
def test_texinfo(app, status, warning): with patch_docutils(app.confdir): app.builder.build(['contents'])
def build_main(argv=sys.argv[1:]): # type: (List[str]) -> int """Sphinx build "main" command-line entry.""" parser = get_parser() args = parser.parse_args(argv) if args.noconfig: args.confdir = None elif not args.confdir: args.confdir = args.sourcedir if not args.doctreedir: args.doctreedir = os.path.join(args.outputdir, '.doctrees') # handle remaining filename arguments filenames = args.filenames missing_files = [] for filename in filenames: if not os.path.isfile(filename): missing_files.append(filename) if missing_files: parser.error(__('cannot find files %r') % missing_files) if args.force_all and filenames: parser.error(__('cannot combine -a option and filenames')) if args.color == 'no' or (args.color == 'auto' and not color_terminal()): nocolor() status = sys.stdout warning = sys.stderr error = sys.stderr if args.quiet: status = None if args.really_quiet: status = warning = None if warning and args.warnfile: try: warnfp = open(args.warnfile, 'w') except Exception as exc: parser.error(__('cannot open warning file %r: %s') % ( args.warnfile, exc)) warning = Tee(warning, warnfp) # type: ignore error = warning confoverrides = {} for val in args.define: try: key, val = val.split('=', 1) except ValueError: parser.error(__('-D option argument must be in the form name=value')) confoverrides[key] = val for val in args.htmldefine: try: key, val = val.split('=') except ValueError: parser.error(__('-A option argument must be in the form name=value')) try: val = int(val) except ValueError: pass confoverrides['html_context.%s' % key] = val if args.nitpicky: confoverrides['nitpicky'] = True app = None try: confdir = args.confdir or args.sourcedir with patch_docutils(confdir), docutils_namespace(): app = Sphinx(args.sourcedir, args.confdir, args.outputdir, args.doctreedir, args.builder, confoverrides, status, warning, args.freshenv, args.warningiserror, args.tags, args.verbosity, args.jobs, args.keep_going) app.build(args.force_all, filenames) return app.statuscode except (Exception, KeyboardInterrupt) as exc: handle_exception(app, args, exc, error) return 2
def main(argv): # type: (List[unicode]) -> int parser = optparse.OptionParser(USAGE, epilog=EPILOG, formatter=MyFormatter()) parser.add_option('--version', action='store_true', dest='version', help='show version information and exit') group = parser.add_option_group('General options') group.add_option('-b', metavar='BUILDER', dest='builder', default='html', help='builder to use; default is html') group.add_option('-a', action='store_true', dest='force_all', help='write all files; default is to only write new and ' 'changed files') group.add_option('-E', action='store_true', dest='freshenv', help='don\'t use a saved environment, always read ' 'all files') group.add_option('-d', metavar='PATH', default=None, dest='doctreedir', help='path for the cached environment and doctree files ' '(default: outdir/.doctrees)') group.add_option('-j', metavar='N', default=1, type='int', dest='jobs', help='build in parallel with N processes where possible') # this option never gets through to this point (it is intercepted earlier) # group.add_option('-M', metavar='BUILDER', dest='make_mode', # help='"make" mode -- as used by Makefile, like ' # '"sphinx-build -M html"') group = parser.add_option_group('Build configuration options') group.add_option('-c', metavar='PATH', dest='confdir', help='path where configuration file (conf.py) is located ' '(default: same as sourcedir)') group.add_option('-C', action='store_true', dest='noconfig', help='use no config file at all, only -D options') group.add_option('-D', metavar='setting=value', action='append', dest='define', default=[], help='override a setting in configuration file') group.add_option('-A', metavar='name=value', action='append', dest='htmldefine', default=[], help='pass a value into HTML templates') group.add_option('-t', metavar='TAG', action='append', dest='tags', default=[], help='define tag: include "only" blocks with TAG') group.add_option('-n', action='store_true', dest='nitpicky', help='nit-picky mode, warn about all missing references') group = parser.add_option_group('Console output options') group.add_option('-v', action='count', dest='verbosity', default=0, help='increase verbosity (can be repeated)') group.add_option('-q', action='store_true', dest='quiet', help='no output on stdout, just warnings on stderr') group.add_option('-Q', action='store_true', dest='really_quiet', help='no output at all, not even warnings') group.add_option('--color', dest='color', action='store_const', const='yes', default='auto', help='Do emit colored output (default: auto-detect)') group.add_option('-N', '--no-color', dest='color', action='store_const', const='no', help='Do not emit colored output (default: auto-detect)') group.add_option('-w', metavar='FILE', dest='warnfile', help='write warnings (and errors) to given file') group.add_option('-W', action='store_true', dest='warningiserror', help='turn warnings into errors') group.add_option('-T', action='store_true', dest='traceback', help='show full traceback on exception') group.add_option('-P', action='store_true', dest='pdb', help='run Pdb on exception') # parse options try: opts, args = parser.parse_args(list(argv[1:])) except SystemExit as err: return err.code # handle basic options if opts.version: print('Sphinx (sphinx-build) %s' % __display_version__) return 0 # get paths (first and second positional argument) try: srcdir = abspath(args[0]) confdir = abspath(opts.confdir or srcdir) if opts.noconfig: confdir = None if not path.isdir(srcdir): print('Error: Cannot find source directory `%s\'.' % srcdir, file=sys.stderr) return 1 if not opts.noconfig and not path.isfile(path.join(confdir, 'conf.py')): print('Error: Config directory doesn\'t contain a conf.py file.', file=sys.stderr) return 1 outdir = abspath(args[1]) if srcdir == outdir: print( 'Error: source directory and destination directory are same.', file=sys.stderr) return 1 except IndexError: parser.print_help() return 1 except UnicodeError: print('Error: Multibyte filename not supported on this filesystem ' 'encoding (%r).' % fs_encoding, file=sys.stderr) return 1 # handle remaining filename arguments filenames = args[2:] errored = False for filename in filenames: if not path.isfile(filename): print('Error: Cannot find file %r.' % filename, file=sys.stderr) errored = True if errored: return 1 # likely encoding used for command-line arguments try: locale = __import__('locale') # due to submodule of the same name likely_encoding = locale.getpreferredencoding() except Exception: likely_encoding = None if opts.force_all and filenames: print('Error: Cannot combine -a option and filenames.', file=sys.stderr) return 1 if opts.color == 'no' or (opts.color == 'auto' and not color_terminal()): nocolor() doctreedir = abspath(opts.doctreedir or path.join(outdir, '.doctrees')) status = sys.stdout warning = sys.stderr error = sys.stderr if opts.quiet: status = None if opts.really_quiet: status = warning = None if warning and opts.warnfile: try: warnfp = open(opts.warnfile, 'w') except Exception as exc: print('Error: Cannot open warning file %r: %s' % (opts.warnfile, exc), file=sys.stderr) sys.exit(1) warning = Tee(warning, warnfp) # type: ignore error = warning confoverrides = {} for val in opts.define: try: key, val = val.split('=', 1) except ValueError: print('Error: -D option argument must be in the form name=value.', file=sys.stderr) return 1 if likely_encoding and isinstance(val, binary_type): try: val = val.decode(likely_encoding) except UnicodeError: pass confoverrides[key] = val for val in opts.htmldefine: try: key, val = val.split('=') except ValueError: print('Error: -A option argument must be in the form name=value.', file=sys.stderr) return 1 try: val = int(val) except ValueError: if likely_encoding and isinstance(val, binary_type): try: val = val.decode(likely_encoding) except UnicodeError: pass confoverrides['html_context.%s' % key] = val if opts.nitpicky: confoverrides['nitpicky'] = True app = None try: with patch_docutils(), docutils_namespace(): app = Sphinx(srcdir, confdir, outdir, doctreedir, opts.builder, confoverrides, status, warning, opts.freshenv, opts.warningiserror, opts.tags, opts.verbosity, opts.jobs) app.build(opts.force_all, filenames) return app.statuscode except (Exception, KeyboardInterrupt) as exc: handle_exception(app, opts, exc, error) return 1
def build_main(argv: List[str] = sys.argv[1:]) -> int: """Sphinx build "main" command-line entry.""" parser = get_parser() args = parser.parse_args(argv) if args.noconfig: args.confdir = None elif not args.confdir: args.confdir = args.sourcedir if not args.doctreedir: args.doctreedir = os.path.join(args.outputdir, '.doctrees') # handle remaining filename arguments filenames = args.filenames missing_files = [] for filename in filenames: if not os.path.isfile(filename): missing_files.append(filename) if missing_files: parser.error(__('cannot find files %r') % missing_files) if args.force_all and filenames: parser.error(__('cannot combine -a option and filenames')) if args.color == 'no' or (args.color == 'auto' and not color_terminal()): nocolor() status = sys.stdout warning = sys.stderr error = sys.stderr if args.quiet: status = None if args.really_quiet: status = warning = None if warning and args.warnfile: try: warnfp = open(args.warnfile, 'w') except Exception as exc: parser.error( __('cannot open warning file %r: %s') % (args.warnfile, exc)) warning = Tee(warning, warnfp) # type: ignore error = warning confoverrides = {} for val in args.define: try: key, val = val.split('=', 1) except ValueError: parser.error( __('-D option argument must be in the form name=value')) confoverrides[key] = val for val in args.htmldefine: try: key, val = val.split('=') except ValueError: parser.error( __('-A option argument must be in the form name=value')) try: val = int(val) except ValueError: pass confoverrides['html_context.%s' % key] = val if args.nitpicky: confoverrides['nitpicky'] = True app = None try: confdir = args.confdir or args.sourcedir with patch_docutils(confdir), docutils_namespace(): app = Sphinx(args.sourcedir, args.confdir, args.outputdir, args.doctreedir, args.builder, confoverrides, status, warning, args.freshenv, args.warningiserror, args.tags, args.verbosity, args.jobs, args.keep_going) app.build(args.force_all, filenames) # Post process equations if requested if os.environ.get('UPDATEHTMLEQS'): ival = os.environ.get('UPDATEHTMLEQS') ival = ival.strip() if ival == 'Y': # Perform post processing of equations here print("Performing post processing of equations.") eqVerbose = "" if os.environ.get('UPDATEHTMLEQSVERBOSE'): eqVerbose = "-v" #@python3 ./postProcessEquations.py -d $(BUILDDIR) -p html -b sphinx -s index.html $(UPDATEHTMLEQSVERBOSE) builddir = os.sep.join(args.outputdir.split('/')[0:-1]) projdir = args.outputdir.split('/')[-1] if builddir == projdir: projdir = "." if eqVerbose != "": cmd = [ 'python3', './postProcessEquations.py', '-d', builddir, '-p', projdir, '-b', 'sphinx', '-s', 'index.html', eqVerbose ] else: cmd = [ 'python3', './postProcessEquations.py', '-d', builddir, '-p', projdir, '-b', 'sphinx', '-s', 'index.html' ] print('Running:', cmd) out = check_output(cmd) return app.statuscode except (Exception, KeyboardInterrupt) as exc: handle_exception(app, args, exc, error) return 2
def main(argv=sys.argv[1:]): # type: ignore # type: (List[unicode]) -> int parser = optparse.OptionParser(USAGE, epilog=EPILOG, formatter=MyFormatter()) parser.add_option('--version', action='store_true', dest='version', help='show version information and exit') group = parser.add_option_group('General options') group.add_option('-b', metavar='BUILDER', dest='builder', default='html', help='builder to use; default is html') group.add_option('-a', action='store_true', dest='force_all', help='write all files; default is to only write new and ' 'changed files') group.add_option('-E', action='store_true', dest='freshenv', help='don\'t use a saved environment, always read ' 'all files') group.add_option('-d', metavar='PATH', default=None, dest='doctreedir', help='path for the cached environment and doctree files ' '(default: outdir/.doctrees)') group.add_option('-j', metavar='N', default=1, type='int', dest='jobs', help='build in parallel with N processes where possible') # this option never gets through to this point (it is intercepted earlier) # group.add_option('-M', metavar='BUILDER', dest='make_mode', # help='"make" mode -- as used by Makefile, like ' # '"sphinx-build -M html"') group = parser.add_option_group('Build configuration options') group.add_option('-c', metavar='PATH', dest='confdir', help='path where configuration file (conf.py) is located ' '(default: same as sourcedir)') group.add_option('-C', action='store_true', dest='noconfig', help='use no config file at all, only -D options') group.add_option('-D', metavar='setting=value', action='append', dest='define', default=[], help='override a setting in configuration file') group.add_option('-A', metavar='name=value', action='append', dest='htmldefine', default=[], help='pass a value into HTML templates') group.add_option('-t', metavar='TAG', action='append', dest='tags', default=[], help='define tag: include "only" blocks with TAG') group.add_option('-n', action='store_true', dest='nitpicky', help='nit-picky mode, warn about all missing references') group = parser.add_option_group('Console output options') group.add_option('-v', action='count', dest='verbosity', default=0, help='increase verbosity (can be repeated)') group.add_option('-q', action='store_true', dest='quiet', help='no output on stdout, just warnings on stderr') group.add_option('-Q', action='store_true', dest='really_quiet', help='no output at all, not even warnings') group.add_option('--color', dest='color', action='store_const', const='yes', default='auto', help='Do emit colored output (default: auto-detect)') group.add_option('-N', '--no-color', dest='color', action='store_const', const='no', help='Do not emit colored output (default: auot-detect)') group.add_option('-w', metavar='FILE', dest='warnfile', help='write warnings (and errors) to given file') group.add_option('-W', action='store_true', dest='warningiserror', help='turn warnings into errors') group.add_option('-T', action='store_true', dest='traceback', help='show full traceback on exception') group.add_option('-P', action='store_true', dest='pdb', help='run Pdb on exception') # parse options try: opts, args = parser.parse_args(argv) except SystemExit as err: return err.code # handle basic options if opts.version: print('Sphinx (sphinx-build) %s' % __display_version__) return 0 # get paths (first and second positional argument) try: srcdir = abspath(args[0]) confdir = abspath(opts.confdir or srcdir) if opts.noconfig: confdir = None if not path.isdir(srcdir): print('Error: Cannot find source directory `%s\'.' % srcdir, file=sys.stderr) return 1 if not opts.noconfig and not path.isfile(path.join(confdir, 'conf.py')): print('Error: Config directory doesn\'t contain a conf.py file.', file=sys.stderr) return 1 outdir = abspath(args[1]) if srcdir == outdir: print('Error: source directory and destination directory are same.', file=sys.stderr) return 1 except IndexError: parser.print_help() return 1 except UnicodeError: print( 'Error: Multibyte filename not supported on this filesystem ' 'encoding (%r).' % fs_encoding, file=sys.stderr) return 1 # handle remaining filename arguments filenames = args[2:] errored = False for filename in filenames: if not path.isfile(filename): print('Error: Cannot find file %r.' % filename, file=sys.stderr) errored = True if errored: return 1 # likely encoding used for command-line arguments try: locale = __import__('locale') # due to submodule of the same name likely_encoding = locale.getpreferredencoding() except Exception: likely_encoding = None if opts.force_all and filenames: print('Error: Cannot combine -a option and filenames.', file=sys.stderr) return 1 if opts.color == 'no' or (opts.color == 'auto' and not color_terminal()): nocolor() doctreedir = abspath(opts.doctreedir or path.join(outdir, '.doctrees')) status = sys.stdout warning = sys.stderr error = sys.stderr if opts.quiet: status = None if opts.really_quiet: status = warning = None if warning and opts.warnfile: try: warnfp = open(opts.warnfile, 'w') except Exception as exc: print('Error: Cannot open warning file %r: %s' % (opts.warnfile, exc), file=sys.stderr) sys.exit(1) warning = Tee(warning, warnfp) # type: ignore error = warning confoverrides = {} for val in opts.define: try: key, val = val.split('=', 1) except ValueError: print('Error: -D option argument must be in the form name=value.', file=sys.stderr) return 1 if likely_encoding and isinstance(val, binary_type): try: val = val.decode(likely_encoding) except UnicodeError: pass confoverrides[key] = val for val in opts.htmldefine: try: key, val = val.split('=') except ValueError: print('Error: -A option argument must be in the form name=value.', file=sys.stderr) return 1 try: val = int(val) except ValueError: if likely_encoding and isinstance(val, binary_type): try: val = val.decode(likely_encoding) except UnicodeError: pass confoverrides['html_context.%s' % key] = val if opts.nitpicky: confoverrides['nitpicky'] = True app = None try: with patch_docutils(), docutils_namespace(): app = Sphinx(srcdir, confdir, outdir, doctreedir, opts.builder, confoverrides, status, warning, opts.freshenv, opts.warningiserror, opts.tags, opts.verbosity, opts.jobs) app.build(opts.force_all, filenames) return app.statuscode except (Exception, KeyboardInterrupt) as exc: handle_exception(app, opts, exc, error) return 1
def main(argv=sys.argv[1:]): # type: ignore # type: (List[unicode]) -> int parser = get_parser() args = parser.parse_args(argv) # get paths (first and second positional argument) try: srcdir = abspath(args.sourcedir) confdir = abspath(args.confdir or srcdir) if args.noconfig: confdir = None if not path.isdir(srcdir): parser.error('cannot find source directory (%s)' % srcdir) if not args.noconfig and not path.isfile(path.join(confdir, 'conf.py')): parser.error("config directory doesn't contain a conf.py file " "(%s)" % confdir) outdir = abspath(args.outputdir) if srcdir == outdir: parser.error('source directory and destination directory are same') except UnicodeError: parser.error('multibyte filename not supported on this filesystem ' 'encoding (%r)' % fs_encoding) # handle remaining filename arguments filenames = args.filenames missing_files = [] for filename in filenames: if not path.isfile(filename): missing_files.append(filename) if missing_files: parser.error('cannot find files %r' % missing_files) # likely encoding used for command-line arguments try: locale = __import__('locale') # due to submodule of the same name likely_encoding = locale.getpreferredencoding() except Exception: likely_encoding = None if args.force_all and filenames: parser.error('cannot combine -a option and filenames') if args.color == 'no' or (args.color == 'auto' and not color_terminal()): nocolor() doctreedir = abspath(args.doctreedir or path.join(outdir, '.doctrees')) status = sys.stdout warning = sys.stderr error = sys.stderr if args.quiet: status = None if args.really_quiet: status = warning = None if warning and args.warnfile: try: warnfp = open(args.warnfile, 'w') except Exception as exc: parser.error('cannot open warning file %r: %s' % (args.warnfile, exc)) warning = Tee(warning, warnfp) # type: ignore error = warning confoverrides = {} for val in args.define: try: key, val = val.split('=', 1) except ValueError: parser.error('-D option argument must be in the form name=value') if likely_encoding and isinstance(val, binary_type): try: val = val.decode(likely_encoding) except UnicodeError: pass confoverrides[key] = val for val in args.htmldefine: try: key, val = val.split('=') except ValueError: parser.error('-A option argument must be in the form name=value') try: val = int(val) except ValueError: if likely_encoding and isinstance(val, binary_type): try: val = val.decode(likely_encoding) except UnicodeError: pass confoverrides['html_context.%s' % key] = val if args.nitpicky: confoverrides['nitpicky'] = True app = None try: with patch_docutils(), docutils_namespace(): app = Sphinx(srcdir, confdir, outdir, doctreedir, args.builder, confoverrides, status, warning, args.freshenv, args.warningiserror, args.tags, args.verbosity, args.jobs) app.build(args.force_all, filenames) return app.statuscode except (Exception, KeyboardInterrupt) as exc: handle_exception(app, args, exc, error) return 2
def build_sphinx( sourcedir, outputdir, *, use_external_toc=True, confdir=None, path_config=None, noconfig=False, confoverrides=None, doctreedir=None, filenames=None, force_all=False, quiet=False, really_quiet=False, builder="html", freshenv=False, warningiserror=False, tags=None, verbosity=0, jobs=None, keep_going=False, ) -> Union[int, Exception]: """Sphinx build "main" command-line entry. This is a slightly modified version of https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/cmd/build.py#L198. """ ####################### # Configuration creation sphinx_config, config_meta = get_final_config( user_yaml=Path(path_config) if path_config else None, cli_config=confoverrides or {}, sourcedir=Path(sourcedir), use_external_toc=use_external_toc, ) ################################## # Preparing Sphinx build arguments # Configuration directory if noconfig: confdir = None elif not confdir: confdir = sourcedir # Doctrees directory if not doctreedir: doctreedir = Path(outputdir).parent.joinpath(".doctrees") if jobs is None: jobs = 1 # Manually re-building files in filenames if filenames is None: filenames = [] missing_files = [] for filename in filenames: if not op.isfile(filename): missing_files.append(filename) if missing_files: raise IOError("cannot find files %r" % missing_files) if force_all and filenames: raise ValueError("cannot combine -a option and filenames") # Debug args (hack to get this to pass through properly) def debug_args(): pass debug_args.pdb = False debug_args.verbosity = False debug_args.traceback = False # Logging behavior status = sys.stdout warning = sys.stderr error = sys.stderr if quiet: status = None if really_quiet: status = warning = None ################### # Build with Sphinx app = None # In case we fail, this allows us to handle the exception try: # These patches temporarily override docutils global variables, # such as the dictionaries of directives, roles and nodes # NOTE: this action is not thread-safe and not suitable for asynchronous use! with patch_docutils(confdir), docutils_namespace(): app = Sphinx( srcdir=sourcedir, confdir=confdir, outdir=outputdir, doctreedir=doctreedir, buildername=builder, confoverrides=sphinx_config, status=status, warning=warning, freshenv=freshenv, warningiserror=warningiserror, tags=tags, verbosity=verbosity, parallel=jobs, keep_going=keep_going, ) app.srcdir = Path(app.srcdir).as_posix() app.outdir = Path(app.outdir).as_posix() app.confdir = Path(app.confdir).as_posix() app.doctreedir = Path(app.doctreedir).as_posix() # We have to apply this update after the sphinx initialisation, # since default_latex_documents is dynamically generated # see sphinx/builders/latex/__init__.py:default_latex_documents new_latex_documents = update_latex_documents( app.config.latex_documents, config_meta["latex_doc_overrides"]) app.config.latex_documents = new_latex_documents # Build latex_doc tuples based on --individualpages option request if config_meta["latex_individualpages"]: from .pdf import autobuild_singlepage_latexdocs # Ask Builder to read the source files to fetch titles and documents app.builder.read() latex_documents = autobuild_singlepage_latexdocs(app) app.config.latex_documents = latex_documents app.build(force_all, filenames) return app.statuscode except (Exception, KeyboardInterrupt) as exc: handle_exception(app, debug_args, exc, error) return exc
def build_sphinx( sourcedir, outputdir, confdir=None, path_config=None, noconfig=False, confoverrides=None, doctreedir=None, filenames=None, force_all=False, quiet=False, really_quiet=False, builder="html", freshenv=False, warningiserror=False, tags=None, verbosity=0, jobs=None, keep_going=False, ) -> Union[int, Exception]: """Sphinx build "main" command-line entry. This is a slightly modified version of https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/cmd/build.py#L198. """ ####################### # Configuration creation sphinx_config, config_meta = get_final_config( user_yaml=Path(path_config) if path_config else None, cli_config=confoverrides or {}, sourcedir=Path(sourcedir), ) ################################## # Preparing Sphinx build arguments # Configuration directory if noconfig: confdir = None elif not confdir: confdir = sourcedir # Doctrees directory if not doctreedir: doctreedir = Path(outputdir).parent.joinpath(".doctrees") if jobs is None: jobs = 1 # Manually re-building files in filenames if filenames is None: filenames = [] missing_files = [] for filename in filenames: if not op.isfile(filename): missing_files.append(filename) if missing_files: raise IOError("cannot find files %r" % missing_files) if force_all and filenames: raise ValueError("cannot combine -a option and filenames") # Debug args (hack to get this to pass through properly) def debug_args(): pass debug_args.pdb = False debug_args.verbosity = False debug_args.traceback = False # Logging behavior status = sys.stdout warning = sys.stderr error = sys.stderr if quiet: status = None if really_quiet: status = warning = None ################### # Build with Sphinx app = None # In case we fail, this allows us to handle the exception try: # These patches temporarily override docutils global variables, # such as the dictionaries of directives, roles and nodes # NOTE: this action is not thread-safe and not suitable for asynchronous use! with patch_docutils(confdir), docutils_namespace(): app = Sphinx( srcdir=sourcedir, confdir=confdir, outdir=outputdir, doctreedir=doctreedir, buildername=builder, confoverrides=sphinx_config, status=status, warning=warning, freshenv=freshenv, warningiserror=warningiserror, tags=tags, verbosity=verbosity, parallel=jobs, keep_going=keep_going, ) app.srcdir = Path(app.srcdir).as_posix() app.outdir = Path(app.outdir).as_posix() app.confdir = Path(app.confdir).as_posix() app.doctreedir = Path(app.doctreedir).as_posix() # We have to apply this update after the sphinx initialisation, # since default_latex_documents is dynamically generated # see sphinx/builders/latex/__init__.py:default_latex_documents # TODO what if the user has specifically set latex_documents? default_latex_document = app.config.latex_documents[0] new_latex_document = update_latex_document( default_latex_document, config_meta["latex_doc_overrides"]) app.config.latex_documents = [new_latex_document] app.build(force_all, filenames) # Write an index.html file in the root to redirect to the first page path_index = outputdir.joinpath("index.html") if sphinx_config["globaltoc_path"]: path_toc = Path(sphinx_config["globaltoc_path"]) if not path_toc.exists(): raise IOError(("You gave a Configuration file path" f"that doesn't exist: {path_toc}")) if path_toc.suffix not in [".yml", ".yaml"]: raise IOError("You gave a Configuration file path" f"that is not a YAML file: {path_toc}") else: path_toc = None if not path_index.exists() and path_toc: toc = yaml.safe_load(path_toc.read_text(encoding="utf8")) if isinstance(toc, dict): first_page = toc["file"] else: first_page = toc[0]["file"] first_page = first_page.split(".")[0] + ".html" with open(path_index, "w", encoding="utf8") as ff: ff.write(REDIRECT_TEXT.format(first_page=first_page)) return app.statuscode except (Exception, KeyboardInterrupt) as exc: handle_exception(app, debug_args, exc, error) return exc
def create_sphinx_app( conf_dir=None, confoverrides=None, source_dir=None, output_dir=None, doctree_dir=None, ) -> Tuple[Sphinx, dict, dict]: """Yield a Sphinx Application, within a context. This context implements the standard sphinx patches to docutils, including the addition of builtin and extension roles and directives. Parameters ---------- conf_dir : str or None path where configuration file (conf.py) is located confoverrides : dict or None dictionary containing parameters that will update those set from conf.py """ # below is taken from sphinx.cmd.build.main # note: this may be removed in future sphinx.locale.setlocale(locale.LC_ALL, "") sphinx.locale.init_console(os.path.join(package_dir, "locale"), "sphinx") # below is adapted from sphinx.cmd.build.build_main confoverrides = confoverrides or {} builder = "html" # these are not needed before build, but there existence is checked in ``Sphinx``` # note source directory and output directory cannot be identical _source_temp = _out_temp = None if source_dir is None: source_dir = _source_temp = tempfile.mkdtemp() if output_dir is None and doctree_dir is None: doctree_dir = output_dir = _out_temp = tempfile.mkdtemp() elif doctree_dir is None: doctree_dir = _out_temp = tempfile.mkdtemp() elif output_dir is None: output_dir = _out_temp = tempfile.mkdtemp() app = None try: log_stream_status = StringIO() log_stream_warning = StringIO() with patch_docutils(conf_dir), docutils_namespace(): from docutils.parsers.rst.directives import _directives from docutils.parsers.rst.roles import _roles from sphinx.util.docutils import additional_nodes app = Sphinx( source_dir, conf_dir, output_dir, doctree_dir, builder, confoverrides=confoverrides, status=log_stream_status, warning=log_stream_warning, # also originally parsed # args.freshenv, args.warningiserror, # args.tags, args.verbosity, args.jobs, args.keep_going ) roles = copy.copy(_roles) directives = copy.copy(_directives) additional_nodes = copy.copy(additional_nodes) except (Exception, KeyboardInterrupt) as exc: # handle_exception(app, args, exc, error) raise exc finally: if _source_temp: shutil.rmtree(_source_temp, ignore_errors=True) if _out_temp: shutil.rmtree(_out_temp, ignore_errors=True) return SphinxAppEnv(app, roles, directives, additional_nodes, log_stream_status, log_stream_warning)
def main(argv=sys.argv[1:]): # type: ignore # type: (List[unicode]) -> int parser = get_parser() # parse options try: args = parser.parse_args(argv) except SystemExit as err: return err.code # get paths (first and second positional argument) try: srcdir = abspath(args.sourcedir) confdir = abspath(args.confdir or srcdir) if args.noconfig: confdir = None if not path.isdir(srcdir): print('Error: Cannot find source directory `%s\'.' % srcdir, file=sys.stderr) return 1 if not args.noconfig and not path.isfile(path.join(confdir, 'conf.py')): print('Error: Config directory doesn\'t contain a conf.py file.', file=sys.stderr) return 1 outdir = abspath(args.outputdir) if srcdir == outdir: print('Error: source directory and destination directory are same.', file=sys.stderr) return 1 except UnicodeError: print( 'Error: Multibyte filename not supported on this filesystem ' 'encoding (%r).' % fs_encoding, file=sys.stderr) return 1 # handle remaining filename arguments filenames = args.filenames errored = False for filename in filenames: if not path.isfile(filename): print('Error: Cannot find file %r.' % filename, file=sys.stderr) errored = True if errored: return 1 # likely encoding used for command-line arguments try: locale = __import__('locale') # due to submodule of the same name likely_encoding = locale.getpreferredencoding() except Exception: likely_encoding = None if args.force_all and filenames: print('Error: Cannot combine -a option and filenames.', file=sys.stderr) return 1 if args.color == 'no' or (args.color == 'auto' and not color_terminal()): nocolor() doctreedir = abspath(args.doctreedir or path.join(outdir, '.doctrees')) status = sys.stdout warning = sys.stderr error = sys.stderr if args.quiet: status = None if args.really_quiet: status = warning = None if warning and args.warnfile: try: warnfp = open(args.warnfile, 'w') except Exception as exc: print('Error: Cannot open warning file %r: %s' % (args.warnfile, exc), file=sys.stderr) sys.exit(1) warning = Tee(warning, warnfp) # type: ignore error = warning confoverrides = {} for val in args.define: try: key, val = val.split('=', 1) except ValueError: print('Error: -D option argument must be in the form name=value.', file=sys.stderr) return 1 if likely_encoding and isinstance(val, binary_type): try: val = val.decode(likely_encoding) except UnicodeError: pass confoverrides[key] = val for val in args.htmldefine: try: key, val = val.split('=') except ValueError: print('Error: -A option argument must be in the form name=value.', file=sys.stderr) return 1 try: val = int(val) except ValueError: if likely_encoding and isinstance(val, binary_type): try: val = val.decode(likely_encoding) except UnicodeError: pass confoverrides['html_context.%s' % key] = val if args.nitpicky: confoverrides['nitpicky'] = True app = None try: with patch_docutils(), docutils_namespace(): app = Sphinx(srcdir, confdir, outdir, doctreedir, args.builder, confoverrides, status, warning, args.freshenv, args.warningiserror, args.tags, args.verbosity, args.jobs) app.build(args.force_all, filenames) return app.statuscode except (Exception, KeyboardInterrupt) as exc: handle_exception(app, args, exc, error) return 1
def build_sphinx( sourcedir, outputdir, confdir=None, noconfig=False, confoverrides=None, htmloverrides=None, doctreedir=None, filenames=None, force_all=False, quiet=False, really_quiet=False, nitpicky=False, builder="html", freshenv=False, warningiserror=False, tags=None, verbosity=0, jobs=None, keep_going=False, ): """Sphinx build "main" command-line entry. This is a slightly modified version of https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/cmd/build.py#L198. """ # Manual configuration overrides if confoverrides is None: confoverrides = {} config = DEFAULT_CONFIG.copy() config.update(confoverrides) # HTML-specific configuration if htmloverrides is None: htmloverrides = {} for key, val in htmloverrides.items(): config["html_context.%s" % key] = val # Configuration directory if noconfig: confdir = None elif not confdir: confdir = sourcedir # Doctrees directory if not doctreedir: doctreedir = op.join(outputdir, ".doctrees") if jobs is None: jobs = 1 # Manually re-building files in filenames if filenames is None: filenames = [] missing_files = [] for filename in filenames: if not op.isfile(filename): missing_files.append(filename) if missing_files: raise ValueError("cannot find files %r" % missing_files) if force_all and filenames: raise ValueError("cannot combine -a option and filenames") # Debug args (hack to get this to pass through properly) def debug_args(): pass debug_args.pdb = False debug_args.verbosity = False debug_args.traceback = False # Logging behavior status = sys.stdout warning = sys.stderr error = sys.stderr if quiet: status = None if really_quiet: status = warning = None # Error on warnings if nitpicky: config["nitpicky"] = True app = None # In case we fail, this allows us to handle the exception try: with patch_docutils(confdir), docutils_namespace(): app = Sphinx( sourcedir, confdir, outputdir, doctreedir, builder, config, status, warning, freshenv, warningiserror, tags, verbosity, jobs, keep_going, ) app.build(force_all, filenames) # Write an index.html file in the root to redirect to the first page path_index = outputdir.joinpath("index.html") if config["globaltoc_path"]: path_toc = Path(config["globaltoc_path"]) if not path_toc.exists(): raise ValueError( f"You gave a Table of Contents path that doesn't exist: {path_toc}" ) if path_toc.suffix not in [".yml", ".yaml"]: raise ValueError( f"You gave a Table of Contents path that is not a YAML file: {path_toc}" ) else: path_toc = None if not path_index.exists() and path_toc: toc = yaml.safe_load(path_toc.read_text()) first_page = toc[0]["file"].split(".")[0] + ".html" with open(path_index, "w") as ff: ff.write(REDIRECT_TEXT.format(first_page=first_page)) return app.statuscode except (Exception, KeyboardInterrupt) as exc: handle_exception(app, debug_args, exc, error) return 2
dark_highlighter = PygmentsBridge("html", "sphinx_library.theme.LibraryDark") with open(os.path.join(SASS_DIR, "_pygments-dark.scss"), "w") as f: f.write(disclaimer) f.write(dark_highlighter.get_stylesheet()) # Compile theme scss. sass.compile( output_style="compressed", dirname=(SASS_DIR, CSS_DIR), ) # Copy pygments to main static dir, to override sphinx's pygments file. os.replace( os.path.join(CSS_DIR, "pygments.css"), os.path.join(STATIC_DIR, "pygments.css"), ) # -- BUILD PROJECT DOCS -------------------------------------------------------- with patch_docutils(DOCS_IN_DIR), docutils_namespace(): app = Sphinx( srcdir=DOCS_IN_DIR, confdir=DOCS_IN_DIR, outdir=DOCS_OUT_DIR, doctreedir=os.path.join(DOCS_OUT_DIR, ".doctrees"), buildername="html", warningiserror=True ) app.build()
def build_sphinx( sourcedir, outputdir, confdir=None, path_config=None, noconfig=False, confoverrides=None, extra_extensions=None, htmloverrides=None, latexoverrides=None, doctreedir=None, filenames=None, force_all=False, quiet=False, really_quiet=False, nitpicky=False, builder="html", freshenv=False, warningiserror=False, tags=None, verbosity=0, jobs=None, keep_going=False, ): """Sphinx build "main" command-line entry. This is a slightly modified version of https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/cmd/build.py#L198. Extra parameters ---------------- extra_extensions : list | None A list of extra extensions to load into Sphinx. This must be done before Sphinx is initialized otherwise the extensions aren't properly initialized. """ if confoverrides is None: confoverrides = {} if latexoverrides is None: latexoverrides = {} ####################### # Configuration updates # Start with the default Sphinx config sphinx_config = DEFAULT_CONFIG.copy() # Update with the *default* config.yml default_yaml_config = yaml.safe_load(PATH_YAML_DEFAULT.read_text()) new_config = yaml_to_sphinx(default_yaml_config) _recursive_update(sphinx_config, new_config) # Update with the given config file, if it exists if path_config: path_config = Path(path_config) yaml_config = yaml.safe_load(path_config.read_text()) # Check for manual Sphinx over-rides which we'll apply later to take precedence sphinx_overrides = yaml_config.get("sphinx", {}).get("config") if sphinx_overrides: confoverrides.update(sphinx_overrides) # Some latex-specific changes we need to make if we're building latex if builder == "latex": # First update the overrides with the latex config latexoverrides.update(yaml_config.get("latex", {})) # If we have a document title and no explicit latex title, use the doc title if "title" in yaml_config.keys(): latex_documents = latexoverrides.get("latex_documents", {}) if "title" not in latex_documents: latex_documents["title"] = yaml_config["title"] latexoverrides["latex_documents"] = latex_documents new_config = yaml_to_sphinx(yaml_config) _recursive_update(sphinx_config, new_config) # Manual configuration overrides from the CLI _recursive_update(sphinx_config, confoverrides) # HTML-specific configuration from the CLI if htmloverrides is None: htmloverrides = {} for key, val in htmloverrides.items(): sphinx_config["html_context.%s" % key] = val # #LaTeX-specific configuration # TODO: if this is included we should ignore latex_documents # if latexoverrides is None: # latexoverrides = {} # for key, val in latexoverrides.items(): # config[key] = val # Add the folder `_static` if it exists if Path(sourcedir).joinpath("_static").is_dir(): paths_static = sphinx_config.get("html_static_path", []) paths_static.append("_static") sphinx_config["html_static_path"] = paths_static # Flags from the CLI # Raise more warnings if nitpicky: sphinx_config["nitpicky"] = True ################################## # Preparing Sphinx build arguments # Configuration directory if noconfig: confdir = None elif not confdir: confdir = sourcedir # Doctrees directory if not doctreedir: doctreedir = Path(outputdir).parent.joinpath(".doctrees") if jobs is None: jobs = 1 # Manually re-building files in filenames if filenames is None: filenames = [] missing_files = [] for filename in filenames: if not op.isfile(filename): missing_files.append(filename) if missing_files: raise ValueError("cannot find files %r" % missing_files) if force_all and filenames: raise ValueError("cannot combine -a option and filenames") # Debug args (hack to get this to pass through properly) def debug_args(): pass debug_args.pdb = False debug_args.verbosity = False debug_args.traceback = False # Logging behavior status = sys.stdout warning = sys.stderr error = sys.stderr if quiet: status = None if really_quiet: status = warning = None ################### # Build with Sphinx app = None # In case we fail, this allows us to handle the exception try: # This patch is what Sphinx does, so we copy it blindly... with patch_docutils(confdir), docutils_namespace(): app = Sphinx( srcdir=sourcedir, confdir=confdir, outdir=outputdir, doctreedir=doctreedir, buildername=builder, confoverrides=sphinx_config, status=status, warning=warning, freshenv=freshenv, warningiserror=warningiserror, tags=tags, verbosity=verbosity, parallel=jobs, keep_going=keep_going, ) # Apply Latex Overrides for latex_documents if (latexoverrides is not None and "latex_documents" in latexoverrides.keys()): from .pdf import update_latex_documents latex_documents = update_latex_documents( app.config.latex_documents[0], latexoverrides) app.config.latex_documents = [latex_documents] app.build(force_all, filenames) # Write an index.html file in the root to redirect to the first page path_index = outputdir.joinpath("index.html") if sphinx_config["globaltoc_path"]: path_toc = Path(sphinx_config["globaltoc_path"]) if not path_toc.exists(): raise ValueError(("You gave a Configuration file path" f"that doesn't exist: {path_toc}")) if path_toc.suffix not in [".yml", ".yaml"]: raise ValueError("You gave a Configuration file path" f"that is not a YAML file: {path_toc}") else: path_toc = None if not path_index.exists() and path_toc: toc = yaml.safe_load(path_toc.read_text()) if isinstance(toc, dict): first_page = toc["file"] else: first_page = toc[0]["file"] first_page = first_page.split(".")[0] + ".html" with open(path_index, "w") as ff: ff.write(REDIRECT_TEXT.format(first_page=first_page)) return app.statuscode except (Exception, KeyboardInterrupt) as exc: handle_exception(app, debug_args, exc, error) return exc
def build_sphinx( sourcedir, outputdir, confdir=None, noconfig=False, confoverrides=None, extra_extensions=None, htmloverrides=None, latexoverrides=None, doctreedir=None, filenames=None, force_all=False, quiet=False, really_quiet=False, nitpicky=False, builder="html", freshenv=False, warningiserror=False, tags=None, verbosity=0, jobs=None, keep_going=False, ): """Sphinx build "main" command-line entry. This is a slightly modified version of https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/cmd/build.py#L198. Extra parameters ---------------- extra_extensions : list | None A list of extra extensions to load into Sphinx. This must be done before Sphinx is initialized otherwise the extensions aren't properly initialized. """ # Manual configuration overrides if confoverrides is None: confoverrides = {} config = DEFAULT_CONFIG.copy() config.update(confoverrides) if extra_extensions: if not isinstance(extra_extensions, list): extra_extensions = [extra_extensions] for ext in extra_extensions: config["extensions"].append(ext) # HTML-specific configuration if htmloverrides is None: htmloverrides = {} for key, val in htmloverrides.items(): config["html_context.%s" % key] = val # #LaTeX-specific configuration # TODO: if this is included we should ignore latex_documents # if latexoverrides is None: # latexoverrides = {} # for key, val in latexoverrides.items(): # config[key] = val # Configuration directory if noconfig: confdir = None elif not confdir: confdir = sourcedir # Doctrees directory if not doctreedir: doctreedir = Path(outputdir).parent.joinpath(".doctrees") if jobs is None: jobs = 1 # Manually re-building files in filenames if filenames is None: filenames = [] missing_files = [] for filename in filenames: if not op.isfile(filename): missing_files.append(filename) if missing_files: raise ValueError("cannot find files %r" % missing_files) if force_all and filenames: raise ValueError("cannot combine -a option and filenames") # Debug args (hack to get this to pass through properly) def debug_args(): pass debug_args.pdb = False debug_args.verbosity = False debug_args.traceback = False # Logging behavior status = sys.stdout warning = sys.stderr error = sys.stderr if quiet: status = None if really_quiet: status = warning = None # Raise more warnings if nitpicky: config["nitpicky"] = True app = None # In case we fail, this allows us to handle the exception try: with patch_docutils(confdir), docutils_namespace(): app = Sphinx( sourcedir, confdir, outputdir, doctreedir, builder, config, status, warning, freshenv, warningiserror, tags, verbosity, jobs, keep_going, ) # Apply Latex Overrides for latex_documents if ( latexoverrides is not None and "latex_documents" in latexoverrides.keys() ): from .pdf import update_latex_documents latex_documents = update_latex_documents( app.config.latex_documents[0], latexoverrides ) app.config.latex_documents = [latex_documents] app.build(force_all, filenames) # Write an index.html file in the root to redirect to the first page path_index = outputdir.joinpath("index.html") if config["globaltoc_path"]: path_toc = Path(config["globaltoc_path"]) if not path_toc.exists(): raise ValueError( ( f"You gave a Configuration file path" "that doesn't exist: {path_toc}" ) ) if path_toc.suffix not in [".yml", ".yaml"]: raise ValueError( f"You gave a Configuration file path" "that is not a YAML file: {path_toc}" ) else: path_toc = None if not path_index.exists() and path_toc: toc = yaml.safe_load(path_toc.read_text()) if isinstance(toc, dict): first_page = toc["file"] else: first_page = toc[0]["file"] first_page = first_page.split(".")[0] + ".html" with open(path_index, "w") as ff: ff.write(REDIRECT_TEXT.format(first_page=first_page)) return app.statuscode except (Exception, KeyboardInterrupt) as exc: handle_exception(app, debug_args, exc, error) return exc