Skip to content
Browse files
First version of plugin.
  • Loading branch information
felixfontein committed May 2, 2018
1 parent 94555d5 commit c94cff1ad9eba167c279c8fd87bf811d47105da3
Showing with 107 additions and 20 deletions.
  1. +6 −7 v8/upgrade_metadata_v8/
  2. +101 −13 v8/upgrade_metadata_v8/
@@ -1,13 +1,12 @@
Nikola Metadata Upgrade (v7 to v8)

This is a plugin to upgrade metadata in `.meta` files from the old format
(without descriptions) to the new reST-esque format. It only applies to users
with two-file posts (`.meta` files alongside text) in the older format (up to 7
lines of text without any descriptions), used by Nikola before v8.0.0. If this
is the case on your site, **you will be warned** by `nikola build` — otherwise,
don’t install this plugin (it won’t do a thing anyway and will only waste disk
space and load time).
This is a plugin to upgrade old-style tags metadata (`draft`, `private`, `mathjax`)
to new-style metadata, i.e. using the `status` and `has_math` metadata fields. The
tags were used in Nikola v7 and earlier, and are no longer used by default in
Nikola v8. If you are using them in your blog, **you will be warned** by
`nikola build` — otherwise, don’t install this plugin (it won’t do a thing anyway
and will only waste disk space and load time).

The plugin adds the `nikola upgrade_metadata_v8` command. Remove the plugin
manually after the upgrade.
@@ -28,6 +28,7 @@
from __future__ import unicode_literals
import io
import os
import sys
from nikola.plugin_categories import Command
from nikola import utils
@@ -52,6 +53,11 @@ class UpgradeMetadata(Command):
def _execute(self, options, args):
L = utils.get_logger('upgrade_metadata_v8', utils.STDERR_HANDLER)

L.error('This plugin can only be used if USE_TAG_METADATA is set to True.')
sys.exit(-1)['WARN_ABOUT_TAG_METADATA'] = False

# scan posts
flagged = []
@@ -64,30 +70,112 @@ def _execute(self, options, args):
else:'{0} posts (and/or their translations) contain old-style metadata:'.format(len(flagged)))
for post in flagged:' ' + post.metadata_path)' ' + (post.metadata_path if post.is_two_file else post.source_path))
L.warn('Please make a backup before running this plugin. It might eat your data.')
if not options['yes']:
yesno = utils.ask_yesno("Proceed with metadata upgrade?")
if options['yes'] or yesno:
no_converted = 0
no_converted_partial = 0
for post in flagged:
converted = False
fully_converted = True
for lang in['TRANSLATIONS'].keys():
# Get file names and extractor
extractor = post.used_extractor[lang]
is_two_file = post.is_two_file
if lang == post.default_lang:
fname = post.metadata_path
fname = post.metadata_path if is_two_file else post.source_path
meta_path = os.path.splitext(post.source_path)[0] + '.meta'
meta_path = os.path.splitext(post.source_path)[0] + '.meta' if is_two_file else post.source_path
fname = utils.get_translation_candidate(post.config, meta_path, lang)

if os.path.exists(fname):
with, 'r', encoding='utf-8') as fh:
meta = fh.readlines()
# We don't handle compilers which extract metadata for now
if post.compiler is extractor:
L.warn('Cannot convert {0} (language {1}), as metadata was extracted by compiler.'.format(fname, lang))
fully_converted = False

# Read metadata and text from post file
with, "r", encoding="utf-8-sig") as meta_file:
source_text =
if not is_two_file:
_, content_str = extractor.split_metadata_from_text(source_text)
meta = extractor.extract_text(source_text)

# Consider metadata mappings
sources = {
'tags': 'tags',
'status': 'status',
'has_math': 'has_math',
for foreign, ours in'METADATA_MAPPING', {}).get(extractor.map_from, {}).items():
if ours in sources:
sources[ours] = foreign
for meta_key, hook in'METADATA_VALUE_MAPPING', {}).get(extractor.map_from, {}).items():
if meta_key in sources.values():
L.warn('Cannot convert {0} (language {1}): a metadata value mapping is defined for "{2}"!'.format(fname, lang, meta_key))

# Update metadata
updated = False
tags = meta.get(sources['tags'], [])
tags_are_string = False
if not isinstance(tags, dict):
tags_are_string = True
tags = [tag.strip() for tag in tags.split(',') if tag.strip()]

if 'draft' in [_.lower() for _ in tags]:
meta[sources['status']] = 'draft'
updated = True

if 'private' in tags:
meta[sources['status']] = 'private'
updated = True

if 'mathjax' in tags:
meta[sources['has_math']] = 'yes'
updated = True

if not meta[min(1, len(meta) - 1)].startswith('.. '):
# check if we’re dealing with old style metadata
with, 'w', encoding='utf-8') as fh:
for k, v in zip(self.fields, meta):
fh.write('.. {0}: {1}'.format(k, v))
if tags_are_string:
meta[sources['tags']] = ', '.join(tags)

if not updated:
# Nothing to do (but successful)!
converted = True

# Recombine metadata with post text if necessary, and write back to file
meta_str = extractor.write_metadata(meta)
if not is_two_file:
if == 'nikola':
final_str = meta_str + '\n\n' + content_str
elif == 'yaml':
final_str = meta_str + '\n---\n' + content_str
elif == 'toml':
final_str = meta_str + '\n+++\n' + content_str
L.error("Cannot convert {0} (language {1}): don't know how to recombine metadata with text for the {2} extractor!".format(fname, lang,
fully_converted = False
final_str = meta_str

with, "w", encoding="utf-8-sig") as meta_file:
converted = True

if converted:
if fully_converted:
no_converted += 1
no_converted_partial += 1'{0} posts upgraded.'.format(len(flagged)))'{0} out of {2} posts upgraded; {1} only converted partially (see above output).'
.format(no_converted + no_converted_partial, no_converted_partial, len(flagged)))
else:'Metadata not upgraded.')

0 comments on commit c94cff1

Please sign in to comment.