Skip to content
Browse files
Putting theme paths and not just names into site.THEMES. Removing exp…
…licit passing of themes_dirs in several places except where handling theme names is necessary.
  • Loading branch information
felixfontein committed Aug 7, 2016
1 parent 78c59a3 commit 5f61a6306b2137256b08d15933c94c10f926f7d6
@@ -351,7 +351,7 @@ And the ```` file, in its entirety:

tasks = {}
for theme_name in kw['themes']:
src = os.path.join(utils.get_theme_path(theme_name,, 'assets')
src = os.path.join(utils.get_theme_path(theme_name), 'assets')
dst = os.path.join(kw['output_folder'], 'assets')
for task in utils.copy_tree(src, dst):
if task['name'] in tasks:
@@ -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"
@@ -1127,7 +1127,7 @@ def _get_themes(self):
# Check consistency of USE_CDN and the current THEME (Issue #386)
if self.config['USE_CDN'] and self.config['USE_CDN_WARNING']:
bootstrap_path = utils.get_asset_path(os.path.join(
'assets', 'css', 'bootstrap.min.css'), self._THEMES, themes_dirs=self.themes_dirs)
'assets', 'css', 'bootstrap.min.css'), self._THEMES)
if bootstrap_path and bootstrap_path.split(os.sep)[-4] not in ['bootstrap', 'bootstrap3']:
utils.LOGGER.warn('The USE_CDN option may be incompatible with your theme, because it uses a hosted version of bootstrap.')

@@ -1158,8 +1158,7 @@ def _get_global_context(self):
custom_css_path = utils.get_asset_path(
if custom_css_path and self.file_exists(custom_css_path, not_empty=True):
self._GLOBAL_CONTEXT['has_custom_css'] = True
@@ -1173,15 +1172,15 @@ def _get_global_context(self):
def _get_template_system(self):
if self._template_system is None:
# Load template plugin
template_sys_name = utils.get_template_engine(self.THEMES, self.themes_dirs)
template_sys_name = utils.get_template_engine(self.THEMES)
pi = self.plugin_manager.getPluginByName(
template_sys_name, "TemplateSystem")
if pi is None:
sys.stderr.write("Error loading {0} template system "
self._template_system = pi.plugin_object
lookup_dirs = ['templates'] + [os.path.join(utils.get_theme_path(name, self.themes_dirs), "templates")
lookup_dirs = ['templates'] + [os.path.join(utils.get_theme_path(name), "templates")
for name in self.THEMES]
@@ -1422,7 +1421,7 @@ def _register_templated_shortcodes(self):
"""Register shortcodes provided by templates in shortcodes/ folders."""
builtin_sc_dir = resource_filename(
os.path.join('data', 'shortcodes', utils.get_template_engine(self.THEMES, self.themes_dirs)))
os.path.join('data', 'shortcodes', utils.get_template_engine(self.THEMES)))

for sc_dir in [builtin_sc_dir, 'shortcodes']:
if not os.path.isdir(sc_dir):
@@ -1906,7 +1905,7 @@ def generic_page_renderer(self, lang, post, filters, context=None):
context = context.copy() if context else {}
deps = post.deps(lang) + \
deps.extend(utils.get_asset_path(x, self.THEMES, themes_dirs=self.themes_dirs) for x in ('bundles', 'parent', 'engine'))
deps.extend(utils.get_asset_path(x, self.THEMES) for x in ('bundles', 'parent', 'engine'))
deps = list(filter(None, deps))
context['post'] = post
context['lang'] = lang
@@ -158,7 +158,7 @@ def _execute(self, options, args):
# Do not duplicate entries -- otherwise, multiple rebuilds are triggered
watched = set([
] + [get_theme_path(name, for name in])
] + [get_theme_path(name) for name in])
for item in['post_pages']:
for item in['FILES_FOLDERS']:
@@ -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."""

@@ -80,11 +87,11 @@ def _execute(self, options, args):

# 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:
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")
@@ -268,7 +268,7 @@ def copy_template(self, template):

# Figure out where to put it.
# Check if a local theme exists.
theme_path = utils.get_theme_path([0],
theme_path = utils.get_theme_path([0])
if theme_path.startswith('themes' + os.sep):
# Theme in local themes/ directory
base = os.path.join(theme_path, 'templates')
@@ -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 =

@@ -60,7 +60,7 @@ def gen_tasks(self):
'theme_bundles': get_theme_bundles(,,
'theme_bundles': get_theme_bundles(,
@@ -104,7 +104,6 @@ def build_bundle(output, inputs):
output_dir=kw['output_folder']) or fname == os.path.join('assets', 'css', 'code.css')]
# code.css will be generated by us if it does not exist in
# FILES_FOLDERS or theme assets. It is guaranteed that the
@@ -126,12 +125,12 @@ def build_bundle(output, inputs):
yield utils.apply_filters(task, kw['filters'])

def get_theme_bundles(themes, themes_dirs):
def get_theme_bundles(themes):
"""Given a theme chain, return the bundle definitions."""
bundles = {}
for theme_name in themes:
bundles_path = os.path.join(
utils.get_theme_path(theme_name, themes_dirs), 'bundles')
utils.get_theme_path(theme_name), 'bundles')
if os.path.isfile(bundles_path):
with open(bundles_path) as fd:
for line in fd:
@@ -60,12 +60,11 @@ def gen_tasks(self):
code_css_path = os.path.join(kw['output_folder'], 'assets', 'css', 'code.css')
code_css_input = utils.get_asset_path('assets/css/code.css',
files_folders=kw['files_folders'],, output_dir=None)
files_folders=kw['files_folders'], output_dir=None)
yield self.group_task()

for theme_name in kw['themes']:
src = os.path.join(utils.get_theme_path(theme_name,, 'assets')
src = os.path.join(utils.get_theme_path(theme_name), 'assets')
dst = os.path.join(kw['output_folder'], 'assets')
for task in utils.copy_tree(src, dst):
if task['name'] in tasks:
@@ -71,7 +71,7 @@ def write_robots():

yield self.group_task()

if not utils.get_asset_path("robots.txt", [], files_folders=kw["files_folders"],, output_dir=False):
if not utils.get_asset_path("robots.txt", [], files_folders=kw["files_folders"], output_dir=False):
yield utils.apply_filters({
"name": robots_path,
@@ -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,7 +572,7 @@ def __repr__(self):

def get_theme_path(theme, themes_dirs=['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.
@@ -587,32 +587,40 @@ def get_theme_path(theme, themes_dirs=['themes']):
raise Exception("Can't find theme '{0}'".format(theme))

def get_template_engine(themes, themes_dirs=['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_dirs), '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_dirs=['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_dirs), '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_dirs=['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_dirs)
parent = get_parent_theme_name(themes[-1], themes_dirs=themes_dirs)
# Avoid silly loops
if parent is None or parent in themes:
@@ -636,7 +644,7 @@ def __str__(self):
return 'cannot find language {0}'.format(self.lang)

def load_messages(themes, translations, default_lang, themes_dirs=['themes']):
def load_messages(themes, translations, default_lang, themes_dirs):
"""Load theme's messages into context.
All the messages from parent themes are loaded,
@@ -645,8 +653,8 @@ def load_messages(themes, translations, default_lang, themes_dirs=['themes']):
messages = Functionary(dict, default_lang)
oldpath = list(sys.path)
for theme_name in themes[::-1]:
msg_folder = os.path.join(get_theme_path(theme_name, themes_dirs), 'messages')
default_folder = os.path.join(get_theme_path('base', themes_dirs), 'messages')
msg_folder = os.path.join(get_theme_path(theme_name), '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')
@@ -979,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_dirs=['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.
@@ -1005,10 +1013,7 @@ def get_asset_path(path, themes, files_folders={'files': ''}, themes_dirs=['them
for theme_name in themes:
candidate = os.path.join(
get_theme_path(theme_name, themes_dirs),
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 5f61a63

Please sign in to comment.