From f7f0a065f61df8e6320e3fc18f6f3fbe0bdc9344 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Wed, 3 Oct 2018 12:10:50 -0400 Subject: [PATCH] show the modification time of each page individually The old way of setting last_updated for the output showed a single value on all pages, which makes it more difficult to know how up to date a given resolution or policy is. The output was also formatted as binary strings, which rendered poorly with b'' wrapped around the actual timestamp. This change uses the git history of each page to show when it was modified. For pages generated from the project list, show the modification time of the input data file. For pages rendered from templates, without a source file, use the current build time. Always return the value as a unicode string. Change-Id: I82849176775484328b3939b4e0c66aaf7a35f49d Signed-off-by: Doug Hellmann --- doc/source/_exts/page_context.py | 96 ++++++++++++++++++++++++++++++++ doc/source/conf.py | 7 +-- 2 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 doc/source/_exts/page_context.py diff --git a/doc/source/_exts/page_context.py b/doc/source/_exts/page_context.py new file mode 100644 index 000000000..20830fc4f --- /dev/null +++ b/doc/source/_exts/page_context.py @@ -0,0 +1,96 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import datetime +import os.path +import subprocess + +from sphinx.util import logging + +LOG = logging.getLogger('page_context') + +_default_last_updated = datetime.datetime.now() + + +def _get_last_updated_file(src_file): + if not os.path.exists(src_file): + return None + try: + last_updated_t = subprocess.check_output( + [ + 'git', 'log', '-n1', '--format=%ad', + '--date=format:%Y-%m-%d %H:%M:%S', + '--', src_file, + ] + ).decode('utf-8').strip() + except subprocess.CalledProcessError as err: + LOG.info('[governance] Could not get modification time of %s: %s', + src_file, err) + else: + if last_updated_t: + try: + return datetime.datetime.strptime(last_updated_t, + '%Y-%m-%d %H:%M:%S') + except ValueError as err: + LOG.info('[governance] Could not parse modification time of ' + '%s: %r', + src_file, last_updated_t) + return None + + +def _get_last_updated(app, pagename): + last_updated = None + full_src_file = app.builder.env.doc2path(pagename) + + candidates = [] + + # Strip the prefix from the filename so the git command recognizes + # the file as part of the current repository. + src_file = full_src_file[len(app.builder.env.srcdir):].lstrip('/') + candidates.append(src_file) + + if not os.path.exists(src_file): + # Some of the files are in doc/source and some are not. Some + # of the ones that are not are symlinked. If we can't find the + # file after stripping the full prefix, try looking for it in + # doc/source explicitly. + candidates.append(os.path.join('doc/source', src_file)) + + if pagename.startswith('reference/projects/'): + # If the file is in the reference/projects directory, it may + # be an auto-generated project page. In that case, use the + # YAML file as the source of the last_updated timestamp. + candidates.append('reference/projects.yaml') + + for filename in candidates: + last_updated = _get_last_updated_file(filename) + if last_updated: + LOG.info('[governance] Last updated for %s is %s', + pagename, last_updated) + return last_updated + LOG.info('[governance] could not determine last_updated for %r', + pagename) + return _default_last_updated + + +def html_page_context(app, pagename, templatename, context, doctree): + # Use the last modified date from git instead of applying a single + # value to the entire site. + context['last_updated'] = _get_last_updated(app, pagename) + + +def setup(app): + LOG.info('[governance] connecting html-page-context event handler') + app.connect('html-page-context', html_page_context) + return { + 'parallel_read_safe': True, + } diff --git a/doc/source/conf.py b/doc/source/conf.py index 6e3af532b..63b01eb28 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -13,6 +13,7 @@ # flake8: noqa +import datetime import subprocess import sys import os @@ -39,6 +40,7 @@ extensions = [ 'teams', 'tags', 'badges', + 'page_context', ] todo_include_todos = True @@ -141,10 +143,7 @@ html_extra_path = ['_extra'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local", - "-n1"] -html_last_updated_fmt = str(subprocess.Popen( - git_cmd, stdout=subprocess.PIPE).communicate()[0]) +html_last_updated_fmt = '%Y-%m-%d' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities.