def emit_feed(app, exc): import os.path ordered_items = app.builder.env.feed_items.values() feed = app.builder.env.feed_feed ordered_items.sort( cmp=lambda x,y: cmp(x['pubDate'],y['pubDate']), reverse=True) for item in ordered_items: feed.items.append(item) path = os.path.join(app.builder.outdir, app.config.feed_filename) feed.format_rss2_file(path) from os import path from sphinx.application import ENV_PICKLE_FILENAME from sphinx.util.console import bold # save the environment builder = app.builder builder.info(bold('pickling environment... '), nonl=True) builder.env.topickle(path.join(builder.doctreedir, ENV_PICKLE_FILENAME)) builder.info('done') # global actions builder.info(bold('checking consistency... '), nonl=True) builder.env.check_consistency() builder.info('done')
def handle_finish(self): self.info(bold('dumping search index... '), nonl=True) self.indexer.prune(self.env.all_docs) searchindexfn = path.join(self.outdir, self.searchindex_filename) # first write to a temporary file, so that if dumping fails, # the existing index won't be overwritten f = open(searchindexfn + '.tmp', 'wb') try: self.indexer.dump(f, self.indexer_format) finally: f.close() movefile(searchindexfn + '.tmp', searchindexfn) self.info('done') self.info(bold('dumping object inventory... '), nonl=True) f = open(path.join(self.outdir, INVENTORY_FILENAME), 'w') try: f.write('# Sphinx inventory version 1\n') f.write('# Project: %s\n' % self.config.project.encode('utf-8')) f.write('# Version: %s\n' % self.config.version) for modname, info in self.env.modules.iteritems(): f.write('%s mod %s\n' % (modname, self.get_target_uri(info[0]))) for refname, (docname, desctype) in self.env.descrefs.iteritems(): f.write('%s %s %s\n' % (refname, desctype, self.get_target_uri(docname))) finally: f.close() self.info('done')
def emit_feed(app, exc): ordered_items = list(app.builder.env.feed_items.values()) feed = app.builder.env.feed_feed ordered_items.sort( key=lambda x: x['pubDate'], reverse=True, ) for item in ordered_items: feed.items.append(item) path = os.path.join(app.builder.outdir, app.config.feed_filename) feed.format_rss2_file(path) # save the environment builder = app.builder LOG.info(bold('pickling environment... '), nonl=True) pickle_path = os.path.join(builder.doctreedir, ENV_PICKLE_FILENAME) with open(pickle_path, 'wb') as f: pickle.dump(builder.env, f) LOG.info('done') # global actions LOG.info(bold('checking consistency... '), nonl=True) builder.env.check_consistency() LOG.info('done')
def copy_media(app, exception): """ Move our dynamically generated files after build. """ if app.builder.name in ["readthedocs", "readthedocsdirhtml"] and not exception: for file in ["readthedocs-dynamic-include.js_t", "readthedocs-data.js_t", "searchtools.js_t"]: app.info(bold("Copying %s... " % file), nonl=True) dest_dir = os.path.join(app.builder.outdir, "_static") source = os.path.join(os.path.abspath(os.path.dirname(__file__)), "_static", file) try: ctx = app.builder.globalcontext except AttributeError: ctx = {} if app.builder.indexer is not None: ctx.update(app.builder.indexer.context_for_searchtool()) copy_static_entry(source, dest_dir, app.builder, ctx) app.info("done") if "comments" in app.builder.name and not exception: for file in STATIC_FILES: app.info(bold("Copying %s... " % file), nonl=True) dest_dir = os.path.join(app.builder.outdir, "_static") source = os.path.join(os.path.abspath(os.path.dirname(__file__)), "_static", file) try: ctx = app.builder.globalcontext except AttributeError: ctx = {} ctx["websupport2_base_url"] = app.builder.config.websupport2_base_url ctx["websupport2_static_url"] = app.builder.config.websupport2_static_url copy_static_entry(source, dest_dir, app.builder, ctx) app.info("done")
def get_input(fields, argv): print bold('Welcome to the sffms quickstart utility!') print ''' Please enter values for the following settings (just press Enter to accept a default value, if one is given in brackets).''' fields['path'] = get_path_from_cmdline(argv) if fields['path'] is None: print ''' Enter the directory in which to create your manuscript. The default is this directory.''' do_prompt(fields, 'path', 'Path to your manuscript', '.', is_path) get_clear_path(fields) print ''' You can use this script to set up a novel or a short story. For novels, sffms-quickstart creates a master story file and three chapter files, while for short stories, sffms-quickstart generates a single master story file. Short stories are also typeset a little differently from novels.''' do_prompt(fields, 'novel', 'Are you creating a novel? (y/N)', 'n', boolean) print '' do_prompt(fields, 'title', 'Enter your manuscript\'s title') fields['reST_title'] = generate_reST_title(fields['title']) # We sanitize the title after creating the 'reST_title' because we # don't actually want to escape those characters in reST -- just in Python. fields['title'] = py_sanitize(fields['title']) print ''' Your title appears in a running header at the top of the page. If you have a long title, consider supplying a shorter version for inclusion in the running header. For example, for the story 'The Adventures of Sherlock Holmes: A Scandal in Bohemia,' the short version could be 'A Scandal in Bohemia.' ''' do_prompt(fields, 'runningtitle', 'Enter your manuscript\'s short title (optional)', validator=optional) print '' do_prompt(fields, 'author', 'Enter your full name', validator=required_string) print ''' Your full name (or surname, if specified) appears in the running header. Consider supplying your surname here.''' do_prompt(fields, 'surname', 'Enter your surname (optional)', validator=optional) print ''' You may enter a free-form multi-line address, including a postal address, telephone number, email address, or whatever contact info you wish to include. The address is displayed on the title page. When you are done entering the address, enter an empty (blank) line.''' prompt_address(fields) print ''' Your story source is contained in a master file. This file either contains the entire story, or a table of contents that points to separate chapter files.''' do_prompt(fields, 'master_doc', 'Name of your master source file (without suffix)', 'manuscript', validator=py_sanitize) fields['now'] = time.asctime() fields['copyright'] = time.strftime('%Y') + ', ' + fields['author']
def finish(self): # copy image files if self.images: self.info(bold("copying images..."), nonl=1) for src, dest in self.images.iteritems(): self.info(" " + src, nonl=1) copyfile(path.join(self.srcdir, src), path.join(self.outdir, dest)) self.info() # copy additional files if self.config.latex_additional_files: self.info(bold("copying additional files..."), nonl=1) for filename in self.config.latex_additional_files: self.info(" " + filename, nonl=1) copyfile(path.join(self.confdir, filename), path.join(self.outdir, path.basename(filename))) self.info() # the logo is handled differently if self.config.latex_logo: logobase = path.basename(self.config.latex_logo) copyfile(path.join(self.confdir, self.config.latex_logo), path.join(self.outdir, logobase)) self.info(bold("copying TeX support files... "), nonl=True) staticdirname = path.join(package_dir, "texinputs") for filename in os.listdir(staticdirname): if not filename.startswith("."): copyfile(path.join(staticdirname, filename), path.join(self.outdir, filename)) self.info("done")
def finish(self): # copy image files if self.images: self.info(bold('copying images...'), nonl=1) for src, dest in iteritems(self.images): self.info(' '+src, nonl=1) copyfile(path.join(self.srcdir, src), path.join(self.outdir, dest)) self.info() # copy TeX support files from texinputs self.info(bold('copying TeX support files...')) staticdirname = path.join(package_dir, 'texinputs') for filename in os.listdir(staticdirname): if not filename.startswith('.'): copyfile(path.join(staticdirname, filename), path.join(self.outdir, filename)) # copy additional files if self.config.latex_additional_files: self.info(bold('copying additional files...'), nonl=1) for filename in self.config.latex_additional_files: self.info(' '+filename, nonl=1) copyfile(path.join(self.confdir, filename), path.join(self.outdir, path.basename(filename))) self.info() # the logo is handled differently if self.config.latex_logo: logobase = path.basename(self.config.latex_logo) copyfile(path.join(self.confdir, self.config.latex_logo), path.join(self.outdir, logobase)) self.info('done')
def finish(self): # copy image files if self.images: self.info(bold('copying images...'), nonl=1) for src, dest in iteritems(self.images): self.info(' '+src, nonl=1) copy_asset_file(path.join(self.srcdir, src), path.join(self.outdir, dest)) self.info() # copy TeX support files from texinputs context = {'latex_engine': self.config.latex_engine} self.info(bold('copying TeX support files...')) staticdirname = path.join(package_dir, 'texinputs') for filename in os.listdir(staticdirname): if not filename.startswith('.'): copy_asset_file(path.join(staticdirname, filename), self.outdir, context=context) # copy additional files if self.config.latex_additional_files: self.info(bold('copying additional files...'), nonl=1) for filename in self.config.latex_additional_files: self.info(' '+filename, nonl=1) copy_asset_file(path.join(self.confdir, filename), self.outdir) self.info() # the logo is handled differently if self.config.latex_logo: if not path.isfile(path.join(self.confdir, self.config.latex_logo)): raise SphinxError('logo file %r does not exist' % self.config.latex_logo) else: copy_asset_file(path.join(self.confdir, self.config.latex_logo), self.outdir) self.info('done')
def finish(self): # type: () -> None self.copy_image_files() # copy TeX support files from texinputs context = {'latex_engine': self.config.latex_engine} logger.info(bold('copying TeX support files...')) staticdirname = path.join(package_dir, 'texinputs') for filename in os.listdir(staticdirname): if not filename.startswith('.'): copy_asset_file(path.join(staticdirname, filename), self.outdir, context=context) # use pre-1.6.x Makefile for make latexpdf on Windows if os.name == 'nt': staticdirname = path.join(package_dir, 'texinputs_win') copy_asset_file(path.join(staticdirname, 'Makefile_t'), self.outdir, context=context) # copy additional files if self.config.latex_additional_files: logger.info(bold('copying additional files...'), nonl=1) for filename in self.config.latex_additional_files: logger.info(' ' + filename, nonl=1) copy_asset_file(path.join(self.confdir, filename), self.outdir) logger.info('') # the logo is handled differently if self.config.latex_logo: if not path.isfile(path.join(self.confdir, self.config.latex_logo)): raise SphinxError('logo file %r does not exist' % self.config.latex_logo) else: copy_asset_file(path.join(self.confdir, self.config.latex_logo), self.outdir) logger.info('done')
def emit_feed(app, exc): ordered_items = list(app.builder.env.feed_items.values()) feed = app.builder.env.feed_feed # ordered_items.sort(key=lambda x: x['pubDate'], reverse=True) ordered_items.sort(key=lambda x: x.published()) for item in ordered_items: feed.add_entry(item) # prepends the item # for k, v in item.items(): # getattr(e, k)(v) path = os.path.join(app.builder.outdir, app.config.feed_filename) # print(20190315, path) feed.rss_file(path) return # LS 20180204 The following code (pickle the environment and check # consistency at this point) caused an error when also bibtex was # installed. I deactivated it since I don't know why it's needed. from os import path from sphinx.application import ENV_PICKLE_FILENAME from sphinx.util.console import bold # save the environment builder = app.builder builder.info(bold('pickling environment... '), nonl=True) builder.env.topickle(path.join(builder.doctreedir, ENV_PICKLE_FILENAME)) builder.info('done') # global actions builder.info(bold('checking consistency... '), nonl=True) builder.env.check_consistency() builder.info('done')
def build(self, force_all=False, filenames=None): # type: (bool, List[unicode]) -> None try: if force_all: self.builder.compile_all_catalogs() self.builder.build_all() elif filenames: self.builder.compile_specific_catalogs(filenames) self.builder.build_specific(filenames) else: self.builder.compile_update_catalogs() self.builder.build_update() status = (self.statuscode == 0 and __('succeeded') or __('finished with problems')) if self._warncount: logger.info(bold(__('build %s, %s warning%s.') % (status, self._warncount, self._warncount != 1 and 's' or ''))) else: logger.info(bold(__('build %s.') % status)) except Exception as err: # delete the saved env to force a fresh build next time envfile = path.join(self.doctreedir, ENV_PICKLE_FILENAME) if path.isfile(envfile): os.unlink(envfile) self.emit('build-finished', err) raise else: self.emit('build-finished', None) self.builder.cleanup()
def merge_js_index(app): """ Merge the JS indexes of the sub-docs into the main JS index """ app.info('') app.info(bold('Merging js index files...')) mapping = app.builder.indexer._mapping for curdoc in app.env.config.multidocs_subdoc_list: app.info(" %s:"%curdoc, nonl=1) fixpath = lambda path: os.path.join(curdoc, path) index = get_js_index(app, curdoc) if index is not None: # merge the mappings app.info(" %s js index entries"%(len(index._mapping))) for (ref, locs) in index._mapping.iteritems(): newmapping = set(map(fixpath, locs)) if ref in mapping: newmapping = mapping[ref] | newmapping mapping[unicode(ref)] = newmapping # merge the titles titles = app.builder.indexer._titles for (res, title) in index._titles.iteritems(): titles[fixpath(res)] = title # TODO: merge indexer._objtypes, indexer._objnames as well # Setup source symbolic links dest = os.path.join(app.outdir, "_sources", curdoc) if not os.path.exists(dest): os.symlink(os.path.join("..", curdoc, "_sources"), dest) app.info('... done (%s js index entries)'%(len(mapping))) app.info(bold('Writing js search indexes...'), nonl=1) return [] # no extra page to setup
def build(self, force_all=False, filenames=None): try: if force_all: self.builder.compile_all_catalogs() self.builder.build_all() elif filenames: self.builder.compile_specific_catalogs(filenames) self.builder.build_specific(filenames) else: self.builder.compile_update_catalogs() self.builder.build_update() status = self.statuscode == 0 and "succeeded" or "finished with problems" if self._warncount: self.info( bold("build %s, %s warning%s." % (status, self._warncount, self._warncount != 1 and "s" or "")) ) else: self.info(bold("build %s." % status)) except Exception as err: # delete the saved env to force a fresh build next time envfile = path.join(self.doctreedir, ENV_PICKLE_FILENAME) if path.isfile(envfile): os.unlink(envfile) self.emit("build-finished", err) raise else: self.emit("build-finished", None) self.builder.cleanup()
def get_clear_path(fields): while os.path.isfile(os.path.join(fields['path'], 'conf.py')): print bold('\nError: your path already has a conf.py.') print 'sffms-quickstart will not overwrite existing projects.\n' do_prompt(fields, 'path', 'Please enter a new path (or just Enter to exit)', '', is_path) if not fields['path']: sys.exit(1)
def write(self, *ignored): global logger if logger is None: # Sphinx 1.2.3 compatibility, I know it's bad logger = self mastername = self.config.master_doc mastertree = self.env.get_doctree(mastername) mastertitle = u'%(project)s v%(release)s documentation' % \ {'project': self.config.project, 'release': self.config.release} if hasattr(self.config, 'text_title') and self.config.text_title is not None: mastertitle = self.config.text_title logger.info(bold('preparing documents... '), nonl=True) self.prepare_writing(self.env.found_docs) logger.info('done') logger.info(bold('assembling single document... '), nonl=True) tree = None try: tree = inline_all_toctrees(self, set(), mastername, mastertree, darkgreen) except TypeError: tree = inline_all_toctrees(self, set(), mastername, mastertree, darkgreen, [mastername]) tree['docname'] = mastername toctree = getTocTree(self, mastername, self, False) tree.insert(0, nodes.section() + nodes.title(mastertitle, mastertitle)) tree.insert(1, toctree) self.env.resolve_references(tree, mastername, self) logger.info('done') logger.info(bold('writing... '), nonl=True) if hasattr(self, "write_doc_serialized"): self.write_doc_serialized(mastername, tree) self.write_doc(mastername, tree) logger.info('done')
def ask_user(d): """Wrap sphinx_quickstart.ask_user, and add additional questions.""" # Print welcome message msg = bold('Welcome to the Hieroglyph %s quickstart utility.') % ( version(), ) print(msg) msg = """ This will ask questions for creating a Hieroglyph project, and then ask some basic Sphinx questions. """ print(msg) # set a few defaults that we don't usually care about for Hieroglyph d.update({ 'version': datetime.date.today().strftime('%Y.%m.%d'), 'release': datetime.date.today().strftime('%Y.%m.%d'), 'ext_autodoc': False, 'ext_doctest': True, 'ext_intersphinx': True, 'ext_todo': True, 'ext_coverage': True, 'ext_pngmath': True, 'ext_mathjax': False, 'ext_ifconfig': True, 'ext_viewcode': False, }) if 'project' not in d: print(''' The presentation title will be included on the title slide.''') sphinx_quickstart.do_prompt(d, 'project', 'Presentation title') if 'author' not in d: sphinx_quickstart.do_prompt(d, 'author', 'Author name(s)') # slide_theme msg = """ Hieroglyph includes two themes: * """ + bold("slides") + """ The default theme, with different styling for first, second, and third level headings. * """ + bold("single-level") + """ All slides are styled the same, with the heading at the top. Which theme would you like to use?""" print(msg) # XXX make a themes dict that has the keys/descriptions sphinx_quickstart.do_prompt( d, 'slide_theme', 'Slide Theme', 'slides', sphinx_quickstart.choice('slides', 'single-level',), ) # Ask original questions print("") sphinx_ask_user(d)
def write(self, *ignored): if self.config.man_pages: # build manpages from config.man_pages as usual ManualPageBuilder.write(self, *ignored) self.info(bold("scan master tree for kernel-doc man-pages ... ") + darkgreen("{"), nonl=True) master_tree = self.env.get_doctree(self.config.master_doc) master_tree = inline_all_toctrees( self, set(), self.config.master_doc, master_tree, darkgreen, [self.config.master_doc]) self.info(darkgreen("}")) man_nodes = master_tree.traverse(condition=self.is_manpage) if not man_nodes and not self.config.man_pages: self.warn('no "man_pages" config value nor manual section found; no manual pages ' 'will be written') return self.info(bold('writing man pages ... '), nonl=True) for man_parent in man_nodes: doc_tree = self.get_partial_document(man_parent) Section2Manpage(doc_tree).apply() if not doc_tree.man_info["authors"] and self.config.author: doc_tree.man_info["authors"].append(self.config.author) doc_writer = ManualPageWriter(self) doc_settings = OptionParser( defaults = self.env.settings , components = (doc_writer,) , read_config_files = True , ).get_default_values() doc_settings.__dict__.update(doc_tree.man_info) doc_tree.settings = doc_settings targetname = '%s.%s' % (doc_tree.man_info.title, doc_tree.man_info.section) if doc_tree.man_info.decl_type in [ "struct", "enum", "union", "typedef"]: targetname = "%s_%s" % (doc_tree.man_info.decl_type, targetname) destination = FileOutput( destination_path = path.join(self.outdir, targetname) , encoding='utf-8') self.info(darkgreen(targetname) + " ", nonl=True) self.env.resolve_references(doc_tree, doc_tree.man_info.manpage, self) # remove pending_xref nodes for pendingnode in doc_tree.traverse(addnodes.pending_xref): pendingnode.replace_self(pendingnode.children) doc_writer.write(doc_tree, destination) self.info()
def write(self, *ignored): docnames = self.env.all_docs self.info(bold('preparing documents... '), nonl=True) self.prepare_writing(docnames) self.info('done') self.info(bold('assembling single document... '), nonl=True) doctree = self.assemble_doctree() self.info() self.info(bold('writing... '), nonl=True) self.write_doc(self.config.master_doc, doctree) self.info('done')
def finish(self): # type: () -> None self.copy_image_files() self.write_message_catalog() # copy TeX support files from texinputs # configure usage of xindy (impacts Makefile and latexmkrc) # FIXME: convert this rather to a confval with suitable default # according to language ? but would require extra documentation if self.config.language: xindy_lang_option = \ XINDY_LANG_OPTIONS.get(self.config.language[:2], '-L general -C utf8 ') xindy_cyrillic = self.config.language[:2] in XINDY_CYRILLIC_SCRIPTS else: xindy_lang_option = '-L english -C utf8 ' xindy_cyrillic = False context = { 'latex_engine': self.config.latex_engine, 'xindy_use': self.config.latex_use_xindy, 'xindy_lang_option': xindy_lang_option, 'xindy_cyrillic': xindy_cyrillic, } logger.info(bold(__('copying TeX support files...'))) staticdirname = path.join(package_dir, 'texinputs') for filename in os.listdir(staticdirname): if not filename.startswith('.'): copy_asset_file(path.join(staticdirname, filename), self.outdir, context=context) # use pre-1.6.x Makefile for make latexpdf on Windows if os.name == 'nt': staticdirname = path.join(package_dir, 'texinputs_win') copy_asset_file(path.join(staticdirname, 'Makefile_t'), self.outdir, context=context) # copy additional files if self.config.latex_additional_files: logger.info(bold(__('copying additional files...')), nonl=1) for filename in self.config.latex_additional_files: logger.info(' ' + filename, nonl=1) copy_asset_file(path.join(self.confdir, filename), self.outdir) logger.info('') # the logo is handled differently if self.config.latex_logo: if not path.isfile(path.join(self.confdir, self.config.latex_logo)): raise SphinxError(__('logo file %r does not exist') % self.config.latex_logo) else: copy_asset_file(path.join(self.confdir, self.config.latex_logo), self.outdir) logger.info(__('done'))
def write(self, *ignored): docnames = self.env.all_docs self.info(bold('preparing documents... '), nonl=True) self.prepare_writing(docnames) self.info('done') self.info(bold('assembling single document... '), nonl=True) doctree = self.assemble_doctree() doctree.settings = self.docsettings self.env.toc_secnumbers = self.assemble_toc_secnumbers() self.secnumbers = self.env.toc_secnumbers.get(self.config.master_doc, {}) self.fignumbers = self.env.toc_fignumbers.get(self.config.master_doc, {}) target_uri = self.get_target_uri(self.config.master_doc) self.imgpath = relative_uri(target_uri, '_images') self.dlpath = relative_uri(target_uri, '_downloads') self.current_docname = self.config.master_doc if self.should_submit: self.post_process_images(doctree) meta = self.env.metadata.get(self.config.master_doc) title = self.env.longtitles.get(self.config.master_doc) toc = self.env.get_toctree_for(self.config.master_doc, self, False) self.fix_refuris(toc) rendered_title = self.render_partial(title)['title'] rendered_toc = self.render_partial(toc)['fragment'] layout_key = meta.get('deconstlayout', self.config.deconst_default_layout) rendered_body = self.write_body(doctree) envelope = { "title": meta.get('deconsttitle', rendered_title), "body": rendered_body, "toc": rendered_toc, "layout_key": layout_key, "meta": dict(meta) } outfile = os.path.join(self.outdir, self.config.master_doc + '.json') with open(outfile, 'w', encoding="utf-8") as dumpfile: json.dump(envelope, dumpfile)
def write(self, *ignored): docnames = self.env.all_docs self.info(bold("preparing documents... "), nonl=True) self.prepare_writing(docnames) self.info("done") self.info(bold("assembling single document... "), nonl=True) doctree = self.assemble_doctree() self.info() self.info(bold("writing... "), nonl=True) self.write_doc_serialized(self.config.master_doc, doctree) self.write_doc(self.config.master_doc, doctree) self.info("done")
def write(self, *ignored): docnames = self.env.all_docs self.info(bold('preparing documents... '), nonl=True) self.prepare_writing(docnames) self.info('done') self.info(bold('assembling single document... '), nonl=True) doctree = self.assemble_doctree() self.info() self.info(bold('writing... '), nonl=True) docname = "%s-%s" % (self.config.project, self.config.version) self.write_doc(docname, doctree) self.info('done')
def print_success(fields): print print bold('Finished: Initial manuscript files created in directory \n%s.' % os.path.abspath(fields['path'])) print if fields['novel'] is True: print 'You should now begin adding material to your chapter .txt files.' print 'To add new chapters or change their filenames, edit %s.txt.' % fields['master_doc'] else: print 'You should now begin adding material to %s.txt.' % fields['master_doc'] print 'To generate PDF, run the command ' + bold('make sffmspdf') + ' in the directory.' print 'Happy writing!' print
def _write_parallel(self, docnames, nproc): # type: (Sequence[unicode], int) -> None def write_process(docs): # type: (List[Tuple[unicode, nodes.Node]]) -> None self.app.phase = BuildPhase.WRITING for docname, doctree in docs: self.write_doc(docname, doctree) # warm up caches/compile templates using the first document firstname, docnames = docnames[0], docnames[1:] self.app.phase = BuildPhase.RESOLVING doctree = self.env.get_and_resolve_doctree(firstname, self) self.app.phase = BuildPhase.WRITING self.write_doc_serialized(firstname, doctree) self.write_doc(firstname, doctree) tasks = ParallelTasks(nproc) chunks = make_chunks(docnames, nproc) self.app.phase = BuildPhase.RESOLVING for chunk in status_iterator(chunks, __('writing output... '), "darkgreen", len(chunks), self.app.verbosity): arg = [] for i, docname in enumerate(chunk): doctree = self.env.get_and_resolve_doctree(docname, self) self.write_doc_serialized(docname, doctree) arg.append((docname, doctree)) tasks.add_task(write_process, arg) # make sure all threads have finished logger.info(bold(__('waiting for workers...'))) tasks.join()
def _read_parallel(self, docnames, nproc): # type: (List[unicode], int) -> None # clear all outdated docs at once for docname in docnames: self.app.emit('env-purge-doc', self.env, docname) self.env.clear_doc(docname) def read_process(docs): # type: (List[unicode]) -> bytes self.env.app = self.app for docname in docs: self.read_doc(docname) # allow pickling self to send it back return pickle.dumps(self.env, pickle.HIGHEST_PROTOCOL) def merge(docs, otherenv): # type: (List[unicode], bytes) -> None env = pickle.loads(otherenv) self.env.merge_info_from(docs, env, self.app) tasks = ParallelTasks(nproc) chunks = make_chunks(docnames, nproc) for chunk in status_iterator(chunks, 'reading sources... ', "purple", len(chunks), self.app.verbosity): tasks.add_task(read_process, chunk, merge) # make sure all threads have finished logger.info(bold('waiting for workers...')) tasks.join()
def write(self, build_docnames, updated_docnames, method='update'): # type: (Iterable[unicode], Sequence[unicode], unicode) -> None if build_docnames is None or build_docnames == ['__all__']: # build_all build_docnames = self.env.found_docs if method == 'update': # build updated ones as well docnames = set(build_docnames) | set(updated_docnames) else: docnames = set(build_docnames) logger.debug(__('docnames to write: %s'), ', '.join(sorted(docnames))) # add all toctree-containing files that may have changed for docname in list(docnames): for tocdocname in self.env.files_to_rebuild.get(docname, set()): if tocdocname in self.env.found_docs: docnames.add(tocdocname) docnames.add(self.config.master_doc) logger.info(bold(__('preparing documents... ')), nonl=True) self.prepare_writing(docnames) logger.info(__('done')) if self.parallel_ok: # number of subprocesses is parallel-1 because the main process # is busy loading doctrees and doing write_doc_serialized() self._write_parallel(sorted(docnames), nproc=self.app.parallel - 1) else: self._write_serial(sorted(docnames))
def write(self, build_docnames, updated_docnames, method="update"): if build_docnames is None or build_docnames == ["__all__"]: # build_all build_docnames = self.env.found_docs if method == "update": # build updated ones as well docnames = set(build_docnames) | set(updated_docnames) else: docnames = set(build_docnames) # add all toctree-containing files that may have changed for docname in list(docnames): for tocdocname in self.env.files_to_rebuild.get(docname, []): if tocdocname in self.env.found_docs: docnames.add(tocdocname) docnames.add(self.config.master_doc) self.info(bold("preparing documents... "), nonl=True) self.prepare_writing(docnames) self.info("done") warnings = [] self.env.set_warnfunc(lambda *args: warnings.append(args)) # check for prerequisites to parallel build # (parallel only works on POSIX, because the forking impl of # multiprocessing is required) if not (multiprocessing and self.app.parallel > 1 and self.allow_parallel and os.name == "posix"): self._write_serial(sorted(docnames), warnings) else: # number of subprocesses is parallel-1 because the main process # is busy loading doctrees and doing write_doc_serialized() self._write_parallel(sorted(docnames), warnings, nproc=self.app.parallel - 1) self.env.set_warnfunc(self.warn)
def _init_env(self, freshenv): if freshenv: self.env = BuildEnvironment(self.srcdir, self.doctreedir, self.config) self.env.find_files(self.config) for domain in self.domains.keys(): self.env.domains[domain] = self.domains[domain](self.env) else: try: self.info(bold('loading pickled environment... '), nonl=True) self.env = BuildEnvironment.frompickle(self.config, path.join(self.doctreedir, ENV_PICKLE_FILENAME)) self.env.domains = {} for domain in self.domains.keys(): # this can raise if the data version doesn't fit self.env.domains[domain] = self.domains[domain](self.env) self.info('done') except Exception as err: if type(err) is IOError and err.errno == ENOENT: self.info('not yet created') else: self.info('failed: %s' % err) return self._init_env(freshenv=True) self.env.set_warnfunc(self.warn)
def write(self, *ignored): # build_all docnames = set([doc for doc in self.env.found_docs if doc.startswith("stdlib")]) self.info(bold('preparing documents... '), nonl=True) self.prepare_writing(docnames) self.info('done') # write target files warnings = [] self.env.set_warnfunc(lambda *args: warnings.append(args)) outfilename = path.join(self.outdir, self.name + self.out_suffix) ensuredir(path.dirname(outfilename)) try: f = codecs.open(outfilename, 'w', 'utf-8') try: f.write('# automatically generated from files in doc/stdlib/ -- do not edit here\n\n' + '{\n\n') for docname in self.status_iterator( sorted(docnames), 'processing... ', darkgreen, len(docnames)): doctree = self.env.get_and_resolve_doctree(docname, self) self.writer.write(doctree, f) f.write("\n") f.write('\n}\n') finally: f.close() except (IOError, OSError) as err: self.warn("error writing file %s: %s" % (outfilename, err)) for warning in warnings: self.warn(*warning) self.env.set_warnfunc(self.warn)
def copy_static_readthedocs_files(self): log.info(bold('copying readthedocs static files... '), nonl=True) for filename in self.static_readthedocs_files: path_dest = os.path.join(self.outdir, '_static') path_src = os.path.join( os.path.abspath(os.path.dirname(__file__)), '_static', filename ) ctx = self.get_static_readthedocs_context() if sphinx.version_info < (1, 5): copy_static_entry( path_src, path_dest, self, context=ctx, ) else: copy_asset( path_src, path_dest, context=ctx, renderer=self.templates ) log.info('done')
def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir: str = None) -> None: """Generate project based on values in *d*.""" template = QuickstartRenderer(templatedir=templatedir) if 'mastertoctree' not in d: d['mastertoctree'] = '' if 'mastertocmaxdepth' not in d: d['mastertocmaxdepth'] = 2 d['root_doc'] = d['master'] d['now'] = time.asctime() d['project_underline'] = column_width(d['project']) * '=' d.setdefault('extensions', []) d['copyright'] = time.strftime('%Y') + ', ' + d['author'] d["path"] = os.path.abspath(d['path']) ensuredir(d['path']) srcdir = path.join(d['path'], 'source') if d['sep'] else d['path'] ensuredir(srcdir) if d['sep']: builddir = path.join(d['path'], 'build') d['exclude_patterns'] = '' else: builddir = path.join(srcdir, d['dot'] + 'build') exclude_patterns = map(repr, [ d['dot'] + 'build', 'Thumbs.db', '.DS_Store', ]) d['exclude_patterns'] = ', '.join(exclude_patterns) ensuredir(builddir) ensuredir(path.join(srcdir, d['dot'] + 'templates')) ensuredir(path.join(srcdir, d['dot'] + 'static')) def write_file(fpath: str, content: str, newline: str = None) -> None: if overwrite or not path.isfile(fpath): if 'quiet' not in d: print(__('Creating file %s.') % fpath) with open(fpath, 'wt', encoding='utf-8', newline=newline) as f: f.write(content) else: if 'quiet' not in d: print(__('File %s already exists, skipping.') % fpath) conf_path = os.path.join(templatedir, 'conf.py_t') if templatedir else None if not conf_path or not path.isfile(conf_path): conf_path = os.path.join(package_dir, 'templates', 'quickstart', 'conf.py_t') with open(conf_path) as f: conf_text = f.read() write_file(path.join(srcdir, 'conf.py'), template.render_string(conf_text, d)) masterfile = path.join(srcdir, d['master'] + d['suffix']) if template._has_custom_template('quickstart/master_doc.rst_t'): msg = ( 'A custom template `master_doc.rst_t` found. It has been renamed to ' '`root_doc.rst_t`. Please rename it on your project too.') print(colorize('red', msg)) # RemovedInSphinx60Warning write_file(masterfile, template.render('quickstart/master_doc.rst_t', d)) else: write_file(masterfile, template.render('quickstart/root_doc.rst_t', d)) if d.get('make_mode') is True: makefile_template = 'quickstart/Makefile.new_t' batchfile_template = 'quickstart/make.bat.new_t' else: makefile_template = 'quickstart/Makefile_t' batchfile_template = 'quickstart/make.bat_t' if d['makefile'] is True: d['rsrcdir'] = 'source' if d['sep'] else '.' d['rbuilddir'] = 'build' if d['sep'] else d['dot'] + 'build' # use binary mode, to avoid writing \r\n on Windows write_file(path.join(d['path'], 'Makefile'), template.render(makefile_template, d), '\n') if d['batchfile'] is True: d['rsrcdir'] = 'source' if d['sep'] else '.' d['rbuilddir'] = 'build' if d['sep'] else d['dot'] + 'build' write_file(path.join(d['path'], 'make.bat'), template.render(batchfile_template, d), '\r\n') if silent: return print() print( bold(__('Finished: An initial directory structure has been created.'))) print() print(__( 'You should now populate your master file %s and create other documentation\n' 'source files. ') % masterfile, end='') if d['makefile'] or d['batchfile']: print( __('Use the Makefile to build the docs, like so:\n' ' make builder')) else: print( __('Use the sphinx-build command to build the docs, like so:\n' ' sphinx-build -b builder %s %s') % (srcdir, builddir)) print( __('where "builder" is one of the supported builders, ' 'e.g. html, latex or linkcheck.')) print()
def set_translator(self, name, translator_class): self.info(bold('A Translator for the %s builder is changed.' % name)) self._translators[name] = translator_class
def __init__(self, srcdir, confdir, outdir, doctreedir, buildername, confoverrides=None, status=sys.stdout, warning=sys.stderr, freshenv=False, warningiserror=False, tags=None, verbosity=0, parallel=0): self.verbosity = verbosity self.next_listener_id = 0 self._extensions = {} self._extension_metadata = {} self._additional_source_parsers = {} self._listeners = {} self._setting_up_extension = ['?'] self.domains = {} self.buildername = buildername self.builderclasses = {} self.builder = None self.env = None self.enumerable_nodes = {} self.srcdir = srcdir self.confdir = confdir self.outdir = outdir self.doctreedir = doctreedir self.parallel = parallel if status is None: self._status = cStringIO() self.quiet = True else: self._status = status self.quiet = False if warning is None: self._warning = cStringIO() else: self._warning = warning self._warncount = 0 self.warningiserror = warningiserror self._events = events.copy() self._translators = {} # keep last few messages for traceback self.messagelog = deque(maxlen=10) # say hello to the world self.info(bold('Running Sphinx v%s' % sphinx.__display_version__)) # status code for command-line application self.statuscode = 0 if not path.isdir(outdir): self.info('making output directory...') os.makedirs(outdir) # read config self.tags = Tags(tags) self.config = Config(confdir, CONFIG_FILENAME, confoverrides or {}, self.tags) self.config.check_unicode(self.warn) # defer checking types until i18n has been initialized # initialize some limited config variables before loading extensions self.config.pre_init_values(self.warn) # check the Sphinx version if requested if self.config.needs_sphinx and self.config.needs_sphinx > sphinx.__display_version__: raise VersionRequirementError( 'This project needs at least Sphinx v%s and therefore cannot ' 'be built with this version.' % self.config.needs_sphinx) # force preload html_translator_class if self.config.html_translator_class: translator_class = self.import_object( self.config.html_translator_class, 'html_translator_class setting') self.set_translator('html', translator_class) # set confdir to srcdir if -C given (!= no confdir); a few pieces # of code expect a confdir to be set if self.confdir is None: self.confdir = self.srcdir # load all built-in extension modules for extension in builtin_extensions: self.setup_extension(extension) # extension loading support for alabaster theme # self.config.html_theme is not set from conf.py at here # for now, sphinx always load a 'alabaster' extension. if 'alabaster' not in self.config.extensions: self.config.extensions.append('alabaster') # load all user-given extension modules for extension in self.config.extensions: self.setup_extension(extension) # the config file itself can be an extension if self.config.setup: self._setting_up_extension = ['conf.py'] # py31 doesn't have 'callable' function for below check if hasattr(self.config.setup, '__call__'): self.config.setup(self) else: raise ConfigError( "'setup' that is specified in the conf.py has not been " + "callable. Please provide a callable `setup` function " + "in order to behave as a sphinx extension conf.py itself.") # now that we know all config values, collect them from conf.py self.config.init_values(self.warn) # check extension versions if requested if self.config.needs_extensions: for extname, needs_ver in self.config.needs_extensions.items(): if extname not in self._extensions: self.warn( 'needs_extensions config value specifies a ' 'version requirement for extension %s, but it is ' 'not loaded' % extname) continue has_ver = self._extension_metadata[extname]['version'] if has_ver == 'unknown version' or needs_ver > has_ver: raise VersionRequirementError( 'This project needs the extension %s at least in ' 'version %s and therefore cannot be built with the ' 'loaded version (%s).' % (extname, needs_ver, has_ver)) # check primary_domain if requested if self.config.primary_domain and self.config.primary_domain not in self.domains: self.warn('primary_domain %r not found, ignored.' % self.config.primary_domain) # set up translation infrastructure self._init_i18n() # check all configuration values for permissible types self.config.check_types(self.warn) # set up source_parsers self._init_source_parsers() # set up the build environment self._init_env(freshenv) # set up the builder self._init_builder(self.buildername) # set up the enumerable nodes self._init_enumerable_nodes()
def generate(d, overwrite=True, silent=False, templatedir=None): # type: (Dict, bool, bool, unicode) -> None """Generate project based on values in *d*.""" template = QuickstartRenderer(templatedir=templatedir) texescape.init() if 'mastertoctree' not in d: d['mastertoctree'] = '' if 'mastertocmaxdepth' not in d: d['mastertocmaxdepth'] = 2 d['PY3'] = True d['project_fn'] = make_filename(d['project']) d['project_url'] = quote(d['project'].encode('idna')) d['project_manpage'] = d['project_fn'].lower() d['now'] = time.asctime() d['project_underline'] = column_width(d['project']) * '=' d.setdefault('extensions', []) d['copyright'] = time.strftime('%Y') + ', ' + d['author'] d['author_texescaped'] = text_type(d['author']).\ translate(texescape.tex_escape_map) d['project_doc'] = d['project'] + ' Documentation' d['project_doc_texescaped'] = text_type(d['project'] + ' Documentation').\ translate(texescape.tex_escape_map) # escape backslashes and single quotes in strings that are put into # a Python string literal for key in ('project', 'project_doc', 'project_doc_texescaped', 'author', 'author_texescaped', 'copyright', 'version', 'release', 'master'): d[key + '_str'] = d[key].replace('\\', '\\\\').replace("'", "\\'") if not path.isdir(d['path']): ensuredir(d['path']) srcdir = d['sep'] and path.join(d['path'], 'source') or d['path'] ensuredir(srcdir) if d['sep']: builddir = path.join(d['path'], 'build') d['exclude_patterns'] = '' else: builddir = path.join(srcdir, d['dot'] + 'build') exclude_patterns = map(repr, [ d['dot'] + 'build', 'Thumbs.db', '.DS_Store', ]) d['exclude_patterns'] = ', '.join(exclude_patterns) ensuredir(builddir) ensuredir(path.join(srcdir, d['dot'] + 'templates')) ensuredir(path.join(srcdir, d['dot'] + 'static')) def write_file(fpath, content, newline=None): # type: (unicode, unicode, unicode) -> None if overwrite or not path.isfile(fpath): if 'quiet' not in d: print(__('Creating file %s.') % fpath) with open(fpath, 'wt', encoding='utf-8', newline=newline) as f: # type: ignore f.write(content) else: if 'quiet' not in d: print(__('File %s already exists, skipping.') % fpath) conf_path = os.path.join(templatedir, 'conf.py_t') if templatedir else None if not conf_path or not path.isfile(conf_path): conf_path = os.path.join(package_dir, 'templates', 'quickstart', 'conf.py_t') with open(conf_path) as f: conf_text = f.read() write_file(path.join(srcdir, 'conf.py'), template.render_string(conf_text, d)) masterfile = path.join(srcdir, d['master'] + d['suffix']) write_file(masterfile, template.render('quickstart/master_doc.rst_t', d)) if d.get('make_mode') is True: makefile_template = 'quickstart/Makefile.new_t' batchfile_template = 'quickstart/make.bat.new_t' else: makefile_template = 'quickstart/Makefile_t' batchfile_template = 'quickstart/make.bat_t' if d['makefile'] is True: d['rsrcdir'] = d['sep'] and 'source' or '.' d['rbuilddir'] = d['sep'] and 'build' or d['dot'] + 'build' # use binary mode, to avoid writing \r\n on Windows write_file(path.join(d['path'], 'Makefile'), template.render(makefile_template, d), u'\n') if d['batchfile'] is True: d['rsrcdir'] = d['sep'] and 'source' or '.' d['rbuilddir'] = d['sep'] and 'build' or d['dot'] + 'build' write_file(path.join(d['path'], 'make.bat'), template.render(batchfile_template, d), u'\r\n') if silent: return print() print( bold(__('Finished: An initial directory structure has been created.'))) print( __(''' You should now populate your master file %s and create other documentation source files. ''') % masterfile + ((d['makefile'] or d['batchfile']) and __('''\ Use the Makefile to build the docs, like so: make builder ''') or __('''\ Use the sphinx-build command to build the docs, like so: sphinx-build -b builder %s %s ''') % (srcdir, builddir)) + __('''\ where "builder" is one of the supported builders, e.g. html, latex or linkcheck. '''))
def ask_user(d): # type: (Dict) -> None """Ask the user for quickstart values missing from *d*. Values are: * path: root path * sep: separate source and build dirs (bool) * dot: replacement for dot in _templates etc. * project: project name * author: author names * version: version of project * release: release of project * language: document language * suffix: source file suffix * master: master document name * extensions: extensions to use (list) * makefile: make Makefile * batchfile: make command file """ print( bold(__('Welcome to the Sphinx %s quickstart utility.')) % __display_version__) print( __(''' Please enter values for the following settings (just press Enter to accept a default value, if one is given in brackets).''')) if 'path' in d: print(bold(__(''' Selected root path: %s''') % d['path'])) else: print(__(''' Enter the root path for documentation.''')) d['path'] = do_prompt(__('Root path for the documentation'), '.', is_path) while path.isfile(path.join(d['path'], 'conf.py')) or \ path.isfile(path.join(d['path'], 'source', 'conf.py')): print() print( bold( __('Error: an existing conf.py has been found in the ' 'selected root path.'))) print( __('sphinx-quickstart will not overwrite existing Sphinx projects.' )) print() d['path'] = do_prompt( __('Please enter a new root path (or just Enter ' 'to exit)'), '', is_path) if not d['path']: sys.exit(1) if 'sep' not in d: print( __(''' You have two options for placing the build directory for Sphinx output. Either, you use a directory "_build" within the root path, or you separate "source" and "build" directories within the root path.''')) d['sep'] = do_prompt(__('Separate source and build directories (y/n)'), 'n', boolean) if 'dot' not in d: print( __(''' Inside the root directory, two more directories will be created; "_templates" for custom HTML templates and "_static" for custom stylesheets and other static files. You can enter another prefix (such as ".") to replace the underscore.''' )) d['dot'] = do_prompt(__('Name prefix for templates and static dir'), '_', ok) if 'project' not in d: print( __(''' The project name will occur in several places in the built documentation.''')) d['project'] = do_prompt(__('Project name')) if 'author' not in d: d['author'] = do_prompt(__('Author name(s)')) if 'version' not in d: print( __(''' Sphinx has the notion of a "version" and a "release" for the software. Each version can have multiple releases. For example, for Python the version is something like 2.5 or 3.0, while the release is something like 2.5.1 or 3.0a1. If you don't need this dual structure, just set both to the same value.''')) d['version'] = do_prompt(__('Project version'), '', allow_empty) if 'release' not in d: d['release'] = do_prompt(__('Project release'), d['version'], allow_empty) if 'language' not in d: print( __(''' If the documents are to be written in a language other than English, you can select a language here by its language code. Sphinx will then translate text that it generates into that language. For a list of supported codes, see http://sphinx-doc.org/config.html#confval-language.''')) d['language'] = do_prompt(__('Project language'), 'en') if d['language'] == 'en': d['language'] = None if 'suffix' not in d: print( __(''' The file name suffix for source files. Commonly, this is either ".txt" or ".rst". Only files with this suffix are considered documents.''')) d['suffix'] = do_prompt(__('Source file suffix'), '.rst', suffix) if 'master' not in d: print( __(''' One document is special in that it is considered the top node of the "contents tree", that is, it is the root of the hierarchical structure of the documents. Normally, this is "index", but if your "index" document is a custom template, you can also set this to another filename.''')) d['master'] = do_prompt( __('Name of your master document (without suffix)'), 'index') while path.isfile(path.join(d['path'], d['master'] + d['suffix'])) or \ path.isfile(path.join(d['path'], 'source', d['master'] + d['suffix'])): print() print( bold( __('Error: the master file %s has already been found in the ' 'selected root path.') % (d['master'] + d['suffix']))) print(__('sphinx-quickstart will not overwrite the existing file.')) print() d['master'] = do_prompt( __('Please enter a new file name, or rename the ' 'existing file and press Enter'), d['master']) if 'extensions' not in d: print( __('Indicate which of the following Sphinx extensions should be ' 'enabled:')) d['extensions'] = [] for name, description in EXTENSIONS.items(): if do_prompt('%s: %s (y/n)' % (name, description), 'n', boolean): d['extensions'].append('sphinx.ext.%s' % name) # Handle conflicting options if set(['sphinx.ext.imgmath', 'sphinx.ext.mathjax']).issubset(d['extensions']): print( __('Note: imgmath and mathjax cannot be enabled at the same ' 'time. imgmath has been deselected.')) d['extensions'].remove('sphinx.ext.imgmath') if 'makefile' not in d: print( __(''' A Makefile and a Windows command file can be generated for you so that you only have to run e.g. `make html' instead of invoking sphinx-build directly.''')) d['makefile'] = do_prompt(__('Create Makefile? (y/n)'), 'y', boolean) if 'batchfile' not in d: d['batchfile'] = do_prompt(__('Create Windows command file? (y/n)'), 'y', boolean) print()
def main(argv=sys.argv[1:]): # type: (List[str]) -> int locale.setlocale(locale.LC_ALL, '') sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx') if not color_terminal(): nocolor() # parse options parser = get_parser() try: args = parser.parse_args(argv) except SystemExit as err: return err.code d = vars(args) # delete None or False value d = dict((k, v) for k, v in d.items() if v is not None) try: if 'quiet' in d: if not set(['project', 'author']).issubset(d): print( __('''"quiet" is specified, but any of "project" or \ "author" is not specified.''')) return 1 if set(['quiet', 'project', 'author']).issubset(d): # quiet mode with all required params satisfied, use default d.setdefault('version', '') d.setdefault('release', d['version']) d2 = DEFAULTS.copy() d2.update(d) d = d2 if not valid_dir(d): print() print( bold( __('Error: specified path is not a directory, or sphinx' ' files already exist.'))) print( __('sphinx-quickstart only generate into a empty directory.' ' Please specify a new root path.')) return 1 else: ask_user(d) except (KeyboardInterrupt, EOFError): print() print('[Interrupted.]') return 130 # 128 + SIGINT # handle use of CSV-style extension values d.setdefault('extensions', []) for ext in d['extensions'][:]: if ',' in ext: d['extensions'].remove(ext) d['extensions'].extend(ext.split(',')) for variable in d.get('variables', []): try: name, value = variable.split('=') d[name] = value except ValueError: print(__('Invalid template variable: %s') % variable) generate(d, overwrite=False, templatedir=args.templatedir) return 0
def write(self, *ignored): version = self.config.version libchanges = {} apichanges = [] otherchanges = {} if version not in self.env.versionchanges: self.info(bold('no changes in version %s.' % version)) return self.info(bold('writing summary file...')) for type, docname, lineno, module, descname, content in \ self.env.versionchanges[version]: if isinstance(descname, tuple): descname = descname[0] ttext = self.typemap[type] context = content.replace('\n', ' ') if descname and docname.startswith('c-api'): if not descname: continue if context: entry = '<b>%s</b>: <i>%s:</i> %s' % (descname, ttext, context) else: entry = '<b>%s</b>: <i>%s</i>.' % (descname, ttext) apichanges.append((entry, docname, lineno)) elif descname or module: if not module: module = _('Builtins') if not descname: descname = _('Module level') if context: entry = '<b>%s</b>: <i>%s:</i> %s' % (descname, ttext, context) else: entry = '<b>%s</b>: <i>%s</i>.' % (descname, ttext) libchanges.setdefault(module, []).append( (entry, docname, lineno)) else: if not context: continue entry = '<i>%s:</i> %s' % (ttext.capitalize(), context) title = self.env.titles[docname].astext() otherchanges.setdefault((docname, title), []).append( (entry, docname, lineno)) ctx = { 'project': self.config.project, 'version': version, 'docstitle': self.config.html_title, 'shorttitle': self.config.html_short_title, 'libchanges': sorted(libchanges.iteritems()), 'apichanges': sorted(apichanges), 'otherchanges': sorted(otherchanges.iteritems()), 'show_sphinx': self.config.html_show_sphinx, } f = codecs.open(path.join(self.outdir, 'index.html'), 'w', 'utf8') try: f.write(self.templates.render('changes/frameset.html', ctx)) finally: f.close() f = codecs.open(path.join(self.outdir, 'changes.html'), 'w', 'utf8') try: f.write(self.templates.render('changes/versionchanges.html', ctx)) finally: f.close() hltext = [ '.. versionadded:: %s' % version, '.. versionchanged:: %s' % version, '.. deprecated:: %s' % version ] def hl(no, line): line = '<a name="L%s"> </a>' % no + escape(line) for x in hltext: if x in line: line = '<span class="hl">%s</span>' % line break return line self.info(bold('copying source files...')) for docname in self.env.all_docs: f = codecs.open(self.env.doc2path(docname), 'r', 'latin1') lines = f.readlines() targetfn = path.join(self.outdir, 'rst', os_path(docname)) + '.html' ensuredir(path.dirname(targetfn)) f = codecs.open(targetfn, 'w', 'latin1') try: text = ''.join( hl(i + 1, line) for (i, line) in enumerate(lines)) ctx = { 'filename': self.env.doc2path(docname, None), 'text': text } f.write(self.templates.render('changes/rstsource.html', ctx)) finally: f.close() themectx = dict( ('theme_' + key, val) for (key, val) in self.theme.get_options({}).iteritems()) copy_static_entry( path.join(package_dir, 'themes', 'default', 'static', 'default.css_t'), path.join(self.outdir, 'default.css_t'), self, themectx) copy_static_entry( path.join(package_dir, 'themes', 'basic', 'static', 'basic.css'), path.join(self.outdir, 'basic.css'), self)
def __enter__(self) -> None: logger.info(bold(self.message + '... '), nonl=True)
def build_helpbook(self): contents_dir = path.join(self.bundle_path, 'Contents') resources_dir = path.join(contents_dir, 'Resources') language_dir = path.join(resources_dir, self.config.applehelp_locale + '.lproj') for d in [contents_dir, resources_dir, language_dir]: ensuredir(d) # Construct the Info.plist file toc = self.config.master_doc + self.out_suffix info_plist = { 'CFBundleDevelopmentRegion': self.config.applehelp_dev_region, 'CFBundleIdentifier': self.config.applehelp_bundle_id, 'CFBundleInfoDictionaryVersion': '6.0', 'CFBundlePackageType': 'BNDL', 'CFBundleShortVersionString': self.config.release, 'CFBundleSignature': 'hbwr', 'CFBundleVersion': self.config.applehelp_bundle_version, 'HPDBookAccessPath': '_access.html', 'HPDBookIndexPath': 'search.helpindex', 'HPDBookTitle': self.config.applehelp_title, 'HPDBookType': '3', 'HPDBookUsesExternalViewer': False, } if self.config.applehelp_icon is not None: info_plist['HPDBookIconPath'] \ = path.basename(self.config.applehelp_icon) if self.config.applehelp_kb_url is not None: info_plist['HPDBookKBProduct'] = self.config.applehelp_kb_product info_plist['HPDBookKBURL'] = self.config.applehelp_kb_url if self.config.applehelp_remote_url is not None: info_plist['HPDBookRemoteURL'] = self.config.applehelp_remote_url self.info(bold('writing Info.plist... '), nonl=True) with open(path.join(contents_dir, 'Info.plist'), 'wb') as f: write_plist(info_plist, f) self.info('done') # Copy the icon, if one is supplied if self.config.applehelp_icon: self.info(bold('copying icon... '), nonl=True) try: copyfile( path.join(self.srcdir, self.config.applehelp_icon), path.join(resources_dir, info_plist['HPDBookIconPath'])) self.info('done') except Exception as err: self.warn( 'cannot copy icon file %r: %s' % (path.join(self.srcdir, self.config.applehelp_icon), err)) del info_plist['HPDBookIconPath'] # Build the access page self.info(bold('building access page...'), nonl=True) f = codecs.open(path.join(language_dir, '_access.html'), 'w') try: f.write( access_page_template % { 'toc': htmlescape(toc, quote=True), 'title': htmlescape(self.config.applehelp_title) }) finally: f.close() self.info('done') # Generate the help index self.info(bold('generating help index... '), nonl=True) args = [ self.config.applehelp_indexer_path, '-Cf', path.join(language_dir, 'search.helpindex'), language_dir ] if self.config.applehelp_index_anchors is not None: args.append('-a') if self.config.applehelp_min_term_length is not None: args += ['-m', '%s' % self.config.applehelp_min_term_length] if self.config.applehelp_stopwords is not None: args += ['-s', self.config.applehelp_stopwords] if self.config.applehelp_locale is not None: args += ['-l', self.config.applehelp_locale] if self.config.applehelp_disable_external_tools: self.info('skipping') self.warn('you will need to index this help book with:\n %s' % (' '.join([pipes.quote(arg) for arg in args]))) else: p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output = p.communicate()[0] if p.returncode != 0: raise AppleHelpIndexerFailed(output) else: self.info('done') # If we've been asked to, sign the bundle if self.config.applehelp_codesign_identity: self.info(bold('signing help book... '), nonl=True) args = [ self.config.applehelp_codesign_path, '-s', self.config.applehelp_codesign_identity, '-f' ] args += self.config.applehelp_codesign_flags args.append(self.bundle_path) if self.config.applehelp_disable_external_tools: self.info('skipping') self.warn('you will need to sign this help book with:\n %s' % (' '.join([pipes.quote(arg) for arg in args]))) else: p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output = p.communicate()[0] if p.returncode != 0: raise AppleHelpCodeSigningFailed(output) else: self.info('done')
def main(argv=sys.argv): if not color_terminal(): nocolor() parser = optparse.OptionParser(USAGE, epilog=EPILOG, version='Sphinx v%s' % __display_version__, formatter=MyFormatter()) parser.add_option('-q', '--quiet', action='store_true', dest='quiet', default=False, help='quiet mode') group = parser.add_option_group('Structure options') group.add_option('--sep', action='store_true', dest='sep', help='if specified, separate source and build dirs') group.add_option('--dot', metavar='DOT', dest='dot', help='replacement for dot in _templates etc.') group = parser.add_option_group('Project basic options') group.add_option('-p', '--project', metavar='PROJECT', dest='project', help='project name') group.add_option('-a', '--author', metavar='AUTHOR', dest='author', help='author names') group.add_option('-v', metavar='VERSION', dest='version', help='version of project') group.add_option('-r', '--release', metavar='RELEASE', dest='release', help='release of project') group.add_option('-l', '--language', metavar='LANGUAGE', dest='language', help='document language') group.add_option('--suffix', metavar='SUFFIX', dest='suffix', help='source file suffix') group.add_option('--master', metavar='MASTER', dest='master', help='master document name') group.add_option('--epub', action='store_true', dest='epub', default=False, help='use epub') group = parser.add_option_group('Extension options') for ext in EXTENSIONS: group.add_option('--ext-' + ext, action='store_true', dest='ext_' + ext, default=False, help='enable %s extension' % ext) group = parser.add_option_group('Makefile and Batchfile creation') group.add_option('--makefile', action='store_true', dest='makefile', default=False, help='create makefile') group.add_option('--no-makefile', action='store_true', dest='no_makefile', default=False, help='not create makefile') group.add_option('--batchfile', action='store_true', dest='batchfile', default=False, help='create batchfile') group.add_option('--no-batchfile', action='store_true', dest='no_batchfile', default=False, help='not create batchfile') group.add_option('-M', '--no-use-make-mode', action='store_false', dest='make_mode', help='not use make-mode for Makefile/make.bat') group.add_option('-m', '--use-make-mode', action='store_true', dest='make_mode', default=True, help='use make-mode for Makefile/make.bat') # parse options try: opts, args = parser.parse_args(argv[1:]) except SystemExit as err: return err.code if len(args) > 0: opts.ensure_value('path', args[0]) d = vars(opts) # delete None or False value d = dict((k, v) for k, v in d.items() if not (v is None or v is False)) try: if 'quiet' in d: if not set(['project', 'author']).issubset(d): print('''"quiet" is specified, but any of "project" or \ "author" is not specified.''') return 1 if set(['quiet', 'project', 'author']).issubset(d): # quiet mode with all required params satisfied, use default d.setdefault('version', '') d.setdefault('release', d['version']) d2 = DEFAULT_VALUE.copy() d2.update(dict(("ext_" + ext, False) for ext in EXTENSIONS)) d2.update(d) d = d2 if 'no_makefile' in d: d['makefile'] = False if 'no_batchfile' in d: d['batchfile'] = False if not valid_dir(d): print() print( bold('Error: specified path is not a directory, or sphinx' ' files already exist.')) print('sphinx-quickstart only generate into a empty directory.' ' Please specify a new root path.') return 1 else: ask_user(d) except (KeyboardInterrupt, EOFError): print() print('[Interrupted.]') return 130 # 128 + SIGINT # decode values in d if value is a Python string literal for key, value in d.items(): if isinstance(value, binary_type): d[key] = term_decode(value) generate(d)
def __init__(self, srcdir: str, confdir: str, outdir: str, doctreedir: str, buildername: str, confoverrides: Dict = None, status: IO = sys.stdout, warning: IO = sys.stderr, freshenv: bool = False, warningiserror: bool = False, tags: List[str] = None, verbosity: int = 0, parallel: int = 0, keep_going: bool = False) -> None: self.phase = BuildPhase.INITIALIZATION self.verbosity = verbosity self.extensions = {} # type: Dict[str, Extension] self.builder = None # type: Builder self.env = None # type: BuildEnvironment self.project = None # type: Project self.registry = SphinxComponentRegistry() self.html_themes = {} # type: Dict[str, str] # validate provided directories self.srcdir = abspath(srcdir) self.outdir = abspath(outdir) self.doctreedir = abspath(doctreedir) self.confdir = confdir if self.confdir: # confdir is optional self.confdir = abspath(self.confdir) if not path.isfile(path.join(self.confdir, 'conf.py')): raise ApplicationError(__("config directory doesn't contain a " "conf.py file (%s)") % confdir) if not path.isdir(self.srcdir): raise ApplicationError(__('Cannot find source directory (%s)') % self.srcdir) if path.exists(self.outdir) and not path.isdir(self.outdir): raise ApplicationError(__('Output directory (%s) is not a directory') % self.outdir) if self.srcdir == self.outdir: raise ApplicationError(__('Source directory and destination ' 'directory cannot be identical')) self.parallel = parallel if status is None: self._status = StringIO() # type: IO self.quiet = True else: self._status = status self.quiet = False if warning is None: self._warning = StringIO() # type: IO else: self._warning = warning self._warncount = 0 self.keep_going = warningiserror and keep_going if self.keep_going: self.warningiserror = False else: self.warningiserror = warningiserror logging.setup(self, self._status, self._warning) self.events = EventManager(self) # keep last few messages for traceback # This will be filled by sphinx.util.logging.LastMessagesWriter self.messagelog = deque(maxlen=10) # type: deque # say hello to the world logger.info(bold(__('Running Sphinx v%s') % sphinx.__display_version__)) # notice for parallel build on macOS and py38+ if sys.version_info > (3, 8) and platform.system() == 'Darwin' and parallel > 1: logger.info(bold(__("For security reason, parallel mode is disabled on macOS and " "python3.8 and above. For more details, please read " "https://github.com/sphinx-doc/sphinx/issues/6803"))) # status code for command-line application self.statuscode = 0 # read config self.tags = Tags(tags) if self.confdir is None: self.config = Config({}, confoverrides or {}) else: self.config = Config.read(self.confdir, confoverrides or {}, self.tags) # initialize some limited config variables before initialize i18n and loading # extensions self.config.pre_init_values() # set up translation infrastructure self._init_i18n() # check the Sphinx version if requested if self.config.needs_sphinx and self.config.needs_sphinx > sphinx.__display_version__: raise VersionRequirementError( __('This project needs at least Sphinx v%s and therefore cannot ' 'be built with this version.') % self.config.needs_sphinx) # set confdir to srcdir if -C given (!= no confdir); a few pieces # of code expect a confdir to be set if self.confdir is None: self.confdir = self.srcdir # load all built-in extension modules for extension in builtin_extensions: self.setup_extension(extension) # load all user-given extension modules for extension in self.config.extensions: self.setup_extension(extension) # preload builder module (before init config values) self.preload_builder(buildername) if not path.isdir(outdir): with progress_message(__('making output directory')): ensuredir(outdir) # the config file itself can be an extension if self.config.setup: prefix = __('while setting up extension %s:') % "conf.py" with prefixed_warnings(prefix): if callable(self.config.setup): self.config.setup(self) else: raise ConfigError( __("'setup' as currently defined in conf.py isn't a Python callable. " "Please modify its definition to make it a callable function. " "This is needed for conf.py to behave as a Sphinx extension.") ) # now that we know all config values, collect them from conf.py self.config.init_values() self.events.emit('config-inited', self.config) # create the project self.project = Project(self.srcdir, self.config.source_suffix) # create the builder self.builder = self.create_builder(buildername) # set up the build environment self._init_env(freshenv) # set up the builder self._init_builder()
def ask_user(d): """Ask the user for quickstart values missing from *d*. Values are: * path: root path * sep: separate source and build dirs (bool) * dot: replacement for dot in _templates etc. * project: project name * author: author names * version: version of project * release: release of project * language: document language * suffix: source file suffix * master: master document name * epub: use epub (bool) * ext_*: extensions to use (bools) * makefile: make Makefile * batchfile: make command file """ print( bold('Welcome to the Sphinx %s quickstart utility.') % __display_version__) print(''' Please enter values for the following settings (just press Enter to accept a default value, if one is given in brackets).''') if 'path' in d: print(bold(''' Selected root path: %s''' % d['path'])) else: print(''' Enter the root path for documentation.''') do_prompt(d, 'path', 'Root path for the documentation', '.', is_path) while path.isfile(path.join(d['path'], 'conf.py')) or \ path.isfile(path.join(d['path'], 'source', 'conf.py')): print() print( bold('Error: an existing conf.py has been found in the ' 'selected root path.')) print('sphinx-quickstart will not overwrite existing Sphinx projects.') print() do_prompt(d, 'path', 'Please enter a new root path (or just Enter ' 'to exit)', '', is_path) if not d['path']: sys.exit(1) if 'sep' not in d: print(''' You have two options for placing the build directory for Sphinx output. Either, you use a directory "_build" within the root path, or you separate "source" and "build" directories within the root path.''') do_prompt(d, 'sep', 'Separate source and build directories (y/n)', 'n', boolean) if 'dot' not in d: print(''' Inside the root directory, two more directories will be created; "_templates" for custom HTML templates and "_static" for custom stylesheets and other static files. You can enter another prefix (such as ".") to replace the underscore.''' ) do_prompt(d, 'dot', 'Name prefix for templates and static dir', '_', ok) if 'project' not in d: print(''' The project name will occur in several places in the built documentation.''') do_prompt(d, 'project', 'Project name') if 'author' not in d: do_prompt(d, 'author', 'Author name(s)') if 'version' not in d: print(''' Sphinx has the notion of a "version" and a "release" for the software. Each version can have multiple releases. For example, for Python the version is something like 2.5 or 3.0, while the release is something like 2.5.1 or 3.0a1. If you don't need this dual structure, just set both to the same value.''') do_prompt(d, 'version', 'Project version', '', allow_empty) if 'release' not in d: do_prompt(d, 'release', 'Project release', d['version'], allow_empty) if 'language' not in d: print(''' If the documents are to be written in a language other than English, you can select a language here by its language code. Sphinx will then translate text that it generates into that language. For a list of supported codes, see http://sphinx-doc.org/config.html#confval-language.''') do_prompt(d, 'language', 'Project language', 'en') if d['language'] == 'en': d['language'] = None if 'suffix' not in d: print(''' The file name suffix for source files. Commonly, this is either ".txt" or ".rst". Only files with this suffix are considered documents.''') do_prompt(d, 'suffix', 'Source file suffix', '.rst', suffix) if 'master' not in d: print(''' One document is special in that it is considered the top node of the "contents tree", that is, it is the root of the hierarchical structure of the documents. Normally, this is "index", but if your "index" document is a custom template, you can also set this to another filename.''') do_prompt(d, 'master', 'Name of your master document (without suffix)', 'index') while path.isfile(path.join(d['path'], d['master']+d['suffix'])) or \ path.isfile(path.join(d['path'], 'source', d['master']+d['suffix'])): print() print( bold('Error: the master file %s has already been found in the ' 'selected root path.' % (d['master'] + d['suffix']))) print('sphinx-quickstart will not overwrite the existing file.') print() do_prompt( d, 'master', 'Please enter a new file name, or rename the ' 'existing file and press Enter', d['master']) if 'epub' not in d: print(''' Sphinx can also add configuration for epub output:''') do_prompt(d, 'epub', 'Do you want to use the epub builder (y/n)', 'n', boolean) if 'ext_autodoc' not in d: print(''' Please indicate if you want to use one of the following Sphinx extensions:''') do_prompt( d, 'ext_autodoc', 'autodoc: automatically insert docstrings ' 'from modules (y/n)', 'n', boolean) if 'ext_doctest' not in d: do_prompt( d, 'ext_doctest', 'doctest: automatically test code snippets ' 'in doctest blocks (y/n)', 'n', boolean) if 'ext_intersphinx' not in d: do_prompt( d, 'ext_intersphinx', 'intersphinx: link between Sphinx ' 'documentation of different projects (y/n)', 'n', boolean) if 'ext_todo' not in d: do_prompt( d, 'ext_todo', 'todo: write "todo" entries ' 'that can be shown or hidden on build (y/n)', 'n', boolean) if 'ext_coverage' not in d: do_prompt(d, 'ext_coverage', 'coverage: checks for documentation ' 'coverage (y/n)', 'n', boolean) if 'ext_imgmath' not in d: do_prompt( d, 'ext_imgmath', 'imgmath: include math, rendered ' 'as PNG or SVG images (y/n)', 'n', boolean) if 'ext_mathjax' not in d: do_prompt( d, 'ext_mathjax', 'mathjax: include math, rendered in the ' 'browser by MathJax (y/n)', 'n', boolean) if d['ext_imgmath'] and d['ext_mathjax']: print('''Note: imgmath and mathjax cannot be enabled at the same time. imgmath has been deselected.''') d['ext_imgmath'] = False if 'ext_ifconfig' not in d: do_prompt( d, 'ext_ifconfig', 'ifconfig: conditional inclusion of ' 'content based on config values (y/n)', 'n', boolean) if 'ext_viewcode' not in d: do_prompt( d, 'ext_viewcode', 'viewcode: include links to the source ' 'code of documented Python objects (y/n)', 'n', boolean) if 'ext_githubpages' not in d: do_prompt( d, 'ext_githubpages', 'githubpages: create .nojekyll file ' 'to publish the document on GitHub pages (y/n)', 'n', boolean) if 'no_makefile' in d: d['makefile'] = False elif 'makefile' not in d: print(''' A Makefile and a Windows command file can be generated for you so that you only have to run e.g. `make html' instead of invoking sphinx-build directly.''') do_prompt(d, 'makefile', 'Create Makefile? (y/n)', 'y', boolean) if 'no_batchfile' in d: d['batchfile'] = False elif 'batchfile' not in d: do_prompt(d, 'batchfile', 'Create Windows command file? (y/n)', 'y', boolean) print()