aboutsummaryrefslogtreecommitdiffstats
path: root/nikola/nikola.py
diff options
context:
space:
mode:
Diffstat (limited to 'nikola/nikola.py')
-rw-r--r--nikola/nikola.py322
1 files changed, 196 insertions, 126 deletions
diff --git a/nikola/nikola.py b/nikola/nikola.py
index 2a15568..e0af7ad 100644
--- a/nikola/nikola.py
+++ b/nikola/nikola.py
@@ -24,6 +24,8 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+"""The main Nikola site object."""
+
from __future__ import print_function, unicode_literals
import io
from collections import defaultdict
@@ -60,8 +62,9 @@ from .plugin_categories import (
Command,
LateTask,
PageCompiler,
- RestExtension,
+ CompilerExtension,
MarkdownExtension,
+ RestExtension,
Task,
TaskMultiplier,
TemplateSystem,
@@ -85,7 +88,7 @@ DEFAULT_TRANSLATIONS_PATTERN = '{path}.{lang}.{ext}'
config_changed = utils.config_changed
-__all__ = ['Nikola']
+__all__ = ('Nikola',)
# We store legal values for some setting here. For internal use.
LEGAL_VALUES = {
@@ -123,6 +126,7 @@ LEGAL_VALUES = {
'ko': 'Korean',
'nb': 'Norwegian Bokmål',
'nl': 'Dutch',
+ 'pa': 'Punjabi',
'pl': 'Polish',
'pt_br': 'Portuguese (Brasil)',
'ru': 'Russian',
@@ -135,6 +139,29 @@ LEGAL_VALUES = {
'uk': 'Ukrainian',
'zh_cn': 'Chinese (Simplified)',
},
+ '_WINDOWS_LOCALE_GUESSES': {
+ # TODO incomplete
+ # some languages may need that the appropiate Microsoft Language Pack be instaled.
+ "bg": "Bulgarian",
+ "ca": "Catalan",
+ "de": "German",
+ "el": "Greek",
+ "en": "English",
+ "eo": "Esperanto",
+ "es": "Spanish",
+ "fa": "Farsi", # Persian
+ "fr": "French",
+ "hr": "Croatian",
+ "it": "Italian",
+ "jp": "Japanese",
+ "nl": "Dutch",
+ "pl": "Polish",
+ "pt_br": "Portuguese_Brazil",
+ "ru": "Russian",
+ "sl_si": "Slovenian",
+ "tr_tr": "Turkish",
+ "zh_cn": "Chinese_China", # Chinese (Simplified)
+ },
'_TRANSLATIONS_WITH_COUNTRY_SPECIFIERS': {
# This dict is used in `init` in case of locales that exist with a
# country specifier. If there is no other locale that has the same
@@ -238,7 +265,7 @@ LEGAL_VALUES = {
def _enclosure(post, lang):
- '''Default implementation of enclosures'''
+ """Add an enclosure to RSS."""
enclosure = post.meta('enclosure', lang)
if enclosure:
length = 0
@@ -256,7 +283,6 @@ class Nikola(object):
def __init__(self, **config):
"""Setup proper environment for running tasks."""
-
# Register our own path handlers
self.path_handlers = {
'slug': self.slug_path,
@@ -272,6 +298,7 @@ class Nikola(object):
self.posts_per_month = defaultdict(list)
self.posts_per_tag = defaultdict(list)
self.posts_per_category = defaultdict(list)
+ self.tags_per_language = defaultdict(list)
self.post_per_file = {}
self.timeline = []
self.pages = []
@@ -279,7 +306,7 @@ class Nikola(object):
self._template_system = None
self._THEMES = None
self.debug = DEBUG
- self.loghandlers = []
+ self.loghandlers = utils.STDERR_HANDLER # TODO remove on v8
self.colorful = config.pop('__colorful__', False)
self.invariant = config.pop('__invariant__', False)
self.quiet = config.pop('__quiet__', False)
@@ -422,7 +449,7 @@ class Nikola(object):
'TAG_PAGES_DESCRIPTIONS': {},
'TAGLIST_MINIMUM_POSTS': 1,
'TEMPLATE_FILTERS': {},
- 'THEME': 'bootstrap',
+ 'THEME': 'bootstrap3',
'THEME_REVEAL_CONFIG_SUBTHEME': 'sky',
'THEME_REVEAL_CONFIG_TRANSITION': 'cube',
'THUMBNAIL_SIZE': 180,
@@ -661,6 +688,37 @@ class Nikola(object):
self.tzinfo = dateutil.tz.gettz()
self.config['__tzinfo__'] = self.tzinfo
+ # Store raw compilers for internal use (need a copy for that)
+ self.config['_COMPILERS_RAW'] = {}
+ for k, v in self.config['COMPILERS'].items():
+ self.config['_COMPILERS_RAW'][k] = list(v)
+
+ compilers = defaultdict(set)
+ # Also add aliases for combinations with TRANSLATIONS_PATTERN
+ for compiler, exts in self.config['COMPILERS'].items():
+ for ext in exts:
+ compilers[compiler].add(ext)
+ for lang in self.config['TRANSLATIONS'].keys():
+ candidate = utils.get_translation_candidate(self.config, "f" + ext, lang)
+ compilers[compiler].add(candidate)
+
+ # Avoid redundant compilers
+ # Remove compilers that match nothing in POSTS/PAGES
+ # And put them in "bad compilers"
+ pp_exts = set([os.path.splitext(x[0])[1] for x in self.config['post_pages']])
+ self.config['COMPILERS'] = {}
+ self.disabled_compilers = {}
+ self.bad_compilers = set([])
+ for k, v in compilers.items():
+ if pp_exts.intersection(v):
+ self.config['COMPILERS'][k] = sorted(list(v))
+ else:
+ self.bad_compilers.add(k)
+
+ self._set_global_context()
+
+ def init_plugins(self, commands_only=False):
+ """Load plugins as needed."""
self.plugin_manager = PluginManager(categories_filter={
"Command": Command,
"Task": Task,
@@ -668,13 +726,14 @@ class Nikola(object):
"TemplateSystem": TemplateSystem,
"PageCompiler": PageCompiler,
"TaskMultiplier": TaskMultiplier,
- "RestExtension": RestExtension,
+ "CompilerExtension": CompilerExtension,
"MarkdownExtension": MarkdownExtension,
+ "RestExtension": RestExtension,
"SignalHandler": SignalHandler,
"ConfigPlugin": ConfigPlugin,
"PostScanner": PostScanner,
})
- self.plugin_manager.setPluginInfoExtension('plugin')
+ self.plugin_manager.getPluginLocator().setPluginInfoExtension('plugin')
extra_plugins_dirs = self.config['EXTRA_PLUGINS_DIRS']
if sys.version_info[0] == 3:
places = [
@@ -689,8 +748,36 @@ class Nikola(object):
os.path.expanduser('~/.nikola/plugins'),
] + [utils.sys_encode(path) for path in extra_plugins_dirs if path]
- self.plugin_manager.setPluginPlaces(places)
- self.plugin_manager.collectPlugins()
+ self.plugin_manager.getPluginLocator().setPluginPlaces(places)
+ self.plugin_manager.locatePlugins()
+ bad_candidates = set([])
+ for p in self.plugin_manager._candidates:
+ if commands_only:
+ if p[-1].details.has_option('Nikola', 'plugincategory'):
+ # FIXME TemplateSystem should not be needed
+ if p[-1].details.get('Nikola', 'PluginCategory') not in {'Command', 'Template'}:
+ bad_candidates.add(p)
+ else: # Not commands-only
+ # Remove compilers we don't use
+ if p[-1].name in self.bad_compilers:
+ bad_candidates.add(p)
+ self.disabled_compilers[p[-1].name] = p
+ utils.LOGGER.debug('Not loading unneeded compiler {}', p[-1].name)
+ if p[-1].name not in self.config['COMPILERS'] and \
+ p[-1].details.has_option('Nikola', 'plugincategory') and p[-1].details.get('Nikola', 'PluginCategory') == 'Compiler':
+ bad_candidates.add(p)
+ self.disabled_compilers[p[-1].name] = p
+ utils.LOGGER.debug('Not loading unneeded compiler {}', p[-1].name)
+ # Remove blacklisted plugins
+ if p[-1].name in self.config['DISABLED_PLUGINS']:
+ bad_candidates.add(p)
+ utils.LOGGER.debug('Not loading disabled plugin {}', p[-1].name)
+ # Remove compiler extensions we don't need
+ if p[-1].details.has_option('Nikola', 'compiler') and p[-1].details.get('Nikola', 'compiler') in self.disabled_compilers:
+ bad_candidates.add(p)
+ utils.LOGGER.debug('Not loading comopiler extension {}', p[-1].name)
+ self.plugin_manager._candidates = list(set(self.plugin_manager._candidates) - bad_candidates)
+ self.plugin_manager.loadPlugins()
self._activate_plugins_of_category("SignalHandler")
@@ -709,30 +796,28 @@ class Nikola(object):
self._activate_plugins_of_category("LateTask")
self._activate_plugins_of_category("TaskMultiplier")
- # Store raw compilers for internal use (need a copy for that)
- self.config['_COMPILERS_RAW'] = {}
- for k, v in self.config['COMPILERS'].items():
- self.config['_COMPILERS_RAW'][k] = list(v)
-
- compilers = defaultdict(set)
- # Also add aliases for combinations with TRANSLATIONS_PATTERN
- for compiler, exts in self.config['COMPILERS'].items():
- for ext in exts:
- compilers[compiler].add(ext)
- for lang in self.config['TRANSLATIONS'].keys():
- candidate = utils.get_translation_candidate(self.config, "f" + ext, lang)
- compilers[compiler].add(candidate)
-
- # Avoid redundant compilers
- for k, v in compilers.items():
- self.config['COMPILERS'][k] = sorted(list(v))
-
# Activate all required compiler plugins
+ self.compiler_extensions = self._activate_plugins_of_category("CompilerExtension")
for plugin_info in self.plugin_manager.getPluginsOfCategory("PageCompiler"):
if plugin_info.name in self.config["COMPILERS"].keys():
self.plugin_manager.activatePluginByName(plugin_info.name)
plugin_info.plugin_object.set_site(self)
+ # Load compiler plugins
+ self.compilers = {}
+ self.inverse_compilers = {}
+
+ for plugin_info in self.plugin_manager.getPluginsOfCategory(
+ "PageCompiler"):
+ self.compilers[plugin_info.name] = \
+ plugin_info.plugin_object
+
+ self._activate_plugins_of_category("ConfigPlugin")
+
+ signal('configured').send(self)
+
+ def _set_global_context(self):
+ """Create global context from configuration."""
self._GLOBAL_CONTEXT['url_type'] = self.config['URL_TYPE']
self._GLOBAL_CONTEXT['timezone'] = self.tzinfo
self._GLOBAL_CONTEXT['_link'] = self.link
@@ -801,41 +886,21 @@ class Nikola(object):
self._GLOBAL_CONTEXT['hidden_categories'] = self.config.get('HIDDEN_CATEGORIES')
self._GLOBAL_CONTEXT['url_replacer'] = self.url_replacer
- # IPython theme configuration. If a website can potentially have ipynb
- # posts (as determined by checking POSTS/PAGES against ipynb
- # extensions), we should enable the IPython CSS (leaving that up to the
- # theme itself).
+ # IPython theme configuration. If a website has ipynb enabled in post_pages
+ # we should enable the IPython CSS (leaving that up to the theme itself).
- self._GLOBAL_CONTEXT['needs_ipython_css'] = False
- for i in self.config['post_pages']:
- if os.path.splitext(i[0])[1] in self.config['COMPILERS'].get('ipynb', []):
- self._GLOBAL_CONTEXT['needs_ipython_css'] = True
+ self._GLOBAL_CONTEXT['needs_ipython_css'] = 'ipynb' in self.config['COMPILERS']
self._GLOBAL_CONTEXT.update(self.config.get('GLOBAL_CONTEXT', {}))
- # Load compiler plugins
- self.compilers = {}
- self.inverse_compilers = {}
-
- for plugin_info in self.plugin_manager.getPluginsOfCategory(
- "PageCompiler"):
- self.compilers[plugin_info.name] = \
- plugin_info.plugin_object
-
- self._activate_plugins_of_category("ConfigPlugin")
-
- signal('configured').send(self)
-
def _activate_plugins_of_category(self, category):
"""Activate all the plugins of a given category and return them."""
+ # this code duplicated in tests/base.py
plugins = []
for plugin_info in self.plugin_manager.getPluginsOfCategory(category):
- if plugin_info.name in self.config.get('DISABLED_PLUGINS'):
- self.plugin_manager.removePluginFromCategory(plugin_info, category)
- else:
- self.plugin_manager.activatePluginByName(plugin_info.name)
- plugin_info.plugin_object.set_site(self)
- plugins.append(plugin_info)
+ self.plugin_manager.activatePluginByName(plugin_info.name)
+ plugin_info.plugin_object.set_site(self)
+ plugins.append(plugin_info)
return plugins
def _get_themes(self):
@@ -843,8 +908,8 @@ class Nikola(object):
try:
self._THEMES = utils.get_theme_chain(self.config['THEME'])
except Exception:
- utils.LOGGER.warn('''Cannot load theme "{0}", using 'bootstrap' instead.'''.format(self.config['THEME']))
- self.config['THEME'] = 'bootstrap'
+ utils.LOGGER.warn('''Cannot load theme "{0}", using 'bootstrap3' instead.'''.format(self.config['THEME']))
+ self.config['THEME'] = 'bootstrap3'
return self._get_themes()
# Check consistency of USE_CDN and the current THEME (Issue #386)
if self.config['USE_CDN'] and self.config['USE_CDN_WARNING']:
@@ -909,11 +974,11 @@ class Nikola(object):
template_system = property(_get_template_system)
def get_compiler(self, source_name):
- """Get the correct compiler for a post from `conf.COMPILERS`
+ """Get the correct compiler for a post from `conf.COMPILERS`.
+
To make things easier for users, the mapping in conf.py is
- compiler->[extensions], although this is less convenient for us. The
- majority of this function is reversing that dictionary and error
- checking.
+ compiler->[extensions], although this is less convenient for us.
+ The majority of this function is reversing that dictionary and error checking.
"""
ext = os.path.splitext(source_name)[1]
try:
@@ -925,9 +990,9 @@ class Nikola(object):
len([ext_ for ext_ in exts if source_name.endswith(ext_)]) > 0]
if len(langs) != 1:
if len(set(langs)) > 1:
- exit("Your file extension->compiler definition is"
- "ambiguous.\nPlease remove one of the file extensions"
- "from 'COMPILERS' in conf.py\n(The error is in"
+ exit("Your file extension->compiler definition is "
+ "ambiguous.\nPlease remove one of the file extensions "
+ "from 'COMPILERS' in conf.py\n(The error is in "
"one of {0})".format(', '.join(langs)))
elif len(langs) > 1:
langs = langs[:1]
@@ -988,13 +1053,26 @@ class Nikola(object):
utils.makedirs(os.path.dirname(output_name))
parser = lxml.html.HTMLParser(remove_blank_text=True)
doc = lxml.html.document_fromstring(data, parser)
- doc.rewrite_links(lambda dst: self.url_replacer(src, dst, context['lang']))
+ self.rewrite_links(doc, src, context['lang'])
data = b'<!DOCTYPE html>\n' + lxml.html.tostring(doc, encoding='utf8', method='html', pretty_print=True)
with open(output_name, "wb+") as post_file:
post_file.write(data)
+ def rewrite_links(self, doc, src, lang):
+ """Replace links in document to point to the right places."""
+ # First let lxml replace most of them
+ doc.rewrite_links(lambda dst: self.url_replacer(src, dst, lang), resolve_base_href=False)
+
+ # lxml ignores srcset in img and source elements, so do that by hand
+ objs = list(doc.findall('*//img')) + list(doc.findall('*//source'))
+ for obj in objs:
+ if 'srcset' in obj.attrib:
+ urls = [u.strip() for u in obj.attrib['srcset'].split(',')]
+ urls = [self.url_replacer(src, dst, lang) for dst in urls]
+ obj.set('srcset', ', '.join(urls))
+
def url_replacer(self, src, dst, lang=None, url_type=None):
- """URL mangler.
+ """Mangle URLs.
* Replaces link:// URLs with real links
* Makes dst relative to src
@@ -1111,14 +1189,13 @@ class Nikola(object):
def generic_rss_renderer(self, lang, title, link, description, timeline, output_path,
rss_teasers, rss_plain, feed_length=10, feed_url=None,
enclosure=_enclosure, rss_links_append_query=None):
-
- """Takes all necessary data, and renders a RSS feed in output_path."""
+ """Take all necessary data, and render a RSS feed in output_path."""
rss_obj = utils.ExtendedRSS2(
title=title,
link=link,
description=description,
lastBuildDate=datetime.datetime.utcnow(),
- generator='http://getnikola.com/',
+ generator='https://getnikola.com/',
language=lang
)
@@ -1193,7 +1270,7 @@ class Nikola(object):
rss_file.write(data)
def path(self, kind, name, lang=None, is_link=False):
- """Build the path to a certain kind of page.
+ r"""Build the path to a certain kind of page.
These are mostly defined by plugins by registering via the
register_path_handler method, except for slug, post_path, root
@@ -1223,9 +1300,8 @@ class Nikola(object):
(ex: "/archive/index.html").
If is_link is False, the path is relative to output and uses the
platform's separator.
- (ex: "archive\\index.html")
+ (ex: "archive\index.html")
"""
-
if lang is None:
lang = utils.LocaleBorg().current_lang
@@ -1248,13 +1324,13 @@ class Nikola(object):
return ""
def post_path(self, name, lang):
- """post_path path handler"""
+ """Handle post_path paths."""
return [_f for _f in [self.config['TRANSLATIONS'][lang],
os.path.dirname(name),
self.config['INDEX_FILE']] if _f]
def root_path(self, name, lang):
- """root_path path handler"""
+ """Handle root_path paths."""
d = self.config['TRANSLATIONS'][lang]
if d:
return [d, '']
@@ -1262,7 +1338,7 @@ class Nikola(object):
return []
def slug_path(self, name, lang):
- """slug path handler"""
+ """Handle slug paths."""
results = [p for p in self.timeline if p.meta('slug') == name]
if not results:
utils.LOGGER.warning("Cannot resolve path request for slug: {0}".format(name))
@@ -1272,7 +1348,7 @@ class Nikola(object):
return [_f for _f in results[0].permalink(lang).split('/') if _f]
def filename_path(self, name, lang):
- """filename path handler"""
+ """Handle filename paths."""
results = [p for p in self.timeline if p.source_path == name]
if not results:
utils.LOGGER.warning("Cannot resolve path request for filename: {0}".format(name))
@@ -1282,15 +1358,18 @@ class Nikola(object):
return [_f for _f in results[0].permalink(lang).split('/') if _f]
def register_path_handler(self, kind, f):
+ """Register a path handler."""
if kind in self.path_handlers:
utils.LOGGER.warning('Conflicting path handlers for kind: {0}'.format(kind))
else:
self.path_handlers[kind] = f
def link(self, *args):
+ """Create a link."""
return self.path(*args, is_link=True)
def abs_link(self, dst, protocol_relative=False):
+ """Get an absolute link."""
# Normalize
if dst: # Mako templates and empty strings evaluate to False
dst = urljoin(self.config['BASE_URL'], dst.lstrip('/'))
@@ -1302,6 +1381,7 @@ class Nikola(object):
return url
def rel_link(self, src, dst):
+ """Get a relative link."""
# Normalize
src = urljoin(self.config['BASE_URL'], src)
dst = urljoin(src, dst)
@@ -1326,8 +1406,7 @@ class Nikola(object):
return '/'.join(['..'] * (len(src_elems) - i - 1) + dst_elems[i:])
def file_exists(self, path, not_empty=False):
- """Returns True if the file exists. If not_empty is True,
- it also has to be not empty."""
+ """Check if the file exists. If not_empty is True, it also must not be empty."""
exists = os.path.exists(path)
if exists and not_empty:
exists = os.stat(path).st_size > 0
@@ -1341,8 +1420,9 @@ class Nikola(object):
return task
def gen_tasks(self, name, plugin_category, doc=''):
-
+ """Generate tasks."""
def flatten(task):
+ """Flatten lists of tasks."""
if isinstance(task, dict):
yield task
else:
@@ -1377,6 +1457,7 @@ class Nikola(object):
}
def parse_category_name(self, category_name):
+ """Parse a category name into a hierarchy."""
if self.config['CATEGORY_ALLOW_HIERARCHIES']:
try:
return utils.parse_escaped_hierarchical_category_name(category_name)
@@ -1387,12 +1468,14 @@ class Nikola(object):
return [category_name] if len(category_name) > 0 else []
def category_path_to_category_name(self, category_path):
+ """Translate a category path to a category name."""
if self.config['CATEGORY_ALLOW_HIERARCHIES']:
return utils.join_hierarchical_category_path(category_path)
else:
return ''.join(category_path)
def _add_post_to_category(self, post, category_name):
+ """Add a post to a category."""
category_path = self.parse_category_name(category_name)
current_path = []
current_subtree = self.category_hierarchy
@@ -1404,10 +1487,12 @@ class Nikola(object):
self.posts_per_category[self.category_path_to_category_name(current_path)].append(post)
def _sort_category_hierarchy(self):
+ """Sort category hierarchy."""
# First create a hierarchy of TreeNodes
self.category_hierarchy_lookup = {}
def create_hierarchy(cat_hierarchy, parent=None):
+ """Create category hierarchy."""
result = []
for name, children in cat_hierarchy.items():
node = utils.TreeNode(name, parent)
@@ -1426,7 +1511,7 @@ class Nikola(object):
def scan_posts(self, really=False, ignore_quit=False, quiet=False):
"""Scan all the posts.
- Ignoring quiet.
+ The `quiet` option is ignored.
"""
if self._scanned and not really:
return
@@ -1438,6 +1523,7 @@ class Nikola(object):
self.posts_per_month = defaultdict(list)
self.posts_per_tag = defaultdict(list)
self.posts_per_category = defaultdict(list)
+ self.tags_per_language = defaultdict(list)
self.category_hierarchy = {}
self.post_per_file = {}
self.timeline = []
@@ -1470,6 +1556,8 @@ class Nikola(object):
else:
slugged_tags.add(utils.slugify(tag, force=True))
self.posts_per_tag[tag].append(post)
+ for lang in self.config['TRANSLATIONS'].keys():
+ self.tags_per_language[lang].extend(post.tags_for_language(lang))
self._add_post_to_category(post, post.meta('category'))
if post.is_post:
@@ -1495,6 +1583,8 @@ class Nikola(object):
quit = True
self.post_per_file[dest] = post
self.post_per_file[src_dest] = post
+ # deduplicate tags_per_language
+ self.tags_per_language[lang] = list(set(self.tags_per_language[lang]))
# Sort everything.
@@ -1514,9 +1604,9 @@ class Nikola(object):
sys.exit(1)
signal('scanned').send(self)
- def generic_page_renderer(self, lang, post, filters):
+ def generic_page_renderer(self, lang, post, filters, context=None):
"""Render post fragments to final HTML pages."""
- context = {}
+ 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'))
@@ -1526,6 +1616,8 @@ class Nikola(object):
context['title'] = post.title(lang)
context['description'] = post.description(lang)
context['permalink'] = post.permalink(lang)
+ if 'pagekind' not in context:
+ context['pagekind'] = ['generic_page']
if post.use_in_feeds:
context['enable_comments'] = True
else:
@@ -1557,7 +1649,7 @@ class Nikola(object):
task = {
'name': os.path.normpath(output_name),
- 'file_dep': deps,
+ 'file_dep': sorted(deps),
'targets': [output_name],
'actions': [(self.render_template, [post.template_name,
output_name, context])],
@@ -1569,9 +1661,9 @@ class Nikola(object):
def generic_post_list_renderer(self, lang, posts, output_name,
template_name, filters, extra_context):
- """Renders pages with lists of posts."""
-
- deps = self.template_system.template_deps(template_name)
+ """Render pages with lists of posts."""
+ deps = []
+ deps += self.template_system.template_deps(template_name)
uptodate_deps = []
for post in posts:
deps += post.deps(lang)
@@ -1600,7 +1692,7 @@ class Nikola(object):
task = {
'name': os.path.normpath(output_name),
'targets': [output_name],
- 'file_dep': deps,
+ 'file_dep': sorted(deps),
'actions': [(self.render_template, [template_name, output_name,
context])],
'clean': True,
@@ -1611,9 +1703,10 @@ class Nikola(object):
def atom_feed_renderer(self, lang, posts, output_path, filters,
extra_context):
- """Renders Atom feeds and archives with lists of posts. Feeds are
- considered archives when no future updates to them are expected"""
+ """Render Atom feeds and archives with lists of posts.
+ Feeds are considered archives when no future updates to them are expected.
+ """
def atom_link(link_rel, link_type, link_href):
link = lxml.etree.Element("link")
link.set("rel", link_rel)
@@ -1662,7 +1755,7 @@ class Nikola(object):
feed_id = lxml.etree.SubElement(feed_root, "id")
feed_id.text = self.abs_link(context["feedlink"])
feed_updated = lxml.etree.SubElement(feed_root, "updated")
- feed_updated.text = datetime.datetime.now(tz=dateutil.tz.tzutc()).replace(microsecond=0).isoformat()
+ feed_updated.text = post.formatted_date('webiso', datetime.datetime.now(tz=dateutil.tz.tzutc()))
feed_author = lxml.etree.SubElement(feed_root, "author")
feed_author_name = lxml.etree.SubElement(feed_author, "name")
feed_author_name.text = self.config["BLOG_AUTHOR"](lang)
@@ -1726,9 +1819,9 @@ class Nikola(object):
entry_id = lxml.etree.SubElement(entry_root, "id")
entry_id.text = post.permalink(lang, absolute=True)
entry_updated = lxml.etree.SubElement(entry_root, "updated")
- entry_updated.text = post.updated.isoformat()
+ entry_updated.text = post.formatted_updated('webiso')
entry_published = lxml.etree.SubElement(entry_root, "published")
- entry_published.text = post.date.isoformat()
+ entry_published.text = post.formatted_date('webiso')
entry_author = lxml.etree.SubElement(entry_root, "author")
entry_author_name = lxml.etree.SubElement(entry_author, "name")
entry_author_name.text = post.author(lang)
@@ -1757,7 +1850,7 @@ class Nikola(object):
atom_file.write(data)
def generic_index_renderer(self, lang, posts, indexes_title, template_name, context_source, kw, basename, page_link, page_path, additional_dependencies=[]):
- """Creates an index page.
+ """Create an index page.
lang: The language
posts: A list of posts
@@ -1812,6 +1905,8 @@ class Nikola(object):
num_pages = len(lists)
for i, post_list in enumerate(lists):
context = context_source.copy()
+ if 'pagekind' not in context:
+ context['pagekind'] = ['index']
ipages_i = utils.get_displayed_page_number(i, num_pages, self)
if kw["indexes_pages"]:
indexes_pages = kw["indexes_pages"] % ipages_i
@@ -1885,8 +1980,8 @@ class Nikola(object):
context["feedpagecount"] = num_pages
atom_task = {
"basename": basename,
- "file_dep": [output_name],
"name": atom_output_name,
+ "file_dep": sorted([_.base_path for _ in post_list]),
"targets": [atom_output_name],
"actions": [(self.atom_feed_renderer,
(lang,
@@ -1913,11 +2008,12 @@ class Nikola(object):
}, kw["filters"])
def __repr__(self):
+ """Representation of a Nikola site."""
return '<Nikola Site: {0!r}>'.format(self.config['BLOG_TITLE']())
def sanitized_locales(locale_fallback, locale_default, locales, translations):
- """Sanitizes all locales availble into a nikola session
+ """Sanitize all locales availble in Nikola.
There will be one locale for each language in translations.
@@ -1994,10 +2090,7 @@ def sanitized_locales(locale_fallback, locale_default, locales, translations):
def is_valid_locale(locale_n):
- """True if locale_n is acceptable for locale.setlocale
-
- for py2x compat locale_n should be of type str
- """
+ """Check if locale (type str) is valid."""
try:
locale.setlocale(locale.LC_ALL, locale_n)
return True
@@ -2006,7 +2099,7 @@ def is_valid_locale(locale_n):
def valid_locale_fallback(desired_locale=None):
- """returns a default fallback_locale, a string that locale.setlocale will accept
+ """Provide a default fallback_locale, a string that locale.setlocale will accept.
If desired_locale is provided must be of type str for py2x compatibility
"""
@@ -2032,13 +2125,15 @@ def valid_locale_fallback(desired_locale=None):
def guess_locale_from_lang_windows(lang):
- locale_n = str(_windows_locale_guesses.get(lang, None))
+ """Guess a locale, basing on Windows language."""
+ locale_n = str(LEGAL_VALUES['_WINDOWS_LOCALE_GUESSES'].get(lang, None))
if not is_valid_locale(locale_n):
locale_n = None
return locale_n
def guess_locale_from_lang_posix(lang):
+ """Guess a locale, basing on POSIX system language."""
# compatibility v6.0.4
if is_valid_locale(str(lang)):
locale_n = str(lang)
@@ -2065,28 +2160,3 @@ def workaround_empty_LC_ALL_posix():
os.environ['LC_ALL'] = lc_time
except Exception:
pass
-
-
-_windows_locale_guesses = {
- # some languages may need that the appropiate Microsoft's Language Pack
- # be instaled; the 'str' bit will be added in the guess function
- "bg": "Bulgarian",
- "ca": "Catalan",
- "de": "German",
- "el": "Greek",
- "en": "English",
- "eo": "Esperanto",
- "es": "Spanish",
- "fa": "Farsi", # Persian
- "fr": "French",
- "hr": "Croatian",
- "it": "Italian",
- "jp": "Japanese",
- "nl": "Dutch",
- "pl": "Polish",
- "pt_br": "Portuguese_Brazil",
- "ru": "Russian",
- "sl_si": "Slovenian",
- "tr_tr": "Turkish",
- "zh_cn": "Chinese_China", # Chinese (Simplified)
-}