Skip to content

Commit

Permalink
Fix #2758 -- Add support for theme meta files
Browse files Browse the repository at this point in the history
Signed-off-by: Chris Warrick <kwpolska@gmail.com>
  • Loading branch information
Kwpolska committed May 15, 2017
1 parent 3270735 commit 72c7520
Show file tree
Hide file tree
Showing 15 changed files with 117 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
Expand Up @@ -4,6 +4,7 @@ New in master
Features
--------

* Use ``.theme`` files to store theme metadata (Issue #2758)
* New ``add_header_permalinks`` filter, for Sphinx-style header links
(Issue #2636)

Expand Down
10 changes: 10 additions & 0 deletions nikola/data/themes/base-jinja/base-jinja.theme
@@ -0,0 +1,10 @@
[Theme]
engine = jinja
parent = base
author = The Nikola Contributors
author_url = https://getnikola.com/
license = MIT

[Family]
family = base
mako_version = base
1 change: 0 additions & 1 deletion nikola/data/themes/base-jinja/engine

This file was deleted.

1 change: 0 additions & 1 deletion nikola/data/themes/base-jinja/parent

This file was deleted.

9 changes: 9 additions & 0 deletions nikola/data/themes/base/base.theme
@@ -0,0 +1,9 @@
[Theme]
engine = mako
author = The Nikola Contributors
author_url = https://getnikola.com/
license = MIT

[Family]
family = base
jinja_version = base-jinja
1 change: 0 additions & 1 deletion nikola/data/themes/base/engine

This file was deleted.

13 changes: 13 additions & 0 deletions nikola/data/themes/bootstrap3-jinja/bootstrap3-jinja.theme
@@ -0,0 +1,13 @@
[Theme]
engine = jinja
parent = base-jinja
author = The Nikola Contributors
author_url = https://getnikola.com/
based_on = Bootstrap 3 <http://getbootstrap.com/>
license = MIT
tags = bootstrap

[Family]
family = bootstrap3
mako_version = bootstrap3
variants = bootstrap3-gradients, bootstrap3-gradients-jinja
1 change: 0 additions & 1 deletion nikola/data/themes/bootstrap3-jinja/engine

This file was deleted.

1 change: 0 additions & 1 deletion nikola/data/themes/bootstrap3-jinja/parent

This file was deleted.

13 changes: 13 additions & 0 deletions nikola/data/themes/bootstrap3/bootstrap3.theme
@@ -0,0 +1,13 @@
[Theme]
engine = mako
parent = base
author = The Nikola Contributors
author_url = https://getnikola.com/
based_on = Bootstrap 3 <http://getbootstrap.com/>
license = MIT
tags = bootstrap

[Family]
family = bootstrap3
jinja_version = bootstrap3-jinja
variants = bootstrap3-gradients, bootstrap3-gradients-jinja
1 change: 0 additions & 1 deletion nikola/data/themes/bootstrap3/engine

This file was deleted.

1 change: 0 additions & 1 deletion nikola/data/themes/bootstrap3/parent

This file was deleted.

3 changes: 3 additions & 0 deletions nikola/nikola.py
Expand Up @@ -2267,6 +2267,9 @@ def generic_page_renderer(self, lang, post, filters, context=None):
deps = post.deps(lang)
uptodate_deps = post.deps_uptodate(lang)
deps.extend(utils.get_asset_path(x, self.THEMES) for x in ('bundles', 'parent', 'engine'))
_theme_ini = utils.get_asset_path(self.config['THEME'] + '.theme', self.THEMES)
if _theme_ini:
deps.append(_theme_ini)

context = copy(context) if context else {}
context['post'] = post
Expand Down
41 changes: 30 additions & 11 deletions nikola/plugins/command/theme.py
Expand Up @@ -32,6 +32,7 @@
import shutil
import time
import requests
import configparser

import pygments
from pygments.lexers import PythonLexer
Expand Down Expand Up @@ -131,6 +132,13 @@ class CommandTheme(Command):
'default': 'base',
'help': 'Parent to use for new theme (default: base)',
},
{
'name': 'new_legacy_meta',
'long': 'legacy-meta',
'type': bool,
'default': False,
'help': 'Create legacy meta files for new theme',
},
]

def _execute(self, options, args):
Expand All @@ -147,6 +155,7 @@ def _execute(self, options, args):
new = options.get('new')
new_engine = options.get('new_engine')
new_parent = options.get('new_parent')
new_legacy_meta = options.get('new_legacy_meta')
command_count = [bool(x) for x in (
install,
uninstall,
Expand All @@ -172,7 +181,7 @@ def _execute(self, options, args):
elif copy_template:
return self.copy_template(copy_template)
elif new:
return self.new_theme(new, new_engine, new_parent)
return self.new_theme(new, new_engine, new_parent, new_legacy_meta)

def do_install_deps(self, url, name):
"""Install themes and their dependencies."""
Expand Down Expand Up @@ -316,7 +325,7 @@ def copy_template(self, template):
LOGGER.error("This file already exists in your templates directory ({0}).".format(base))
return 3

def new_theme(self, name, engine, parent):
def new_theme(self, name, engine, parent, create_legacy_meta=False):
"""Create a new theme."""
base = 'themes'
themedir = os.path.join(base, name)
Expand All @@ -326,9 +335,7 @@ def new_theme(self, name, engine, parent):
LOGGER.info("Created directory {0}".format(base))

# Check if engine and parent match
engine_file = utils.get_asset_path('engine', utils.get_theme_chain(parent, self.site.themes_dirs))
with io.open(engine_file, 'r', encoding='utf-8') as fh:
parent_engine = fh.read().strip()
parent_engine = utils.get_template_engine(utils.get_theme_chain(parent, self.site.themes_dirs))

if parent_engine != engine:
LOGGER.error("Cannot use engine {0} because parent theme '{1}' uses {2}".format(engine, parent, parent_engine))
Expand All @@ -342,12 +349,24 @@ def new_theme(self, name, engine, parent):
LOGGER.error("Theme already exists")
return 2

with io.open(os.path.join(themedir, 'parent'), 'w', encoding='utf-8') as fh:
fh.write(parent + '\n')
LOGGER.info("Created file {0}".format(os.path.join(themedir, 'parent')))
with io.open(os.path.join(themedir, 'engine'), 'w', encoding='utf-8') as fh:
fh.write(engine + '\n')
LOGGER.info("Created file {0}".format(os.path.join(themedir, 'engine')))
cp = configparser.ConfigParser()
cp['Theme'] = {
'engine': engine,
'parent': parent
}

theme_meta_path = os.path.join(themedir, name + '.theme')
with io.open(theme_meta_path, 'w', encoding='utf-8') as fh:
cp.write(fh)
LOGGER.info("Created file {0}".format(theme_meta_path))

if create_legacy_meta:
with io.open(os.path.join(themedir, 'parent'), 'w', encoding='utf-8') as fh:
fh.write(parent + '\n')
LOGGER.info("Created file {0}".format(os.path.join(themedir, 'parent')))
with io.open(os.path.join(themedir, 'engine'), 'w', encoding='utf-8') as fh:
fh.write(engine + '\n')
LOGGER.info("Created file {0}".format(os.path.join(themedir, 'engine')))

LOGGER.info("Theme {0} created successfully.".format(themedir))
LOGGER.notice('Remember to set THEME="{0}" in conf.py to use this theme.'.format(name))
Expand Down
50 changes: 38 additions & 12 deletions nikola/utils.py
Expand Up @@ -28,6 +28,7 @@

from __future__ import print_function, unicode_literals, absolute_import
import calendar
import configparser
import datetime
import dateutil.tz
import hashlib
Expand Down Expand Up @@ -607,27 +608,52 @@ def get_theme_path(theme):
return theme


def parse_theme_meta(theme_dir):
"""Parse a .theme meta file."""
cp = configparser.ConfigParser()
# The `or` case is in case theme_dir ends with a trailing slash
theme_name = os.path.basename(theme_dir) or os.path.basename(os.path.dirname(theme_dir))
theme_meta_path = os.path.join(theme_dir, theme_name + '.theme')
cp.read(theme_meta_path)
return cp if cp.has_section('Theme') else None


def get_template_engine(themes):
"""Get template engine used by a given theme."""
for theme_name in themes:
engine_path = os.path.join(theme_name, 'engine')
if os.path.isfile(engine_path):
with open(engine_path) as fd:
return fd.readlines()[0].strip()
# default
return 'mako'
meta = parse_theme_meta(theme_name)
if meta:
e = meta.get('Theme', 'engine', fallback=None)
if e:
return e
else:
# Theme still uses old-style parent/engine files
engine_path = os.path.join(theme_name, 'engine')
if os.path.isfile(engine_path):
with open(engine_path) as fd:
return fd.readlines()[0].strip()
# default
return 'mako'


def get_parent_theme_name(theme_name, themes_dirs=None):
"""Get name of parent theme."""
parent_path = os.path.join(theme_name, 'parent')
if os.path.isfile(parent_path):
with open(parent_path) as fd:
parent = fd.readlines()[0].strip()
if themes_dirs:
meta = parse_theme_meta(theme_name)
if meta:
parent = meta.get('Theme', 'parent', fallback=None)
if themes_dirs and parent:
return get_theme_path_real(parent, themes_dirs)
return parent
return None
else:
# Theme still uses old-style parent/engine files
parent_path = os.path.join(theme_name, 'parent')
if os.path.isfile(parent_path):
with open(parent_path) as fd:
parent = fd.readlines()[0].strip()
if themes_dirs:
return get_theme_path_real(parent, themes_dirs)
return parent
return None


def get_theme_chain(theme, themes_dirs):
Expand Down

0 comments on commit 72c7520

Please sign in to comment.