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