Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #2429 from getnikola/extra_themes_dirs
Added EXTRA_THEMES_DIRS.
  • Loading branch information
ralsina committed Aug 15, 2016
2 parents c12e7b8 + d40f648 commit 42a0b7f
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 39 deletions.
2 changes: 2 additions & 0 deletions CHANGES.txt
Expand Up @@ -23,6 +23,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
5 changes: 5 additions & 0 deletions nikola/conf.py.in
Expand Up @@ -1100,6 +1100,11 @@ UNSLUGIFY_TITLES = True
# repository.
# EXTRA_PLUGINS_DIRS = []

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

# List of regular expressions, links matching them will always be considered
# valid by "nikola check -l"
# LINK_CHECK_WHITELIST = []
Expand Down
9 changes: 7 additions & 2 deletions nikola/nikola.py
Expand Up @@ -462,6 +462,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 @@ -879,6 +880,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 @@ -1137,7 +1141,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 @@ -1160,7 +1164,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 Down
15 changes: 11 additions & 4 deletions nikola/plugins/command/bootswatch_theme.py
Expand Up @@ -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 bootswatch.com and a parent theme, creates a custom theme."""

Expand Down Expand Up @@ -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, self.site.themes_dirs)
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')

LOGGER.info("Creating '{0}' theme from '{1}' and '{2}'".format(name, swatch, parent))
Expand Down
2 changes: 1 addition & 1 deletion nikola/plugins/command/init.py
Expand Up @@ -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.
try:
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))
Expand Down
12 changes: 6 additions & 6 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(utils.get_theme_path_real(name, self.site.themes_dirs))
if parent_name is None:
break
try:
utils.get_theme_path(parent_name)
utils.get_theme_path_real(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_real(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_real(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_real(name, self.site.themes_dirs)
print(path)
except Exception:
print("not installed")
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))
with io.open(engine_file, 'r', encoding='utf-8') as fh:
parent_engine = fh.read().strip()

Expand Down
58 changes: 32 additions & 26 deletions nikola/utils.py
Expand Up @@ -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',
Expand Down Expand Up @@ -572,46 +572,55 @@ def __repr__(self):
sort_keys=True))


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:
break
Expand All @@ -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,
Expand All @@ -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')
Expand Down Expand Up @@ -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.
Expand All @@ -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'])))
/.../nikola/data/themes/base/assets/css/rst.css
>>> print(get_asset_path('assets/css/theme.css', ['bootstrap3', 'base']))
>>> print(get_asset_path('assets/css/theme.css', get_theme_chain('bootstrap3', ['themes'])))
/.../nikola/data/themes/bootstrap3/assets/css/theme.css
>>> print(get_asset_path('nikola.py', ['bootstrap3', 'base'], {'nikola': ''}))
>>> print(get_asset_path('nikola.py', get_theme_chain('bootstrap3', ['themes']), {'nikola': ''}))
/.../nikola/nikola.py
>>> print(get_asset_path('nikola.py', ['bootstrap3', 'base'], {'nikola': 'nikola'}))
>>> print(get_asset_path('nikola.py', get_theme_chain('bootstrap3', ['themes']), {'nikola': 'nikola'}))
None
>>> print(get_asset_path('nikola/nikola.py', ['bootstrap3', 'base'], {'nikola': 'nikola'}))
>>> print(get_asset_path('nikola/nikola.py', get_theme_chain('bootstrap3', ['themes']), {'nikola': 'nikola'}))
/.../nikola/nikola.py
"""
for theme_name in themes:
candidate = os.path.join(
get_theme_path(theme_name, _themes_dir),
path
)
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():
Expand Down

0 comments on commit 42a0b7f

Please sign in to comment.