aboutsummaryrefslogtreecommitdiffstats
path: root/nikola/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'nikola/utils.py')
-rw-r--r--nikola/utils.py149
1 files changed, 130 insertions, 19 deletions
diff --git a/nikola/utils.py b/nikola/utils.py
index 5589d68..423ded8 100644
--- a/nikola/utils.py
+++ b/nikola/utils.py
@@ -25,10 +25,11 @@
"""Utility functions."""
-from __future__ import print_function
+from __future__ import print_function, unicode_literals
from collections import defaultdict, Callable
import datetime
import hashlib
+import locale
import os
import re
import codecs
@@ -49,9 +50,12 @@ if sys.version_info[0] == 3:
bytes_str = bytes
unicode_str = str
unichr = chr
+ from imp import reload as _reload
else:
bytes_str = str
unicode_str = unicode # NOQA
+ _reload = reload # NOQA
+ unichr = unichr
from doit import tools
from unidecode import unidecode
@@ -60,7 +64,38 @@ import PyRSS2Gen as rss
__all__ = ['get_theme_path', 'get_theme_chain', 'load_messages', 'copy_tree',
'generic_rss_renderer', 'copy_file', 'slugify', 'unslugify',
- 'to_datetime', 'apply_filters', 'config_changed', 'get_crumbs']
+ 'to_datetime', 'apply_filters', 'config_changed', 'get_crumbs',
+ 'get_asset_path', '_reload', 'unicode_str', 'bytes_str',
+ 'unichr', 'Functionary', 'LocaleBorg']
+
+
+class Functionary(defaultdict):
+
+ """Class that looks like a function, but is a defaultdict."""
+
+ def __init__(self, default, default_lang):
+ super(Functionary, self).__init__(default)
+ self.default_lang = default_lang
+
+ def current_lang(self):
+ """Guess the current language from locale or default."""
+ lang = locale.getlocale()[0]
+ if lang:
+ if lang in self.keys():
+ return lang
+ lang = lang.split('_')[0]
+ if lang in self.keys():
+ return lang
+ # whatever
+ return self.default_lang
+
+ def __call__(self, key, lang=None):
+ """When called as a function, take an optional lang
+ and return self[lang][key]."""
+
+ if lang is None:
+ lang = self.current_lang()
+ return self[lang][key]
class CustomEncoder(json.JSONEncoder):
@@ -140,13 +175,13 @@ def get_theme_chain(theme):
return themes
-def load_messages(themes, translations):
+def load_messages(themes, translations, default_lang):
""" Load theme's messages into context.
All the messages from parent themes are loaded,
and "younger" themes have priority.
"""
- messages = defaultdict(dict)
+ messages = Functionary(dict, default_lang)
warned = []
oldpath = sys.path[:]
for theme_name in themes[::-1]:
@@ -285,16 +320,18 @@ def slugify(value):
From Django's "django/template/defaultfilters.py".
- >>> slugify('\xe1\xe9\xed.\xf3\xfa')
- 'aeiou'
+ >>> print(slugify('\xe1\xe9\xed.\xf3\xfa'))
+ aeiou
- >>> slugify('foo/bar')
- 'foobar'
+ >>> print(slugify('foo/bar'))
+ foobar
- >>> slugify('foo bar')
- 'foo-bar'
+ >>> print(slugify('foo bar'))
+ foo-bar
"""
+ if not isinstance(value, unicode_str):
+ raise ValueError("Not a unicode object: {0}".format(value))
value = unidecode(value)
# WARNING: this may not be python2/3 equivalent
# value = unicode(_slugify_strip_re.sub('', value).strip().lower())
@@ -367,6 +404,15 @@ def to_datetime(value, tzinfo=None):
return tzinfo.localize(dt)
except ValueError:
pass
+ # So, let's try dateutil
+ try:
+ from dateutil import parser
+ dt = parser.parse(value)
+ if tzinfo is None:
+ return dt
+ return tzinfo.localize(dt)
+ except ImportError:
+ raise ValueError('Unrecognized date/time: {0!r}, try installing dateutil...'.format(value))
raise ValueError('Unrecognized date/time: {0!r}'.format(value))
@@ -383,7 +429,7 @@ def apply_filters(task, filters):
if isinstance(key, (tuple, list)):
if ext in key:
return value
- elif isinstance(key, (str, bytes)):
+ elif isinstance(key, (bytes_str, unicode_str)):
if ext == key:
return value
else:
@@ -408,14 +454,29 @@ def apply_filters(task, filters):
def get_crumbs(path, is_file=False):
"""Create proper links for a crumb bar.
- >>> get_crumbs('galleries')
- [['#', 'galleries']]
-
- >>> get_crumbs(os.path.join('galleries','demo'))
- [['..', 'galleries'], ['#', 'demo']]
-
- >>> get_crumbs(os.path.join('listings','foo','bar'), is_file=True)
- [['..', 'listings'], ['.', 'foo'], ['#', 'bar']]
+ >>> crumbs = get_crumbs('galleries')
+ >>> len(crumbs)
+ 1
+ >>> print('|'.join(crumbs[0]))
+ #|galleries
+
+ >>> crumbs = get_crumbs(os.path.join('galleries','demo'))
+ >>> len(crumbs)
+ 2
+ >>> print('|'.join(crumbs[0]))
+ ..|galleries
+ >>> print('|'.join(crumbs[1]))
+ #|demo
+
+ >>> crumbs = get_crumbs(os.path.join('listings','foo','bar'), is_file=True)
+ >>> len(crumbs)
+ 3
+ >>> print('|'.join(crumbs[0]))
+ ..|listings
+ >>> print('|'.join(crumbs[1]))
+ .|foo
+ >>> print('|'.join(crumbs[2]))
+ #|bar
"""
crumbs = path.split(os.sep)
@@ -431,3 +492,53 @@ def get_crumbs(path, is_file=False):
_path = '/'.join(['..'] * i) or '#'
_crumbs.append([_path, crumb])
return list(reversed(_crumbs))
+
+
+def get_asset_path(path, themes, files_folders={'files': ''}):
+ """Checks which theme provides the path with the given asset,
+ and returns the "real" path to the asset, relative to the
+ current directory.
+
+ If the asset is not provided by a theme, then it will be checked for
+ in the FILES_FOLDERS
+
+ >>> print(get_asset_path('assets/css/rst.css', ['site', 'default']))
+ nikola/data/themes/default/assets/css/rst.css
+
+ >>> print(get_asset_path('assets/css/theme.css', ['site', 'default']))
+ nikola/data/themes/site/assets/css/theme.css
+
+ >>> print(get_asset_path('nikola.py', ['site', 'default'], {'nikola': ''}))
+ nikola/nikola.py
+
+ >>> print(get_asset_path('nikola/nikola.py', ['site', 'default'],
+ ... {'nikola':'nikola'}))
+ nikola/nikola.py
+
+ """
+ for theme_name in themes:
+ candidate = os.path.join(
+ get_theme_path(theme_name),
+ path
+ )
+ if os.path.isfile(candidate):
+ return os.path.relpath(candidate, os.getcwd())
+ for src, rel_dst in files_folders.items():
+ candidate = os.path.join(
+ src,
+ os.path.relpath(path, rel_dst)
+ )
+ if os.path.isfile(candidate):
+ return os.path.relpath(candidate, os.getcwd())
+
+ # whatever!
+ return None
+
+
+class LocaleBorg:
+ __shared_state = {
+ 'current_lang': None
+ }
+
+ def __init__(self):
+ self.__dict__ = self.__shared_state