Skip to content
Browse files
merged master
  • Loading branch information
ralsina committed Aug 16, 2016
2 parents 2478595 + 42a0b7f commit 88bca94aaad83ec18b42f8975f219471c6c358db
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 42 deletions.
@@ -6,6 +6,13 @@ Features

* Update options of chart directive to Pygal 2.2.3
* Pass global context to template shortcodes (Issue #2424)


* Default to English for docutils messages if no translations exist
(Issues #2422, #2437)

New in v7.7.12
@@ -17,6 +24,8 @@ Features
* Add ``sections`` filtering in the post list directive
(Issue #2409)
* Update Bootstrap to v3.3.7
* Add ``EXTRA_THEMES_DIRS`` search path, similar to ``EXTRA_PLUGINS_DIRS``,
to locate themes at other places (Issue #2427)

@@ -1100,6 +1100,11 @@ UNSLUGIFY_TITLES = True
# repository.

# Add the absolute paths to directories containing themes to use them.
# For example, the `v7` directory of your clone of the Nikola themes
# repository.

# List of regular expressions, links matching them will always be considered
# valid by "nikola check -l"
@@ -316,6 +316,27 @@
'te': 'te',
'uk': 'uk',
'ca': 'ca',
'da': 'da',
'de': 'de',
'en': 'en',
'eo': 'eo',
'es': 'es',
'fi': 'fi',
'fr': 'fr',
'gl': 'gl',
'it': 'it',
'ja': 'ja',
'lt': 'lt',
'pl': 'pl',
'pt': 'pt_br', # hope nobody will mind
'pt_br': 'pt_br',
'ru': 'ru',
'sk': 'sk',
'sv': 'sv',
'zh_cn': 'zh_cn'

@@ -441,6 +462,7 @@ def __init__(self, **config):
'DEPLOY_COMMANDS': {'default': []},
'COMMENT_SYSTEM_ID': 'nikolademo',
@@ -858,6 +880,9 @@ def __init__(self, **config):
candidate = utils.get_translation_candidate(self.config, "f" + ext, lang)

# Get search path for themes
self.themes_dirs = ['themes'] + self.config['EXTRA_THEMES_DIRS']

# Avoid redundant compilers
# Remove compilers that match nothing in POSTS/PAGES
# And put them in "bad compilers"
@@ -1116,7 +1141,7 @@ def _activate_plugins_of_category(self, category):
def _get_themes(self):
if self._THEMES is None:
self._THEMES = utils.get_theme_chain(self.config['THEME'])
self._THEMES = utils.get_theme_chain(self.config['THEME'], self.themes_dirs)
except Exception:
if self.config['THEME'] != 'bootstrap3':
utils.LOGGER.warn('''Cannot load theme "{0}", using 'bootstrap3' instead.'''.format(self.config['THEME']))
@@ -1139,7 +1164,8 @@ def _get_messages(self):
if self._MESSAGES is None:
self._MESSAGES = utils.load_messages(self.THEMES,
return self._MESSAGES
except utils.LanguageNotFoundError as e:
utils.LOGGER.error('''Cannot load language "{0}". Please make sure it is supported by Nikola itself, or that you have the appropriate messages files in your themes.'''.format(e.lang))
@@ -1411,8 +1437,13 @@ def _make_renderfunc(self, t_data):
def render_shortcode(*args, **kw):
kw['_args'] = args
return self.template_system.render_template_to_string(t_data, kw)
context = self.GLOBAL_CONTEXT.copy()
context['_args'] = args
context['lang'] = utils.LocaleBorg().current_lang
context[k] = context[k](context['lang'])
return self.template_system.render_template_to_string(t_data, context)
return render_shortcode

def _register_templated_shortcodes(self):
@@ -36,6 +36,13 @@
LOGGER = utils.get_logger('bootswatch_theme', utils.STDERR_HANDLER)

def _check_for_theme(theme, themes):
for t in themes:
if t.endswith(os.sep + theme):
return True
return False

class CommandBootswatchTheme(Command):
"""Given a swatch name from and a parent theme, creates a custom theme."""

@@ -79,12 +86,12 @@ def _execute(self, options, args):
version = ''

# See if we need bootswatch for bootstrap v2 or v3
themes = utils.get_theme_chain(parent)
if 'bootstrap3' not in themes and 'bootstrap3-jinja' not in themes:
themes = utils.get_theme_chain(parent,
if not _check_for_theme('bootstrap3', themes) and not _check_for_theme('bootstrap3-jinja', themes):
version = '2'
elif 'bootstrap' not in themes and 'bootstrap-jinja' not in themes:
elif not _check_for_theme('bootstrap', themes) and not _check_for_theme('bootstrap-jinja', themes):
LOGGER.warn('"bootswatch_theme" only makes sense for themes that use bootstrap')
elif 'bootstrap3-gradients' in themes or 'bootstrap3-gradients-jinja' in themes:
elif _check_for_theme('bootstrap3-gradients', themes) or _check_for_theme('bootstrap3-gradients-jinja', themes):
LOGGER.warn('"bootswatch_theme" doesn\'t work well with the bootstrap3-gradients family')"Creating '{0}' theme from '{1}' and '{2}'".format(name, swatch, parent))
@@ -360,7 +360,7 @@ def lhandler(default, toconf, show_header=True):
# Assuming that base contains all the locales, and that base does
# not inherit from anywhere.
messages = load_messages(['base'], tr, default)
messages = load_messages(['base'], tr, default, themes_dirs=['themes'])
SAMPLE_CONF['NAVIGATION_LINKS'] = format_navigation_links(langs, default, messages, SAMPLE_CONF['STRIP_INDEXES'])
except nikola.utils.LanguageNotFoundError as e:
print(" ERROR: the language '{0}' is not supported.".format(e.lang))
@@ -170,11 +170,11 @@ def do_install_deps(self, url, name):
installstatus = self.do_install(name, data)
# See if the theme's parent is available. If not, install it
while True:
parent_name = utils.get_parent_theme_name(name)
parent_name = utils.get_parent_theme_name(utils.get_theme_path_real(name,
if parent_name is None:
except: # Not available
self.do_install(parent_name, data)
@@ -204,7 +204,7 @@ def do_install(self, name, data):
dest_path = os.path.join(self.output_dir, name)
theme_path = utils.get_theme_path(name)
theme_path = utils.get_theme_path_real(name,
LOGGER.error("Theme '{0}' is already installed in {1}".format(name, theme_path))
except Exception:
LOGGER.error("Can't find theme {0}".format(name))
@@ -227,7 +227,7 @@ def do_install(self, name, data):
def do_uninstall(self, name):
"""Uninstall a theme."""
path = utils.get_theme_path(name)
path = utils.get_theme_path_real(name,
except Exception:
LOGGER.error('Unknown theme: {0}'.format(name))
return 1
@@ -243,7 +243,7 @@ def do_uninstall(self, name):
def get_path(self, name):
"""Get path for an installed theme."""
path = utils.get_theme_path(name)
path = utils.get_theme_path_real(name,
except Exception:
print("not installed")
@@ -297,7 +297,7 @@ def new_theme(self, name, engine, parent):"Created directory {0}".format(base))

# Check if engine and parent match
engine_file = utils.get_asset_path('engine', utils.get_theme_chain(parent))
engine_file = utils.get_asset_path('engine', utils.get_theme_chain(parent,
with, 'r', encoding='utf-8') as fh:
parent_engine =

@@ -39,6 +39,7 @@
import docutils.parsers.rst.directives
from docutils.parsers.rst import roles

from nikola.nikola import LEGAL_VALUES
from nikola.plugin_categories import PageCompiler
from nikola.utils import (
@@ -77,7 +78,7 @@ def compile_html_string(self, data, source_path=None, is_two_file=True):
'syntax_highlight': 'short',
'math_output': 'mathjax',
'template': default_template_path,
'language_code': LocaleBorg().current_lang,
'language_code': LEGAL_VALUES['DOCUTILS_LOCALES'].get(LocaleBorg().current_lang, 'en')

output, error_level, deps = rst2html(
@@ -69,7 +69,7 @@

from nikola import DEBUG

__all__ = ('CustomEncoder', 'get_theme_path', 'get_theme_chain', 'load_messages', 'copy_tree',
__all__ = ('CustomEncoder', 'get_theme_path', 'get_theme_path_real', 'get_theme_chain', 'load_messages', 'copy_tree',
'copy_file', 'slugify', 'unslugify', 'to_datetime', 'apply_filters',
'config_changed', 'get_crumbs', 'get_tzname', 'get_asset_path',
'_reload', 'unicode_str', 'bytes_str', 'unichr', 'Functionary',
@@ -572,46 +572,55 @@ def __repr__(self):

def get_theme_path(theme, _themes_dir='themes'):
def get_theme_path_real(theme, themes_dirs):
"""Return the path where the given theme's files are located.
Looks in ./themes and in the place where themes go when installed.
dir_name = os.path.join(_themes_dir, theme)
if os.path.isdir(dir_name):
return dir_name
for themes_dir in themes_dirs:
dir_name = os.path.join(themes_dir, theme)
if os.path.isdir(dir_name):
return dir_name
dir_name = resource_filename('nikola', os.path.join('data', 'themes', theme))
if os.path.isdir(dir_name):
return dir_name
raise Exception("Can't find theme '{0}'".format(theme))

def get_template_engine(themes, _themes_dir='themes'):
def get_theme_path(theme):
"""Return the theme's path, which equals the theme's name."""
return theme

def get_template_engine(themes):
"""Get template engine used by a given theme."""
for theme_name in themes:
engine_path = os.path.join(get_theme_path(theme_name, _themes_dir), 'engine')
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_dir='themes'):
def get_parent_theme_name(theme_name, themes_dirs=None):
"""Get name of parent theme."""
parent_path = os.path.join(get_theme_path(theme_name, _themes_dir), 'parent')
parent_path = os.path.join(theme_name, 'parent')
if os.path.isfile(parent_path):
with open(parent_path) as fd:
return fd.readlines()[0].strip()
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_dir='themes'):
"""Create the full theme inheritance chain."""
themes = [theme]
def get_theme_chain(theme, themes_dirs):
"""Create the full theme inheritance chain including paths."""
themes = [get_theme_path_real(theme, themes_dirs)]

while True:
parent = get_parent_theme_name(themes[-1], _themes_dir)
parent = get_parent_theme_name(themes[-1], themes_dirs=themes_dirs)
# Avoid silly loops
if parent is None or parent in themes:
@@ -635,7 +644,7 @@ def __str__(self):
return 'cannot find language {0}'.format(self.lang)

def load_messages(themes, translations, default_lang):
def load_messages(themes, translations, default_lang, themes_dirs):
"""Load theme's messages into context.
All the messages from parent themes are loaded,
@@ -645,7 +654,7 @@ def load_messages(themes, translations, default_lang):
oldpath = list(sys.path)
for theme_name in themes[::-1]:
msg_folder = os.path.join(get_theme_path(theme_name), 'messages')
default_folder = os.path.join(get_theme_path('base'), 'messages')
default_folder = os.path.join(get_theme_path_real('base', themes_dirs), 'messages')
sys.path.insert(0, default_folder)
sys.path.insert(0, msg_folder)
english = __import__('messages_en')
@@ -978,7 +987,7 @@ def get_crumbs(path, is_file=False, index_folder=None, lang=None):
return list(reversed(_crumbs))

def get_asset_path(path, themes, files_folders={'files': ''}, _themes_dir='themes', output_dir='output'):
def get_asset_path(path, themes, files_folders={'files': ''}, output_dir='output'):
"""Return the "real", absolute path to the asset.
By default, it checks which theme provides the asset.
@@ -987,27 +996,24 @@ def get_asset_path(path, themes, files_folders={'files': ''}, _themes_dir='theme
If it's not provided by either, it will be chacked in output, where
it may have been created by another plugin.
>>> print(get_asset_path('assets/css/rst.css', ['bootstrap3', 'base']))
>>> print(get_asset_path('assets/css/rst.css', get_theme_chain('bootstrap3', ['themes'])))
>>> print(get_asset_path('assets/css/theme.css', ['bootstrap3', 'base']))
>>> print(get_asset_path('assets/css/theme.css', get_theme_chain('bootstrap3', ['themes'])))
>>> print(get_asset_path('', ['bootstrap3', 'base'], {'nikola': ''}))
>>> print(get_asset_path('', get_theme_chain('bootstrap3', ['themes']), {'nikola': ''}))
>>> print(get_asset_path('', ['bootstrap3', 'base'], {'nikola': 'nikola'}))
>>> print(get_asset_path('', get_theme_chain('bootstrap3', ['themes']), {'nikola': 'nikola'}))
>>> print(get_asset_path('nikola/', ['bootstrap3', 'base'], {'nikola': 'nikola'}))
>>> print(get_asset_path('nikola/', get_theme_chain('bootstrap3', ['themes']), {'nikola': 'nikola'}))
for theme_name in themes:
candidate = os.path.join(
get_theme_path(theme_name, _themes_dir),
candidate = os.path.join(get_theme_path(theme_name), path)
if os.path.isfile(candidate):
return candidate
for src, rel_dst in files_folders.items():

0 comments on commit 88bca94

Please sign in to comment.