Source code for pyquickhelper.sphinxext.sphinx_blog_extension

# -*- coding: utf-8 -*-
"""
Defines blogpost directives.
See `Tutorial: Writing a simple extension <http://sphinx-doc.org/extdev/tutorial.html>`_,
`Creating reStructuredText Directives <http://docutils.readthedocs.org/en/sphinx-docs/howto/rst-directives.html>`_


:githublink:`%|py|8`
"""
import os
import sphinx
from docutils import nodes
from docutils.parsers.rst import Directive
from sphinx.locale import _ as _locale
from docutils.parsers.rst import directives
from docutils.statemachine import StringList
from sphinx import addnodes
from sphinx.util.nodes import set_source_info, process_index_entry
from sphinx.util.nodes import nested_parse_with_titles
from .blog_post import BlogPost
from ..texthelper.texts_language import TITLES


[docs]class blogpost_node(nodes.Element): """ Defines *blogpost* node. :githublink:`%|py|26` """ pass
[docs]class blogpostagg_node(nodes.Element): """ Defines *blogpostagg* node. :githublink:`%|py|34` """ pass
[docs]class BlogPostDirective(Directive): """ Extracts information about a blog post described by a directive ``.. blogpost::`` and modifies the documentation if *env* is not null. The directive handles the following options: * *date*: date of the blog (mandatory) * *title*: title (mandatory) * *keywords*: keywords, comma separated (mandatory) * *categories*: categories, comma separated (mandatory) * *author*: author (optional) * *blog_background*: can change the blog background (boolean, default is True) * *lid* or *label*: an id to refer to (optional) :githublink:`%|py|52` """ required_arguments = 0 optional_arguments = 0 final_argument_whitespace = True option_spec = {'date': directives.unchanged, 'title': directives.unchanged, 'keywords': directives.unchanged, 'categories': directives.unchanged, 'author': directives.unchanged, 'blog_background': directives.unchanged, 'lid': directives.unchanged, 'label': directives.unchanged, } has_content = True add_index = True add_share = True blogpost_class = blogpost_node default_config_bg = "blog_background_page"
[docs] def suffix_label(self): """ returns a suffix to add to a label, it should not be empty for aggregated pages :return: str :githublink:`%|py|77` """ return ""
[docs] def run(self): """ extracts the information in a dictionary and displays it if the environment is not null :return: a list of nodes :githublink:`%|py|86` """ # settings sett = self.state.document.settings language_code = sett.language_code if hasattr(sett, "out_blogpostlist"): sett.out_blogpostlist.append(self) # env if hasattr(self.state.document.settings, "env"): env = self.state.document.settings.env else: env = None if env is None: docname = "___unknown_docname___" config = None blog_background = False sharepost = None else: # otherwise, it means sphinx is running docname = env.docname # settings and configuration config = env.config try: blog_background = getattr( config, self.__class__.default_config_bg) except AttributeError as e: raise AttributeError("Unable to find '{1}' in \n{0}".format( "\n".join(sorted(config.values)), self.__class__.default_config_bg)) from e sharepost = config.sharepost if self.__class__.add_share else None # post p = { 'docname': docname, 'lineno': self.lineno, 'date': self.options["date"], 'title': self.options["title"], 'keywords': [a.strip() for a in self.options["keywords"].split(",")], 'categories': [a.strip() for a in self.options["categories"].split(",")], 'blog_background': self.options.get("blog_background", str(blog_background)).strip() in ("True", "true", "1"), 'lid': self.options.get("lid", self.options.get("label", None)), } tag = BlogPost.build_tag(p["date"], p["title"]) if p[ 'lid'] is None else p['lid'] targetnode = nodes.target(p['title'], '', ids=[tag]) p["target"] = targetnode idbp = tag + "-container" if env is not None: if not hasattr(env, 'blogpost_all'): env.blogpost_all = [] env.blogpost_all.append(p) # build node node = self.__class__.blogpost_class(ids=[idbp], year=p["date"][:4], rawfile=self.options.get( "rawfile", None), linktitle=p["title"], lg=language_code, blog_background=p["blog_background"]) return self.fill_node(node, env, tag, p, language_code, targetnode, sharepost)
[docs] def fill_node(self, node, env, tag, p, language_code, targetnode, sharepost): """ Fills the content of the node. :githublink:`%|py|152` """ # add a label suffix_label = self.suffix_label() if not p['lid'] else "" tag = "{0}{1}".format(tag, suffix_label) tnl = [".. _{0}:".format(tag), ""] title = "{0} {1}".format(p["date"], p["title"]) tnl.append(title) tnl.append("=" * len(title)) tnl.append("") if sharepost is not None: tnl.append("") tnl.append(":sharenet:`{0}`".format(sharepost)) tnl.append('') tnl.append('') content = StringList(tnl) content = content + self.content try: nested_parse_with_titles(self.state, content, node) except Exception as e: from sphinx.util import logging logger = logging.getLogger("blogpost") logger.warning( "[blogpost] unable to parse '{0}' - {1}".format(title, e)) raise e # final p['blogpost'] = node self.exe_class = p.copy() p["content"] = content node['classes'] += ["blogpost"] # for the instruction tocdelay. node['toctitle'] = title node['tocid'] = tag node['tocdoc'] = env.docname # end. ns = [node] return ns
[docs]class BlogPostDirectiveAgg(BlogPostDirective): """ same but for the same post in a aggregated pages :githublink:`%|py|197` """ add_index = False add_share = False blogpost_class = blogpostagg_node default_config_bg = "blog_background" option_spec = {'date': directives.unchanged, 'title': directives.unchanged, 'keywords': directives.unchanged, 'categories': directives.unchanged, 'author': directives.unchanged, 'rawfile': directives.unchanged, 'blog_background': directives.unchanged, }
[docs] def suffix_label(self): """ returns a suffix to add to a label, it should not be empty for aggregated pages :return: str :githublink:`%|py|217` """ if hasattr(self.state.document.settings, "env"): env = self.state.document.settings.env docname = os.path.split(env.docname)[-1] docname = os.path.splitext(docname)[0] else: env = None docname = "" return "-agg" + docname
[docs] def fill_node(self, node, env, tag, p, language_code, targetnode, sharepost): """ Fill the node of an aggregated page. :githublink:`%|py|230` """ # add a label suffix_label = self.suffix_label() container = nodes.container() tnl = [".. _{0}{1}:".format(tag, suffix_label), ""] content = StringList(tnl) self.state.nested_parse(content, self.content_offset, container) node += container # id section if env is not None: mid = int(env.new_serialno('indexblog-u-%s' % p["date"][:4])) + 1 else: mid = -1 # add title sids = "y{0}-{1}".format(p["date"][:4], mid) section = nodes.section(ids=[sids]) section['year'] = p["date"][:4] section['blogmid'] = mid node += section textnodes, messages = self.state.inline_text(p["title"], self.lineno) section += nodes.title(p["title"], '', *textnodes) section += messages # add date and share buttons tnl = [":bigger:`::5:{0}`".format(p["date"])] if sharepost is not None: tnl.append(":sharenet:`{0}`".format(sharepost)) tnl.append('') content = StringList(tnl) content = content + self.content # parse the content into sphinx directive, # it adds it to section container = nodes.container() # nested_parse_with_titles(self.state, content, paragraph) self.state.nested_parse(content, self.content_offset, container) section += container # final p['blogpost'] = node self.exe_class = p.copy() p["content"] = content node['classes'] += ["blogpost"] # target # self.state.add_target(p['title'], '', targetnode, lineno) # index (see site-packages/sphinx/directives/code.py, class Index) if self.__class__.add_index: # it adds an index # self.state.document.note_explicit_target(targetnode) indexnode = addnodes.index() indexnode['entries'] = ne = [] indexnode['inline'] = False set_source_info(self, indexnode) for entry in set(p["keywords"] + p["categories"] + [p["date"]]): ne.extend(process_index_entry(entry, tag)) # targetid)) ns = [indexnode, targetnode, node] else: ns = [targetnode, node] return ns
[docs]def visit_blogpost_node(self, node): """ what to do when visiting a node blogpost the function should have different behaviour, depending on the format, or the setup should specify a different function for each. :githublink:`%|py|302` """ if node["blog_background"]: # the node will be in a box self.visit_admonition(node)
[docs]def depart_blogpost_node(self, node): """ what to do when leaving a node blogpost the function should have different behaviour, depending on the format, or the setup should specify a different function for each. :githublink:`%|py|314` """ if node["blog_background"]: # the node will be in a box self.depart_admonition(node)
[docs]def visit_blogpostagg_node(self, node): """ what to do when visiting a node blogpost the function should have different behaviour, depending on the format, or the setup should specify a different function for each. :githublink:`%|py|326` """ pass
[docs]def depart_blogpostagg_node(self, node): """ what to do when leaving a node blogpost, the function should have different behaviour, depending on the format, or the setup should specify a different function for each. :githublink:`%|py|336` """ pass
[docs]def depart_blogpostagg_node_html(self, node): """ what to do when leaving a node blogpost, the function should have different behaviour, depending on the format, or the setup should specify a different function for each. :githublink:`%|py|346` """ if node.hasattr("year"): rawfile = node["rawfile"] if rawfile is not None: # there is probably better to do # module name is something list doctuils.../[xx].py lg = node["lg"] name = os.path.splitext(os.path.split(rawfile)[-1])[0] name += ".html" year = node["year"] linktitle = node["linktitle"] link = """<p><a class="reference internal" href="{0}/{2}" title="{1}">{3}</a></p>""" \ .format(year, linktitle, name, TITLES[lg]["more"]) self.body.append(link) else: self.body.append( "%blogpostagg: link to source only available for HTML: '{}'\n".format(type(self)))
###################### # unused, kept as example ######################
[docs]class blogpostlist_node(nodes.General, nodes.Element): """ defines *blogpostlist* node, unused, kept as example :githublink:`%|py|374` """ pass
[docs]class BlogPostListDirective(Directive): """ unused, kept as example :githublink:`%|py|382` """ def run(self): return [BlogPostListDirective.blogpostlist('')]
[docs]def purge_blogpost(app, env, docname): """ unused, kept as example :githublink:`%|py|391` """ if not hasattr(env, 'blogpost_all'): return env.blogpost_all = [post for post in env.blogpost_all if post['docname'] != docname]
[docs]def process_blogpost_nodes(app, doctree, fromdocname): """ unused, kept as example :githublink:`%|py|401` """ if not app.config.blogpost_include_s: for node in doctree.traverse(blogpost_node): node.parent.remove(node) # Replace all blogpostlist nodes with a list of the collected blogposts. # Augment each blogpost with a backlink to the original location. env = app.builder.env if hasattr(env, "settings") and hasattr(env.settings, "language_code"): lang = env.settings.language_code else: lang = "en" blogmes = TITLES[lang]["blog_entry"] for node in doctree.traverse(blogpostlist_node): if not app.config.blogpost_include_s: node.replace_self([]) continue content = [] for post_info in env.blogpost_all: para = nodes.paragraph() filename = env.doc2path(post_info['docname'], base=None) description = (_locale(blogmes) % (filename, post_info['lineno'])) para += nodes.Text(description, description) # Create a reference newnode = nodes.reference('', '') innernode = nodes.emphasis(_locale('here'), _locale('here')) newnode['refdocname'] = post_info['docname'] newnode['refuri'] = app.builder.get_relative_uri( fromdocname, post_info['docname']) try: newnode['refuri'] += '#' + post_info['target']['refid'] except Exception as e: raise KeyError("refid in not present in '{0}'".format( post_info['target'])) from e newnode.append(innernode) para += newnode para += nodes.Text('.)', '.)') # Insert into the blogpostlist content.append(post_info['blogpost']) content.append(para) node.replace_self(content)
[docs]def setup(app): """ setup for ``blogpost`` (sphinx) :githublink:`%|py|453` """ # this command enables the parameter blog_background to be part of the # configuration app.add_config_value('sharepost', None, 'env') app.add_config_value('blog_background', True, 'env') app.add_config_value('blog_background_page', False, 'env') app.add_config_value('out_blogpostlist', [], 'env') if hasattr(app, "add_mapping"): app.add_mapping('blogpost', blogpost_node) app.add_mapping('blogpostagg', blogpostagg_node) # app.add_node(blogpostlist) app.add_node(blogpost_node, html=(visit_blogpost_node, depart_blogpost_node), epub=(visit_blogpost_node, depart_blogpost_node), elatex=(visit_blogpost_node, depart_blogpost_node), latex=(visit_blogpost_node, depart_blogpost_node), rst=(visit_blogpost_node, depart_blogpost_node), md=(visit_blogpost_node, depart_blogpost_node), text=(visit_blogpost_node, depart_blogpost_node)) app.add_node(blogpostagg_node, html=(visit_blogpostagg_node, depart_blogpostagg_node_html), epub=(visit_blogpostagg_node, depart_blogpostagg_node_html), elatex=(visit_blogpostagg_node, depart_blogpostagg_node), latex=(visit_blogpostagg_node, depart_blogpostagg_node), rst=(visit_blogpostagg_node, depart_blogpostagg_node), md=(visit_blogpostagg_node, depart_blogpostagg_node), text=(visit_blogpostagg_node, depart_blogpostagg_node)) app.add_directive('blogpost', BlogPostDirective) app.add_directive('blogpostagg', BlogPostDirectiveAgg) #app.add_directive('blogpostlist', BlogPostListDirective) #app.connect('doctree-resolved', process_blogpost_nodes) #app.connect('env-purge-doc', purge_blogpost) return {'version': sphinx.__display_version__, 'parallel_read_safe': True}