Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #3037 from getnikola/fix-3025
Fix #3025 — add METADATA_VALUE_MAPPING and smartjoin
  • Loading branch information
Kwpolska committed Apr 16, 2018
2 parents ee7dece + 861b4d7 commit ce168a7
Show file tree
Hide file tree
Showing 13 changed files with 66 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGES.txt
Expand Up @@ -14,6 +14,10 @@ Features
* Fixing behavior of RSS_PATH to do what the documentation
says it does (Issue #3024)
* Add support for fragments in path handlers (Issue #3032)
* New ``METADATA_VALUE_MAPPING`` setting to allow for flexible global
modification of metadata (Issue #3025)
* New ``smartjoin`` template function/filter that joins lists and
leaves strings as-is (Issue #3025)
* Explain index.html conflicts better (Issue #3022)
* Recognize both TEASER_END and (new) END_TEASER (Issue #3010)
* New MARKDOWN_EXTENSION_CONFIGS setting (Issue #2970)
Expand Down
12 changes: 12 additions & 0 deletions docs/manual.rst
Expand Up @@ -581,6 +581,18 @@ For Hugo, use:
The following source names are supported: ``yaml``, ``toml``, ``rest_docinfo``, ``markdown_metadata``.

Additionally, you can use ``METADATA_VALUE_MAPPING`` to perform any extra conversions on metadata for **all** posts of a given format (``nikola`` metadata is also supported). A few examples:

.. code:: python
METADATA_VALUE_MAPPING = {
"yaml": {"keywords": lambda value: ', '.join(value)}, # yaml: 'keywords' list -> str
"nikola": {
"widgets": lambda value: value.split(', '), # nikola: 'widgets' comma-separated string -> list
"tags": str.lower # nikola: force lowercase 'tags' (input would be string)
}
}
Multilingual posts
~~~~~~~~~~~~~~~~~~

Expand Down
1 change: 1 addition & 0 deletions docs/template-variables.rst
Expand Up @@ -101,6 +101,7 @@ Name Type Descript
``SLUG_TAG_PATH`` bool ``SLUG_TAG_PATH`` setting
``social_buttons_code`` TranslatableSetting<str> ``SOCIAL_BUTTONS_CODE`` setting
``sort_posts`` function ``utils.sort_posts`` function
``smartjoin`` function ``utils.smartjoin`` function
``template_hooks`` dict<str, TemplateHookRegistry> Template hooks registered by plugins
``theme_color`` str ``THEME_COLOR`` setting
``timezone`` tzinfo Timezone object (represents the configured timezone)
Expand Down
13 changes: 13 additions & 0 deletions nikola/conf.py.in
Expand Up @@ -1189,6 +1189,19 @@ MARKDOWN_EXTENSIONS = ['markdown.extensions.fenced_code', 'markdown.extensions.c
# }
# Other examples: https://getnikola.com/handbook.html#mapping-metadata-from-other-formats

# Map metadata between types/values. (Runs after METADATA_MAPPING.)
# Supported formats: nikola, ${_METADATA_MAPPING_FORMATS}
# The value on the right should be a dict of callables.
# METADATA_VALUE_MAPPING = {}
# Examples:
# METADATA_VALUE_MAPPING = {
# "yaml": {"keywords": lambda value: ', '.join(value)}, # yaml: 'keywords' list -> str
# "nikola": {
# "widgets": lambda value: value.split(', '), # nikola: 'widgets' comma-separated string -> list
# "tags": str.lower # nikola: force lowercase 'tags' (input would be string)
# }
# }

# Additional metadata that is added to a post when creating a new_post
# ADDITIONAL_METADATA = {}

Expand Down
2 changes: 1 addition & 1 deletion nikola/data/themes/base-jinja/templates/post.tmpl
Expand Up @@ -8,7 +8,7 @@
{% block extra_head %}
{{ super() }}
{% if post.meta('keywords') %}
<meta name="keywords" content="{{ post.meta('keywords')|e }}">
<meta name="keywords" content="{{ smartjoin(', ', post.meta('keywords'))|e }}">
{% endif %}
<meta name="author" content="{{ post.author()|e }}">
{% if post.prev_post %}
Expand Down
2 changes: 1 addition & 1 deletion nikola/data/themes/base/templates/post.tmpl
Expand Up @@ -8,7 +8,7 @@
<%block name="extra_head">
${parent.extra_head()}
% if post.meta('keywords'):
<meta name="keywords" content="${post.meta('keywords')|h}">
<meta name="keywords" content="${smartjoin(', ', post.meta('keywords'))|h}">
% endif
<meta name="author" content="${post.author()|h}">
%if post.prev_post:
Expand Down
2 changes: 1 addition & 1 deletion nikola/data/themes/bootstrap4-jinja/templates/post.tmpl
Expand Up @@ -9,7 +9,7 @@
{% block extra_head %}
{{ super() }}
{% if post.meta('keywords') %}
<meta name="keywords" content="{{ post.meta('keywords')|e }}">
<meta name="keywords" content="{{ smartjoin(', ', post.meta('keywords'))|e }}">
{% endif %}
{% if post.description() %}
<meta name="description" itemprop="description" content="{{ post.description()|e }}">
Expand Down
2 changes: 1 addition & 1 deletion nikola/data/themes/bootstrap4/templates/post.tmpl
Expand Up @@ -9,7 +9,7 @@
<%block name="extra_head">
${parent.extra_head()}
% if post.meta('keywords'):
<meta name="keywords" content="${post.meta('keywords')|h}">
<meta name="keywords" content="${smartjoin(', ', post.meta('keywords'))|h}">
% endif
%if post.description():
<meta name="description" itemprop="description" content="${post.description()|h}">
Expand Down
1 change: 1 addition & 0 deletions nikola/metadata_extractors.py
Expand Up @@ -142,6 +142,7 @@ class NikolaMetadata(MetadataExtractor):
supports_write = True
split_metadata_re = re.compile('\n\n')
nikola_re = re.compile(r'^\s*\.\. (.*?): (.*)')
map_from = 'nikola' # advertised in values mapping only

def _extract_metadata_from_text(self, source_text: str) -> dict:
"""Extract metadata from text."""
Expand Down
1 change: 1 addition & 0 deletions nikola/nikola.py
Expand Up @@ -1154,6 +1154,7 @@ def _set_global_context_from_config(self):
self._GLOBAL_CONTEXT['posts_section_name'] = self.config.get('POSTS_SECTION_NAME')
self._GLOBAL_CONTEXT['posts_section_title'] = self.config.get('POSTS_SECTION_TITLE')
self._GLOBAL_CONTEXT['sort_posts'] = utils.sort_posts
self._GLOBAL_CONTEXT['smartjoin'] = utils.smartjoin
self._GLOBAL_CONTEXT['meta_generator_tag'] = self.config.get('META_GENERATOR_TAG')

self._GLOBAL_CONTEXT.update(self.config.get('GLOBAL_CONTEXT', {}))
Expand Down
3 changes: 2 additions & 1 deletion nikola/plugins/template/jinja.py
Expand Up @@ -37,7 +37,7 @@
jinja2 = None # NOQA

from nikola.plugin_categories import TemplateSystem
from nikola.utils import makedirs, req_missing, sort_posts
from nikola.utils import makedirs, req_missing, sort_posts, _smartjoin_filter


class JinjaTemplates(TemplateSystem):
Expand Down Expand Up @@ -65,6 +65,7 @@ def set_directories(self, directories, cache_folder):
self.lookup.lstrip_blocks = True
self.lookup.filters['tojson'] = json.dumps
self.lookup.filters['sort_posts'] = sort_posts
self.lookup.filters['smartjoin'] = _smartjoin_filter
self.lookup.globals['enumerate'] = enumerate
self.lookup.globals['isinstance'] = isinstance
self.lookup.globals['tuple'] = tuple
Expand Down
1 change: 1 addition & 0 deletions nikola/post.py
Expand Up @@ -972,6 +972,7 @@ def get_metadata_from_file(source_path, post, config, lang, metadata_extractors_
found_in_priority = True
used_extractor = extractor
# Map metadata from other platforms to names Nikola expects (Issue #2817)
# Map metadata values (Issue #3025)
map_metadata(new_meta, extractor.map_from, config)

meta.update(new_meta)
Expand Down
30 changes: 27 additions & 3 deletions nikola/utils.py
Expand Up @@ -95,8 +95,8 @@
'get_displayed_page_number', 'adjust_name_for_index_path_list',
'adjust_name_for_index_path', 'adjust_name_for_index_link',
'NikolaPygmentsHTML', 'create_redirect', 'clean_before_deployment',
'sort_posts', 'indent', 'load_data', 'html_unescape', 'rss_writer',
'map_metadata',
'sort_posts', 'smartjoin', 'indent', 'load_data', 'html_unescape',
'rss_writer', 'map_metadata', 'req_missing',
# Deprecated, moved to hierarchy_utils:
'TreeNode', 'clone_treenode', 'flatten_tree_structure',
'sort_classifications', 'join_hierarchical_category_path',
Expand Down Expand Up @@ -1862,6 +1862,26 @@ def sort_posts(posts, *keys):
return posts


def smartjoin(join_char: str, string_or_iterable) -> str:
"""Join string_or_iterable with join_char if it is not a string already.
>>> smartjoin('; ', 'foo, bar')
'foo, bar'
>>> smartjoin('; ', ['foo', 'bar'])
'foo; bar'
"""
if isinstance(string_or_iterable, (unicode_str, bytes_str)):
return string_or_iterable
else:
return join_char.join(string_or_iterable)


def _smartjoin_filter(string_or_iterable, join_char: str) -> str:
"""Join stuff smartly, with reversed arguments for Jinja2 filters."""
# http://jinja.pocoo.org/docs/2.10/api/#custom-filters
return smartjoin(join_char, string_or_iterable)


# Stolen from textwrap in Python 3.4.3.
def indent(text, prefix, predicate=None):
"""Add 'prefix' to the beginning of selected lines in 'text'.
Expand Down Expand Up @@ -1932,12 +1952,16 @@ def rss_writer(rss_obj, output_path):
def map_metadata(meta, key, config):
"""Map metadata from other platforms to Nikola names.
This uses the METADATA_MAPPING setting (via ``config``) and modifies the dict in place.
This uses the METADATA_MAPPING and METADATA_VALUE_MAPPING settings (via ``config``) and modifies the dict in place.
"""
for foreign, ours in config.get('METADATA_MAPPING', {}).get(key, {}).items():
if foreign in meta:
meta[ours] = meta[foreign]

for meta_key, hook in config.get('METADATA_VALUE_MAPPING', {}).get(key, {}).items():
if meta_key in meta:
meta[meta_key] = hook(meta[meta_key])


class ClassificationTranslationManager(object):
"""Keeps track of which classifications could be translated as which others.
Expand Down

0 comments on commit ce168a7

Please sign in to comment.