diff options
Diffstat (limited to 'nikola/utils.py')
| -rw-r--r-- | nikola/utils.py | 149 |
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 |
