Skip to content

Commit

Permalink
Added EXTRA_THEMES_DIRS.
Browse files Browse the repository at this point in the history
  • Loading branch information
felixfontein committed Aug 4, 2016
1 parent e8b2064 commit 78c59a3
Show file tree
Hide file tree
Showing 10 changed files with 50 additions and 39 deletions.
2 changes: 2 additions & 0 deletions CHANGES.txt
Expand Up @@ -7,6 +7,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)

Bugfixes
--------
Expand Down
2 changes: 1 addition & 1 deletion docs/extending.txt
Expand Up @@ -351,7 +351,7 @@ And the ``task_copy_assets.py`` 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, self.site.themes_dirs), 'assets')
dst = os.path.join(kw['output_folder'], 'assets')
for task in utils.copy_tree(src, dst):
if task['name'] in tasks:
Expand Down
22 changes: 14 additions & 8 deletions nikola/nikola.py
Expand Up @@ -438,6 +438,7 @@ def __init__(self, **config):
'DEPLOY_COMMANDS': {'default': []},
'DISABLED_PLUGINS': [],
'EXTRA_PLUGINS_DIRS': [],
'EXTRA_THEMES_DIRS': [],
'COMMENT_SYSTEM_ID': 'nikolademo',
'ENABLE_AUTHOR_PAGES': True,
'EXIF_WHITELIST': {},
Expand Down Expand Up @@ -855,6 +856,9 @@ def __init__(self, **config):
candidate = utils.get_translation_candidate(self.config, "f" + ext, lang)
compilers[compiler].add(candidate)

# 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"
Expand Down Expand Up @@ -1113,7 +1117,7 @@ def _activate_plugins_of_category(self, category):
def _get_themes(self):
if self._THEMES is None:
try:
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']))
Expand All @@ -1123,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)
'assets', 'css', 'bootstrap.min.css'), self._THEMES, themes_dirs=self.themes_dirs)
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.')

Expand All @@ -1136,7 +1140,8 @@ def _get_messages(self):
if self._MESSAGES is None:
self._MESSAGES = utils.load_messages(self.THEMES,
self.translations,
self.default_lang)
self.default_lang,
themes_dirs=self.themes_dirs)
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))
Expand All @@ -1153,7 +1158,8 @@ def _get_global_context(self):
custom_css_path = utils.get_asset_path(
'assets/css/custom.css',
self.THEMES,
self.config['FILES_FOLDERS']
self.config['FILES_FOLDERS'],
themes_dirs=self.themes_dirs
)
if custom_css_path and self.file_exists(custom_css_path, not_empty=True):
self._GLOBAL_CONTEXT['has_custom_css'] = True
Expand All @@ -1167,15 +1173,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)
template_sys_name = utils.get_template_engine(self.THEMES, self.themes_dirs)
pi = self.plugin_manager.getPluginByName(
template_sys_name, "TemplateSystem")
if pi is None:
sys.stderr.write("Error loading {0} template system "
"plugin\n".format(template_sys_name))
sys.exit(1)
self._template_system = pi.plugin_object
lookup_dirs = ['templates'] + [os.path.join(utils.get_theme_path(name), "templates")
lookup_dirs = ['templates'] + [os.path.join(utils.get_theme_path(name, self.themes_dirs), "templates")
for name in self.THEMES]
self._template_system.set_directories(lookup_dirs,
self.config['CACHE_FOLDER'])
Expand Down Expand Up @@ -1416,7 +1422,7 @@ def _register_templated_shortcodes(self):
"""Register shortcodes provided by templates in shortcodes/ folders."""
builtin_sc_dir = resource_filename(
'nikola',
os.path.join('data', 'shortcodes', utils.get_template_engine(self.THEMES)))
os.path.join('data', 'shortcodes', utils.get_template_engine(self.THEMES, self.themes_dirs)))

for sc_dir in [builtin_sc_dir, 'shortcodes']:
if not os.path.isdir(sc_dir):
Expand Down Expand Up @@ -1900,7 +1906,7 @@ def generic_page_renderer(self, lang, post, filters, context=None):
context = context.copy() if context else {}
deps = post.deps(lang) + \
self.template_system.template_deps(post.template_name)
deps.extend(utils.get_asset_path(x, self.THEMES) for x in ('bundles', 'parent', 'engine'))
deps.extend(utils.get_asset_path(x, self.THEMES, themes_dirs=self.themes_dirs) for x in ('bundles', 'parent', 'engine'))
deps = list(filter(None, deps))
context['post'] = post
context['lang'] = lang
Expand Down
2 changes: 1 addition & 1 deletion nikola/plugins/command/auto/__init__.py
Expand Up @@ -158,7 +158,7 @@ def _execute(self, options, args):
# Do not duplicate entries -- otherwise, multiple rebuilds are triggered
watched = set([
'templates/'
] + [get_theme_path(name) for name in self.site.THEMES])
] + [get_theme_path(name, self.site.themes_dirs) for name in self.site.THEMES])
for item in self.site.config['post_pages']:
watched.add(os.path.dirname(item[0]))
for item in self.site.config['FILES_FOLDERS']:
Expand Down
2 changes: 1 addition & 1 deletion nikola/plugins/command/bootswatch_theme.py
Expand Up @@ -79,7 +79,7 @@ def _execute(self, options, args):
version = ''

# See if we need bootswatch for bootstrap v2 or v3
themes = utils.get_theme_chain(parent)
themes = utils.get_theme_chain(parent, self.site.themes_dirs)
if 'bootstrap3' not in themes and 'bootstrap3-jinja' not in themes:
version = '2'
elif 'bootstrap' not in themes and 'bootstrap-jinja' not in themes:
Expand Down
14 changes: 7 additions & 7 deletions nikola/plugins/command/theme.py
Expand Up @@ -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(name, self.site.themes_dirs)
if parent_name is None:
break
try:
utils.get_theme_path(parent_name)
utils.get_theme_path(parent_name, self.site.themes_dirs)
break
except: # Not available
self.do_install(parent_name, data)
Expand Down Expand Up @@ -204,7 +204,7 @@ def do_install(self, name, data):
else:
dest_path = os.path.join(self.output_dir, name)
try:
theme_path = utils.get_theme_path(name)
theme_path = utils.get_theme_path(name, self.site.themes_dirs)
LOGGER.error("Theme '{0}' is already installed in {1}".format(name, theme_path))
except Exception:
LOGGER.error("Can't find theme {0}".format(name))
Expand All @@ -227,7 +227,7 @@ def do_install(self, name, data):
def do_uninstall(self, name):
"""Uninstall a theme."""
try:
path = utils.get_theme_path(name)
path = utils.get_theme_path(name, self.site.themes_dirs)
except Exception:
LOGGER.error('Unknown theme: {0}'.format(name))
return 1
Expand All @@ -243,7 +243,7 @@ def do_uninstall(self, name):
def get_path(self, name):
"""Get path for an installed theme."""
try:
path = utils.get_theme_path(name)
path = utils.get_theme_path(name, self.site.themes_dirs)
print(path)
except Exception:
print("not installed")
Expand All @@ -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(self.site.THEMES[0])
theme_path = utils.get_theme_path(self.site.THEMES[0], self.site.themes_dirs)
if theme_path.startswith('themes' + os.sep):
# Theme in local themes/ directory
base = os.path.join(theme_path, 'templates')
Expand Down Expand Up @@ -297,7 +297,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))
engine_file = utils.get_asset_path('engine', utils.get_theme_chain(parent, self.site.themes_dirs), themes_dirs=self.site.themes_dirs)
with io.open(engine_file, 'r', encoding='utf-8') as fh:
parent_engine = fh.read().strip()

Expand Down
7 changes: 4 additions & 3 deletions nikola/plugins/task/bundles.py
Expand Up @@ -60,7 +60,7 @@ def gen_tasks(self):
'filters': self.site.config['FILTERS'],
'output_folder': self.site.config['OUTPUT_FOLDER'],
'cache_folder': self.site.config['CACHE_FOLDER'],
'theme_bundles': get_theme_bundles(self.site.THEMES),
'theme_bundles': get_theme_bundles(self.site.THEMES, self.site.themes_dirs),
'themes': self.site.THEMES,
'files_folders': self.site.config['FILES_FOLDERS'],
'code_color_scheme': self.site.config['CODE_COLOR_SCHEME'],
Expand Down Expand Up @@ -104,6 +104,7 @@ def build_bundle(output, inputs):
fname,
self.site.THEMES,
self.site.config['FILES_FOLDERS'],
themes_dirs=self.site.themes_dirs,
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
Expand All @@ -125,12 +126,12 @@ def build_bundle(output, inputs):
yield utils.apply_filters(task, kw['filters'])


def get_theme_bundles(themes):
def get_theme_bundles(themes, themes_dirs):
"""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), 'bundles')
utils.get_theme_path(theme_name, themes_dirs), 'bundles')
if os.path.isfile(bundles_path):
with open(bundles_path) as fd:
for line in fd:
Expand Down
5 changes: 3 additions & 2 deletions nikola/plugins/task/copy_assets.py
Expand Up @@ -60,11 +60,12 @@ 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',
themes=kw['themes'],
files_folders=kw['files_folders'], output_dir=None)
files_folders=kw['files_folders'],
themes_dirs=self.site.themes_dirs, 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, self.site.themes_dirs), 'assets')
dst = os.path.join(kw['output_folder'], 'assets')
for task in utils.copy_tree(src, dst):
if task['name'] in tasks:
Expand Down
2 changes: 1 addition & 1 deletion nikola/plugins/task/robots.py
Expand Up @@ -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"], themes_dirs=self.site.themes_dirs, output_dir=False):
yield utils.apply_filters({
"basename": self.name,
"name": robots_path,
Expand Down
31 changes: 16 additions & 15 deletions nikola/utils.py
Expand Up @@ -572,46 +572,47 @@ def __repr__(self):
sort_keys=True))


def get_theme_path(theme, _themes_dir='themes'):
def get_theme_path(theme, themes_dirs=['themes']):
"""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_template_engine(themes, themes_dirs=['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(get_theme_path(theme_name, themes_dirs), '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=['themes']):
"""Get name of parent theme."""
parent_path = os.path.join(get_theme_path(theme_name, _themes_dir), 'parent')
parent_path = os.path.join(get_theme_path(theme_name, themes_dirs), 'parent')
if os.path.isfile(parent_path):
with open(parent_path) as fd:
return fd.readlines()[0].strip()
return None


def get_theme_chain(theme, _themes_dir='themes'):
def get_theme_chain(theme, themes_dirs=['themes']):
"""Create the full theme inheritance chain."""
themes = [theme]

while True:
parent = get_parent_theme_name(themes[-1], _themes_dir)
parent = get_parent_theme_name(themes[-1], themes_dirs)
# Avoid silly loops
if parent is None or parent in themes:
break
Expand All @@ -635,7 +636,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=['themes']):
"""Load theme's messages into context.
All the messages from parent themes are loaded,
Expand All @@ -644,8 +645,8 @@ def load_messages(themes, translations, default_lang):
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), 'messages')
default_folder = os.path.join(get_theme_path('base'), 'messages')
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')
sys.path.insert(0, default_folder)
sys.path.insert(0, msg_folder)
english = __import__('messages_en')
Expand Down Expand Up @@ -978,7 +979,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': ''}, themes_dirs=['themes'], output_dir='output'):
"""Return the "real", absolute path to the asset.
By default, it checks which theme provides the asset.
Expand All @@ -1005,7 +1006,7 @@ def get_asset_path(path, themes, files_folders={'files': ''}, _themes_dir='theme
"""
for theme_name in themes:
candidate = os.path.join(
get_theme_path(theme_name, _themes_dir),
get_theme_path(theme_name, themes_dirs),
path
)
if os.path.isfile(candidate):
Expand Down

0 comments on commit 78c59a3

Please sign in to comment.