From 85fac3de6d8cb56ebbcd5cbf9d6f58c832f6658f Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 10:18:06 +0200 Subject: [PATCH 01/74] Adding taxonomy plugin type and basic handling. --- nikola/nikola.py | 3 + nikola/plugin_categories.py | 123 ++++++++++ .../plugins/misc/taxonomies_classifier.plugin | 12 + nikola/plugins/misc/taxonomies_classifier.py | 210 ++++++++++++++++ nikola/plugins/task/taxonomies.plugin | 12 + nikola/plugins/task/taxonomies.py | 227 ++++++++++++++++++ 6 files changed, 587 insertions(+) create mode 100644 nikola/plugins/misc/taxonomies_classifier.plugin create mode 100644 nikola/plugins/misc/taxonomies_classifier.py create mode 100644 nikola/plugins/task/taxonomies.plugin create mode 100644 nikola/plugins/task/taxonomies.py diff --git a/nikola/nikola.py b/nikola/nikola.py index 46813c4b7a..e80236adc3 100644 --- a/nikola/nikola.py +++ b/nikola/nikola.py @@ -73,6 +73,7 @@ SignalHandler, ConfigPlugin, PostScanner, + Taxonomy, ) if DEBUG: @@ -948,6 +949,7 @@ def init_plugins(self, commands_only=False, load_all=False): "SignalHandler": SignalHandler, "ConfigPlugin": ConfigPlugin, "PostScanner": PostScanner, + "Taxonomy": Taxonomy, }) self.plugin_manager.getPluginLocator().setPluginInfoExtension('plugin') extra_plugins_dirs = self.config['EXTRA_PLUGINS_DIRS'] @@ -1019,6 +1021,7 @@ def plugin_position_in_places(plugin): self.plugin_manager.loadPlugins() + self._activate_plugins_of_category("Taxonomy") self._activate_plugins_of_category("SignalHandler") # Emit signal for SignalHandlers which need to start running immediately. diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index db36c405c0..8c87fda8b6 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -49,6 +49,7 @@ 'SignalHandler', 'ConfigPlugin', 'PostScanner', + 'Taxonomy', ) @@ -451,3 +452,125 @@ def import_file(self): def save_post(self): """Save a post to disk.""" raise NotImplementedError() + + +class Taxonomy(BasePlugin): + """Taxonomy for posts. + + A taxonomy plugin allows to classify posts (see #2107) by + classification strings. + """ + + name = "dummy_taxonomy" + + # Adjust the following values in your plugin! + + # The classification name to be used for path handlers. + classification_name = "taxonomy" + # The classification name to be used when storing the classification + # in the metadata. + metadata_name = "taxonomy" + # If True, there can be more than one classification per post; in that case, + # the classification data in the metadata is stored as a list. If False, + # the classification data in the metadata is stored as a string, or None + # when no classification is given. + more_than_one_classifications_per_post = False + # Whether the classification has a hierarchy. + has_hierarchy = False + # If True, the list for a classification includes all posts with a + # sub-classification (in case has_hierarchy is True). + include_posts_from_subhierarchies = False + # Whether to show the posts for one classification as an index or + # as a post list. + show_list_as_index = False + # The template to use for the post list for one classification. + # Set to none to avoid generating overviews. + template_for_list_of_one_classification = "tagindex.tmpl" + # The template to use for the classification overview page. + template_for_classification_overview = "list.tmpl" + # Whether this classification applies to posts. + apply_to_posts = True + # Whether this classification applies to pages. + apply_to_pages = False + # The minimum number of posts a classification must have to be listed in + # the overview. + minimum_post_count_per_classification_in_overview = 1 + # Whether post lists resp. indexes should be created for empty + # classifications. + omit_empty_classifications = False + # Whether to include all classifications for all languages in every + # language, or only the classifications for one language in its language's + # pages. + also_create_classifications_from_other_languages = True + + def classify(self, post, lang): + """Classifies the given post for the given language. + + Must return a list or tuple of strings.""" + raise NotImplementedError() + + def sort_posts(self, posts): + """Sorts the given list of posts.""" + pass + + def get_list_path(self, lang): + """A path handler for the list of all classifications. + + The last element in the returned path must have no extension, and the + PRETTY_URLS config must be ignored. The return value will be modified + based on the PRETTY_URLS and INDEX_FILE settings.""" + raise NotImplementedError() + + def get_path(self, classification, lang): + """A path handler for the given classification. + + The last element in the returned path must have no extension, and the + PRETTY_URLS config must be ignored. The return value will be modified + based on the PRETTY_URLS and INDEX_FILE settings. + + For hierarchical taxonomies, the result of extract_hierarchy is provided. + For non-hierarchical taxonomies, the classification string itself is provided.""" + raise NotImplementedError() + + def extract_hierarchy(self, classification): + """Given a classification, return a list of parts in the hierarchy. + + For non-hierarchical taxonomies, it usually suffices to return + `[classification]`.""" + return [classification] + + def recombine_classification_from_hierarchy(self, hierarchy): + """Given a list of parts in the hierarchy, return the classification string. + + For non-hierarchical taxonomies, it usually suffices to return hierarchy[0].""" + return hierarchy[0] + + def provide_list_context_and_uptodate(self): + """Provides data for the context and the uptodate list for the list of all classifiations. + + Must return a tuple of two dicts. The first is merged into the page's context, + the second will be put into the uptodate list of all generated tasks. + + Context must contain `title`.""" + raise NotImplementedError() + + def provide_context_and_uptodate(self, classification): + """Provides data for the context and the uptodate list for the list of the given classifiation. + + Must return a tuple of two dicts. The first is merged into the page's context, + the second will be put into the uptodate list of all generated tasks. + + Context must contain `title`, which should be something like 'Posts about ', + and `classification_title`, which should be related to the classification string.""" + raise NotImplementedError() + + def postprocess_posts_per_classification(self, posts_per_classification_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None): + """This function can rearrange, modify or otherwise use the list of posts per classification and per language. + + For compatibility reasons, the list could be stored somewhere else as well. + + In case `has_hierarchy` is `True`, `flat_hierarchy_per_lang` is the flat + hierarchy consisting of `TreeNode` elements, and `hierarchy_lookup_per_lang` + is the corresponding hierarchy lookup mapping classification strings to + `TreeNode` objects.""" + pass diff --git a/nikola/plugins/misc/taxonomies_classifier.plugin b/nikola/plugins/misc/taxonomies_classifier.plugin new file mode 100644 index 0000000000..e7546f30a9 --- /dev/null +++ b/nikola/plugins/misc/taxonomies_classifier.plugin @@ -0,0 +1,12 @@ +[Core] +name = classify_taxonomies +module = classify_taxonomies + +[Documentation] +author = Roberto Alsina +version = 1.0 +website = https://getnikola.com/ +description = Classifies the timeline into taxonomies. + +[Nikola] +plugincategory = SignalHandler diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py new file mode 100644 index 0000000000..1791698589 --- /dev/null +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2012-2016 Roberto Alsina and others. + +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the +# Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice +# shall be included in all copies or substantial portions of +# the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +"""Render the taxonomy overviews, classification pages and feeds.""" + +from __future__ import unicode_literals +import blinker +import natsort +import os +import sys + +from collections import defaultdict + +from nikola.plugin_categories import SignalHandler +from nikola import utils + + +class TaxonomiesClassifier(SignalHandler): + """Render the tag/category pages and feeds.""" + + name = "render_taxonomies" + + def _do_classification(self): + taxonomies = self.site.plugin_manager.getPluginsOfCategory('Taxonomy') + self.site.posts_per_classification = {} + for taxonomy in taxonomies: + if taxonomy.classification_name in self.site.posts_per_classification: + raise Exception("Found more than one taxonomy with classification name '{}'!".format(taxonomy.classification_name)) + self.site.posts_per_classification[taxonomy.classification_name] = { + lang: defaultdict(set) for lang in self.config['TRANSLATIONS'].keys() + } + + # Classify posts + for post in self.timeline: + for taxonomy in taxonomies: + if taxonomy.apply_to_posts if post.is_post else taxonomy.apply_to_pages: + classifications = {} + for lang in self.config['TRANSLATIONS'].keys(): + # Extract classifications for this language + classifications[lang] = taxonomy.classify(post, lang) + assert taxonomy.more_than_one_classifications_per_post or len(classifications[lang]) <= 1 + # Store in metadata + if taxonomy.more_than_one_classifications_per_post: + post.meta[lang][taxonomy.metadata_name] = classifications[lang] + else: + post.meta[lang][taxonomy.metadata_name] = classifications[lang][0] if len(classifications[lang]) > 0 else None + # Add post to sets + for classification in classifications[lang]: + while classification: + self.site.posts_per_classification[taxonomy.classification_name][lang][classification].add(post) + if not taxonomy.include_posts_from_subhierarchies or not taxonomy.has_hierarchy: + break + classification = taxonomy.recombine_classification_from_hierarchy(taxonomy.extract_hierarchy(classification)[:-1]) + + # Check for valid paths and for collisions + taxonomy_outputs = {lang: dict() for lang in self.config['TRANSLATIONS'].keys()} + quit = False + for taxonomy in taxonomies: + # Check for collisions (per language) + for lang in self.config['TRANSLATIONS'].keys(): + for tlang in self.config['TRANSLATIONS'].keys(): + if lang != tlang and not taxonomy.also_create_classifications_from_other_languages: + continue + for classification, posts in self.site.posts_per_classification[taxonomy.classification_name][tlang].items(): + # Obtain path as tuple + if taxonomy.has_hierarchy: + path = taxonomy.get_path(taxonomy.extract_hierarchy(classification), lang) + else: + path = taxonomy.get_path(classification, lang) + path = tuple(path) + # Check that path is OK + for path_element in path: + if len(path_element) == 0: + utils.LOGGER.error("{0} {1} yields invalid path '{2}'!".format(taxonomy.classification_name.title(), classification, '/'.join(path))) + quit = True + # Determine collisions + if path in taxonomy_outputs[lang]: + other_classification_name, other_classification, other_posts = taxonomy_outputs[lang][path] + utils.LOGGER.error('You have classifications that are too similar: {0} "{1}" and {1} "{2}"'.format( + taxonomy.classification_name, classification, other_classification_name, other_classification)) + utils.LOGGER.error('{0} {1} is used in: {1}'.format( + taxonomy.classification_name.title(), classification, ', '.join(sorted([p.source_path for p in posts])))) + utils.LOGGER.error('{0} {1} is used in: {1}'.format( + other_classification_name.title(), other_classification, ', '.join(sorted([p.source_path for p in other_posts])))) + quit = True + else: + taxonomy_outputs[lang][path] = (taxonomy.classification_name, classification, posts) + if quit: + sys.exit(1) + + # Sort everything. + self.site.flat_hierarchy_per_classification = {} + self.site.hierarchy_lookup_per_classification = {} + for taxonomy in taxonomies: + # Sort post lists + for lang, posts_per_classification in self.site.posts_per_classification[taxonomy.classification_name].items(): + # Convert sets to lists and sort them + for classification in list(posts_per_classification.keys()): + posts = list(posts_per_classification[classification]) + posts.sort(key=lambda p: + (int(p.meta('priority')) if p.meta('priority') else 0, + p.date, p.source_path)) + posts.reverse() + posts_per_classification[classification] = posts + # Create hierarchy information + if taxonomy.has_hierarchy: + self.site.flat_hierarchy_per_classification[taxonomy.classification_name] = {} + self.site.hierarchy_lookup_per_classification[taxonomy.classification_name] = {} + for lang, posts_per_classification in self.site.posts_per_classification[taxonomy.classification_name].items(): + # Compose hierarchy + hierarchy = {} + for classification in posts_per_classification.keys(): + hier = taxonomy.extract_hierarchy(classification) + node = hierarchy + for he in hier: + if he not in node: + node[he] = {} + node = node[he] + 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) + node.children = create_hierarchy(children, node) + node.classification_path = [pn.name for pn in node.get_path()] + node.classification_name = taxonomy.recombine_classification_from_hierarchy(node.classification_path) + hierarchy_lookup[node.classification_name] = node + return natsort.natsorted(result, key=lambda e: e.name, alg=natsort.ns.F | natsort.ns.IC) + + root_list = create_hierarchy(hierarchy) + flat_hierarchy = utils.flatten_tree_structure(root_list) + # Store result + self.site.flat_hierarchy_per_classification[taxonomy.classification_name][lang] = flat_hierarchy + self.site.hierarchy_lookup_per_classification[taxonomy.classification_name][lang] = hierarchy_lookup + taxonomy.postprocess_posts_per_classification(self.site.posts_per_classification[taxonomy.classification_name], + self.site.flat_hierarchy_per_classification[taxonomy.classification_name], + self.site.hierarchy_lookup_per_classification[taxonomy.classification_name]) + else: + taxonomy.postprocess_posts_per_classification(self.site.posts_per_classification[taxonomy.classification_name], flat_hierarchy, hierarchy_lookup) + + def _postprocess_path(self, path, lang, force_extension=None): + if force_extension is not None: + if len(path) == 0: + path = [os.path.splitext(self.site.config['INDEX_FILE'])[0]] + path[-1] += force_extension + elif self.site.config['PRETTY_URLS'] or len(path) == 0: + path = path + [self.site.config['INDEX_FILE']] + else: + path[-1] += '.html' + return [_f for _f in [self.site.config['TRANSLATIONS'][lang]] + path if _f] + + def _taxonomy_index_path(self, lang, taxonomy): + """Return path to the classification overview.""" + return self._postprocess_path(taxonomy.get_list_path(lang), lang) + + def _taxonomy_path(self, name, lang, taxonomy, force_extension=None): + """Return path to a classification.""" + if taxonomy.has_hirearchy: + path = taxonomy.get_path(taxonomy.extract_hierarchy(name), lang) + else: + path = taxonomy.get_path(name, lang) + return self._postprocess_path(path, lang, force_extension=force_extension) + + def _taxonomy_atom_path(self, name, lang, taxonomy): + """Return path to a classification Atom feed.""" + return self._taxonomy_path(name, lang, taxonomy, force_extension='.atom') + + def _taxonomy_rss_path(self, name, lang, taxonomy): + """Return path to a classification RSS feed.""" + return self._taxonomy_path(name, lang, taxonomy, force_extension='.xml') + + def _register_path_handlers(self, taxonomy): + self.site.register_path_handler('{0}_index'.format(taxonomy.classification_name), lambda name, lang: self._tag_index_path(lang, taxonomy)) + self.site.register_path_handler('{0}'.format(taxonomy.classification_name), lambda name, lang: self._tag_path(name, lang, taxonomy)) + self.site.register_path_handler('{0}_atom'.format(taxonomy.classification_name), lambda name, lang: self._tag_atom_path(name, lang, taxonomy)) + self.site.register_path_handler('{0}_rss'.format(taxonomy.classification_name), lambda name, lang: self._tag_rss_path(name, lang, taxonomy)) + + def set_site(self, site): + """Set site, which is a Nikola instance.""" + super(TaxonomiesClassifier, self).set_site(site) + # Add hook for after post scanning + blinker.signal("scanned").connect(self._do_classification) + # Register path handlers + for taxonomy in self.plugin_manager.getPluginsOfCategory('Taxonomy'): + self._register_path_handlers(taxonomy) diff --git a/nikola/plugins/task/taxonomies.plugin b/nikola/plugins/task/taxonomies.plugin new file mode 100644 index 0000000000..5dbb4f13de --- /dev/null +++ b/nikola/plugins/task/taxonomies.plugin @@ -0,0 +1,12 @@ +[Core] +name = render_taxonomies +module = taxonomies + +[Documentation] +author = Roberto Alsina +version = 1.0 +website = https://getnikola.com/ +description = Render the taxonomy overviews, classification pages and feeds. + +[Nikola] +plugincategory = Task diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py new file mode 100644 index 0000000000..5615f987ec --- /dev/null +++ b/nikola/plugins/task/taxonomies.py @@ -0,0 +1,227 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2012-2016 Roberto Alsina and others. + +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the +# Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice +# shall be included in all copies or substantial portions of +# the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +"""Render the taxonomy overviews, classification pages and feeds.""" + +from __future__ import unicode_literals +import os +import natsort +from copy import copy +try: + from urlparse import urljoin +except ImportError: + from urllib.parse import urljoin # NOQA + +from nikola.plugin_categories import Task +from nikola import utils +from nikola.nikola import _enclosure + + +class RenderTaxonomies(Task): + """Render the tag/category pages and feeds.""" + + name = "render_taxonomies" + + def _generate_classification_overview(self, taxonomy, lang): + """Create a global "all your tags/categories" page for each language.""" + context, kw = taxonomy.provide_list_context_and_uptodate(lang) + + context = copy(context) + kw = copy(kw) + kw["minimum_post_count"] = taxonomy.minimum_post_count_per_classification_in_overview + + # Collect all relevant classifications + if taxonomy.has_hierarchy: + classifications = [cat.classification_name for cat in self.site.flat_hierarchy_per_classification[taxonomy.classification_name][lang]] + else: + classifications = natsort.natsorted([tag for tag, posts in self.site.posts_per_classification[taxonomy.classification_name][lang].items() + if len(posts) >= kw["minimum_post_count"]], + alg=natsort.ns.F | natsort.ns.IC) + + # Set up classifications in context + context[taxonomy.metadata_name] = classifications + context["items"] = [(classification, self.site.link(taxonomy.classification_name, classification, lang)) for classification in classifications] + context["has_hierarchy"] = taxonomy.has_hierarchy + if taxonomy.has_hierarchy: + context["hierarchy"] = [(node.name, node.classification_name, node.classification_path, + self.site.link(taxonomy.classification_name, node.classification_name, lang), + node.indent_levels, node.indent_change_before, + node.indent_change_after) + for node in self.site.flat_hierarchy_per_classification[taxonomy.classification_name][lang]] + + # Prepare rendering + context["permalink"] = self.site.link("{}_index".format(taxonomy.classification_name), None, lang) + context["pagekind"] = ["list", "tags_page"] + output_name = os.path.join(kw['output_folder'], self.site.path('{}_index'.format(taxonomy.classification_name), None, lang)) + task = self.site.generic_post_list_renderer( + lang, + [], + output_name, + taxonomy.template_for_classification_overview, + kw['filters'], + context, + ) + task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.taxonomies:page')] + task['basename'] = str(self.name) + yield task + + def _generate_classification_page_as_rss(self, taxonomy, classification, filtered_posts, title, kw, lang): + """Create a RSS feed for a single classification in a given language.""" + kind = taxonomy.classification_name + # Render RSS + output_name = os.path.normpath(os.path.join(kw['output_folder'], self.site.path(kind + "_rss", classification, lang))) + feed_url = urljoin(self.site.config['BASE_URL'], self.site.link(kind + "_rss", classification, lang).lstrip('/')) + deps = [] + deps_uptodate = [] + for post in filtered_posts: + deps += post.deps(lang) + deps_uptodate += post.deps_uptodate(lang) + task = { + 'basename': str(self.name), + 'name': output_name, + 'file_dep': deps, + 'targets': [output_name], + 'actions': [(utils.generic_rss_renderer, + (lang, "{0} ({1})".format(kw["blog_title"](lang), title), + kw["site_url"], None, filtered_posts, + output_name, kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], + feed_url, _enclosure, kw["feed_link_append_query"]))], + 'clean': True, + 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.taxonomies:rss')] + deps_uptodate, + 'task_dep': ['render_posts'], + } + return utils.apply_filters(task, kw['filters']) + + def _generate_classification_page_as_index(self, taxonomy, classification, filtered_posts, context, kw, lang): + """Render a sort of index page collection using only this classification's posts.""" + kind = taxonomy.classification_name + + def page_link(i, displayed_i, num_pages, force_addition, extension=None): + feed = "{}_atom" if extension == ".atom" else "{}" + return utils.adjust_name_for_index_link(self.site.link(feed.format(kind), classification, lang), i, displayed_i, lang, self.site, force_addition, extension) + + def page_path(i, displayed_i, num_pages, force_addition, extension=None): + feed = "{}_atom" if extension == ".atom" else "{}" + return utils.adjust_name_for_index_path(self.site.path(feed.format(kind), classification, lang), i, displayed_i, lang, self.site, force_addition, extension) + + context = copy(context) + if kw["generate_rss"]: + # On a tag page, the feeds include the tag's feeds + rss_link = ("""""".format( + taxonomy.classification_name, context['classification_title'], lang, self.site.link('{}_rss'.format(kind), classification, lang))) + context['rss_link'] = rss_link + context["pagekind"] = ["index", "tag_page"] + template_name = taxonomy.template_for_list_of_one_classification + + yield self.site.generic_index_renderer(lang, filtered_posts, context['title'], template_name, context, kw, str(self.name), page_link, page_path) + + def _generate_classification_page_as_list_atom(self, taxonomy, classification, filtered_posts, context, kw, lang): + """Generate atom feeds for classification lists.""" + kind = taxonomy.classification_name + context = copy(context) + context['feedlink'] = self.site.abs_link(self.site.path('{}_atom'.format(kind), classification, lang)) + feed_path = os.path.join(kw['output_folder'], self.site.path('{}_atom'.format(kind), classification, lang)) + + task = { + 'basename': str(self.name), + 'name': feed_path, + 'targets': [feed_path], + 'actions': [(self.site.atom_feed_renderer, (lang, filtered_posts, feed_path, kw['filters'], context))], + 'clean': True, + 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.taxonomies:atom')], + 'task_dep': ['render_posts'], + } + return task + + def _generate_classification_page_as_list(self, taxonomy, classification, filtered_posts, context, kw, lang): + """Render a single flat link list with this classification's posts.""" + kind = taxonomy.classification_name + template_name = "tag.tmpl" + output_name = os.path.join(kw['output_folder'], self.site.path(kind, classification, lang)) + context = copy(context) + context["lang"] = lang + context["posts"] = filtered_posts + context["permalink"] = self.site.link(kind, classification, lang) + context["kind"] = kind + context["pagekind"] = ["list", "tag_page"] + task = self.site.generic_post_list_renderer(lang, filtered_posts, output_name, template_name, kw['filters'], context) + task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.taxonomies:list')] + task['basename'] = str(self.name) + yield task + + if self.site.config['GENERATE_ATOM']: + yield self._generate_classification_page_as_list_atom(kind, taxonomy, classification, filtered_posts, context, kw, lang) + + def _generate_classification_page(self, taxonomy, classification, post_list, lang): + """Render index or post list and associated feeds per classification.""" + # Filter list + if self.site.config["SHOW_UNTRANSLATED_POSTS"]: + filtered_posts = post_list + else: + filtered_posts = [x for x in post_list if x.is_translation_available(lang)] + if len(filtered_posts) == 0 and taxonomy.omit_empty_classifications: + return + # Get data + context, kw = taxonomy.provide_context_and_uptodate(classification) + kw = copy(kw) + kw["blog_title"] = self.site.config["BLOG_TITLE"] + kw["feed_teasers"] = self.site.config["FEED_TEASERS"] + kw["feed_plain"] = self.site.config["FEED_PLAIN"] + kw["feed_link_append_query"] = self.site.config["FEED_LINKS_APPEND_QUERY"] + kw["feed_length"] = self.site.config['FEED_LENGTH'] + # Generate RSS feed + if kw["generate_rss"]: + yield self._generate_classification_page_as_rss(taxonomy, classification, filtered_posts, context['title'], kw, lang) + # Render HTML + if taxonomy.show_list_as_index: + yield self._generate_classification_page_as_index(taxonomy, classification, filtered_posts, context, kw, lang) + else: + yield self._generate_classification_page_as_list(taxonomy, classification, filtered_posts, context, kw, lang) + + def gen_tasks(self): + """Render the tag pages and feeds.""" + + self.site.scan_posts() + yield self.group_task() + + for taxonomy in self.site.plugin_manager.getPluginsOfCategory('Taxonomy'): + for lang in self.site.config["TRANSLATIONS"]: + # Generate list of classifications (i.e. classification overview) + if taxonomy.template_for_list_of_one_classification is not None: + for task in self._generate_classification_overview(taxonomy, lang): + yield task + + # Generate classification lists + classifications = {} + for tlang, posts_per_classification in self.site.posts_per_classification[taxonomy.classification_name].items(): + if lang != tlang and not taxonomy.also_create_classifications_from_other_languages: + continue + classifications.update(posts_per_classification) + + # Process classifications + for classification, posts in classifications.items(): + for task in self._generate_classification_page(taxonomy, classification, posts, lang): + yield task From 1bc2a3113d9a7356218c7e78a574be52498de828 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 12:54:04 +0200 Subject: [PATCH 02/74] Making pydocstyle happy. --- nikola/plugin_categories.py | 36 +++++++++++++++++++------------ nikola/plugins/task/taxonomies.py | 1 - 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 8c87fda8b6..1b0bda002f 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -504,13 +504,14 @@ class Taxonomy(BasePlugin): also_create_classifications_from_other_languages = True def classify(self, post, lang): - """Classifies the given post for the given language. + """Classify the given post for the given language. - Must return a list or tuple of strings.""" + Must return a list or tuple of strings. + """ raise NotImplementedError() - def sort_posts(self, posts): - """Sorts the given list of posts.""" + def sort_posts(self, posts, lang): + """Sort the given list of posts.""" pass def get_list_path(self, lang): @@ -518,7 +519,8 @@ def get_list_path(self, lang): The last element in the returned path must have no extension, and the PRETTY_URLS config must be ignored. The return value will be modified - based on the PRETTY_URLS and INDEX_FILE settings.""" + based on the PRETTY_URLS and INDEX_FILE settings. + """ raise NotImplementedError() def get_path(self, classification, lang): @@ -529,48 +531,54 @@ def get_path(self, classification, lang): based on the PRETTY_URLS and INDEX_FILE settings. For hierarchical taxonomies, the result of extract_hierarchy is provided. - For non-hierarchical taxonomies, the classification string itself is provided.""" + For non-hierarchical taxonomies, the classification string itself is provided. + """ raise NotImplementedError() def extract_hierarchy(self, classification): """Given a classification, return a list of parts in the hierarchy. For non-hierarchical taxonomies, it usually suffices to return - `[classification]`.""" + `[classification]`. + """ return [classification] def recombine_classification_from_hierarchy(self, hierarchy): """Given a list of parts in the hierarchy, return the classification string. - For non-hierarchical taxonomies, it usually suffices to return hierarchy[0].""" + For non-hierarchical taxonomies, it usually suffices to return hierarchy[0]. + """ return hierarchy[0] def provide_list_context_and_uptodate(self): - """Provides data for the context and the uptodate list for the list of all classifiations. + """Provide data for the context and the uptodate list for the list of all classifiations. Must return a tuple of two dicts. The first is merged into the page's context, the second will be put into the uptodate list of all generated tasks. - Context must contain `title`.""" + Context must contain `title`. + """ raise NotImplementedError() def provide_context_and_uptodate(self, classification): - """Provides data for the context and the uptodate list for the list of the given classifiation. + """Provide data for the context and the uptodate list for the list of the given classifiation. Must return a tuple of two dicts. The first is merged into the page's context, the second will be put into the uptodate list of all generated tasks. Context must contain `title`, which should be something like 'Posts about ', - and `classification_title`, which should be related to the classification string.""" + and `classification_title`, which should be related to the classification string. + """ raise NotImplementedError() def postprocess_posts_per_classification(self, posts_per_classification_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None): - """This function can rearrange, modify or otherwise use the list of posts per classification and per language. + """Rearrange, modify or otherwise use the list of posts per classification and per language. For compatibility reasons, the list could be stored somewhere else as well. In case `has_hierarchy` is `True`, `flat_hierarchy_per_lang` is the flat hierarchy consisting of `TreeNode` elements, and `hierarchy_lookup_per_lang` is the corresponding hierarchy lookup mapping classification strings to - `TreeNode` objects.""" + `TreeNode` objects. + """ pass diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 5615f987ec..0dcfdeb01b 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -203,7 +203,6 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan def gen_tasks(self): """Render the tag pages and feeds.""" - self.site.scan_posts() yield self.group_task() From fe859aa3cb4219c28ff55d6958946b49c4eed49f Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 13:00:22 +0200 Subject: [PATCH 03/74] Improved sorting. --- nikola/plugin_categories.py | 10 +++++++++- nikola/plugins/misc/taxonomies_classifier.py | 5 ++++- nikola/plugins/task/taxonomies.py | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 1b0bda002f..5d012e2276 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -510,10 +510,18 @@ def classify(self, post, lang): """ raise NotImplementedError() - def sort_posts(self, posts, lang): + def sort_posts(self, posts, classification, lang): """Sort the given list of posts.""" pass + def sort_classifications(self, classifications, lang): + """Sort the given list of classification strings. + + For hierarchical taxonomies, the elements of the list are a single + path element of the path returned by extract_hierarchy(). + """ + pass + def get_list_path(self, lang): """A path handler for the list of all classifications. diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 1791698589..5f5e4df899 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -124,6 +124,7 @@ def _do_classification(self): (int(p.meta('priority')) if p.meta('priority') else 0, p.date, p.source_path)) posts.reverse() + taxonomy.sort_posts(posts, classification, lang) posts_per_classification[classification] = posts # Create hierarchy information if taxonomy.has_hierarchy: @@ -150,7 +151,9 @@ def create_hierarchy(cat_hierarchy, parent=None): node.classification_path = [pn.name for pn in node.get_path()] node.classification_name = taxonomy.recombine_classification_from_hierarchy(node.classification_path) hierarchy_lookup[node.classification_name] = node - return natsort.natsorted(result, key=lambda e: e.name, alg=natsort.ns.F | natsort.ns.IC) + classifications = natsort.natsorted(result, key=lambda e: e.name, alg=natsort.ns.F | natsort.ns.IC) + taxonomy.sort_classifications(classifications) + return classifications root_list = create_hierarchy(hierarchy) flat_hierarchy = utils.flatten_tree_structure(root_list) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 0dcfdeb01b..ec5d0d03d0 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -60,6 +60,7 @@ def _generate_classification_overview(self, taxonomy, lang): classifications = natsort.natsorted([tag for tag, posts in self.site.posts_per_classification[taxonomy.classification_name][lang].items() if len(posts) >= kw["minimum_post_count"]], alg=natsort.ns.F | natsort.ns.IC) + taxonomy.sort_classifications(classifications) # Set up classifications in context context[taxonomy.metadata_name] = classifications From 8a5d156c250f18c7f80a485443b3ada0d3d1e9d4 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 13:00:45 +0200 Subject: [PATCH 04/74] Forgot lang argument. --- nikola/plugins/misc/taxonomies_classifier.py | 2 +- nikola/plugins/task/taxonomies.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 5f5e4df899..30b3b1f4cf 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -152,7 +152,7 @@ def create_hierarchy(cat_hierarchy, parent=None): node.classification_name = taxonomy.recombine_classification_from_hierarchy(node.classification_path) hierarchy_lookup[node.classification_name] = node classifications = natsort.natsorted(result, key=lambda e: e.name, alg=natsort.ns.F | natsort.ns.IC) - taxonomy.sort_classifications(classifications) + taxonomy.sort_classifications(classifications, lang) return classifications root_list = create_hierarchy(hierarchy) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index ec5d0d03d0..63e0b5ee66 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -60,7 +60,7 @@ def _generate_classification_overview(self, taxonomy, lang): classifications = natsort.natsorted([tag for tag, posts in self.site.posts_per_classification[taxonomy.classification_name][lang].items() if len(posts) >= kw["minimum_post_count"]], alg=natsort.ns.F | natsort.ns.IC) - taxonomy.sort_classifications(classifications) + taxonomy.sort_classifications(classifications, lang) # Set up classifications in context context[taxonomy.metadata_name] = classifications From 5bd161662dde120465b07fd990eef9aaa4626e4c Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 13:10:14 +0200 Subject: [PATCH 05/74] Fixing minimum post count for hierarchies. --- nikola/plugins/misc/taxonomies_classifier.py | 3 +++ nikola/plugins/task/taxonomies.py | 28 ++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 30b3b1f4cf..8e04eeb075 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -112,6 +112,7 @@ def _do_classification(self): sys.exit(1) # Sort everything. + self.site.hierarchy_per_classification = {} self.site.flat_hierarchy_per_classification = {} self.site.hierarchy_lookup_per_classification = {} for taxonomy in taxonomies: @@ -128,6 +129,7 @@ def _do_classification(self): posts_per_classification[classification] = posts # Create hierarchy information if taxonomy.has_hierarchy: + self.site.hierarchy_per_classification[taxonomy.classification_name] = {} self.site.flat_hierarchy_per_classification[taxonomy.classification_name] = {} self.site.hierarchy_lookup_per_classification[taxonomy.classification_name] = {} for lang, posts_per_classification in self.site.posts_per_classification[taxonomy.classification_name].items(): @@ -158,6 +160,7 @@ def create_hierarchy(cat_hierarchy, parent=None): root_list = create_hierarchy(hierarchy) flat_hierarchy = utils.flatten_tree_structure(root_list) # Store result + self.site.hierarchy_per_classification[taxonomy.classification_name][lang] = root_list self.site.flat_hierarchy_per_classification[taxonomy.classification_name][lang] = flat_hierarchy self.site.hierarchy_lookup_per_classification[taxonomy.classification_name][lang] = hierarchy_lookup taxonomy.postprocess_posts_per_classification(self.site.posts_per_classification[taxonomy.classification_name], diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 63e0b5ee66..d31e49f59c 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -40,6 +40,23 @@ from nikola.nikola import _enclosure +def _clone_treenode(treenode, parent=None, acceptor=lambda x: True): + # Standard TreeNode stuff + node_clone = utils.TreeNode(treenode.name, parent) + node_clone.children = [_clone_treenode(node, parent=node_clone, acceptor=acceptor) for node in treenode.children] + node_clone.children = [node for node in node_clone.children if node] + node_clone.indent_levels = treenode.indent_levels + node_clone.indent_change_before = treenode.indent_change_before + node_clone.indent_change_after = treenode.indent_change_after + # Stuff added by extended_tags_preproces plugin + node_clone.tag_path = treenode.tag_path + node_clone.tag_name = treenode.tag_name + # Accept this node if there are no children (left) and acceptor fails + if not node_clone.children and not acceptor(treenode): + return None + return node_clone + + class RenderTaxonomies(Task): """Render the tag/category pages and feeds.""" @@ -55,7 +72,14 @@ def _generate_classification_overview(self, taxonomy, lang): # Collect all relevant classifications if taxonomy.has_hierarchy: - classifications = [cat.classification_name for cat in self.site.flat_hierarchy_per_classification[taxonomy.classification_name][lang]] + def acceptor(node): + return len(self.site.posts_per_classification[taxonomy.classification_name][lang][node.classification_name]) >= kw["minimum_post_count"] + + clipped_root_list = [_clone_treenode(node, parent=None, acceptor=acceptor) for node in self.site.hierarchy_per_classification[taxonomy.classification_name][lang]] + clipped_root_list = [node for node in clipped_root_list if node] + clipped_flat_hierarchy = utils.flatten_tree_structure(clipped_root_list) + + classifications = [cat.classification_name for cat in clipped_flat_hierarchy] else: classifications = natsort.natsorted([tag for tag, posts in self.site.posts_per_classification[taxonomy.classification_name][lang].items() if len(posts) >= kw["minimum_post_count"]], @@ -71,7 +95,7 @@ def _generate_classification_overview(self, taxonomy, lang): self.site.link(taxonomy.classification_name, node.classification_name, lang), node.indent_levels, node.indent_change_before, node.indent_change_after) - for node in self.site.flat_hierarchy_per_classification[taxonomy.classification_name][lang]] + for node in clipped_flat_hierarchy] # Prepare rendering context["permalink"] = self.site.link("{}_index".format(taxonomy.classification_name), None, lang) From a6c970827fec91bab6ee7a44f58ba120ab082ffa Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 13:11:01 +0200 Subject: [PATCH 06/74] Fixing output folder. --- nikola/plugins/task/taxonomies.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index d31e49f59c..b166158a3c 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -100,7 +100,7 @@ def acceptor(node): # Prepare rendering context["permalink"] = self.site.link("{}_index".format(taxonomy.classification_name), None, lang) context["pagekind"] = ["list", "tags_page"] - output_name = os.path.join(kw['output_folder'], self.site.path('{}_index'.format(taxonomy.classification_name), None, lang)) + output_name = os.path.join(self.site.config['OUTPUT_FOLDER'], self.site.path('{}_index'.format(taxonomy.classification_name), None, lang)) task = self.site.generic_post_list_renderer( lang, [], @@ -117,7 +117,7 @@ def _generate_classification_page_as_rss(self, taxonomy, classification, filtere """Create a RSS feed for a single classification in a given language.""" kind = taxonomy.classification_name # Render RSS - output_name = os.path.normpath(os.path.join(kw['output_folder'], self.site.path(kind + "_rss", classification, lang))) + output_name = os.path.normpath(os.path.join(self.site.config['OUTPUT_FOLDER'], self.site.path(kind + "_rss", classification, lang))) feed_url = urljoin(self.site.config['BASE_URL'], self.site.link(kind + "_rss", classification, lang).lstrip('/')) deps = [] deps_uptodate = [] @@ -168,7 +168,7 @@ def _generate_classification_page_as_list_atom(self, taxonomy, classification, f kind = taxonomy.classification_name context = copy(context) context['feedlink'] = self.site.abs_link(self.site.path('{}_atom'.format(kind), classification, lang)) - feed_path = os.path.join(kw['output_folder'], self.site.path('{}_atom'.format(kind), classification, lang)) + feed_path = os.path.join(self.site.config['OUTPUT_FOLDER'], self.site.path('{}_atom'.format(kind), classification, lang)) task = { 'basename': str(self.name), @@ -185,7 +185,7 @@ def _generate_classification_page_as_list(self, taxonomy, classification, filter """Render a single flat link list with this classification's posts.""" kind = taxonomy.classification_name template_name = "tag.tmpl" - output_name = os.path.join(kw['output_folder'], self.site.path(kind, classification, lang)) + output_name = os.path.join(self.site.config['OUTPUT_FOLDER'], self.site.path(kind, classification, lang)) context = copy(context) context["lang"] = lang context["posts"] = filtered_posts From 430173e5790379ca4dfbd0da4de9c1889350e4df Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 13:13:36 +0200 Subject: [PATCH 07/74] Fixing kws. --- nikola/plugins/task/taxonomies.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index b166158a3c..637d81b688 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -68,6 +68,7 @@ def _generate_classification_overview(self, taxonomy, lang): context = copy(context) kw = copy(kw) + kw['filters'] = self.site.config['FILTERS'] kw["minimum_post_count"] = taxonomy.minimum_post_count_per_classification_in_overview # Collect all relevant classifications @@ -212,7 +213,10 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan # Get data context, kw = taxonomy.provide_context_and_uptodate(classification) kw = copy(kw) - kw["blog_title"] = self.site.config["BLOG_TITLE"] + kw['filters'] = self.site.config['FILTERS'] + kw['site_url'] = self.site.config['SITE_URL'] + kw['blog_title'] = self.site.config['BLOG_TITLE'] + kw['generate_rss'] = self.site.config['GENERATE_RSS'] kw["feed_teasers"] = self.site.config["FEED_TEASERS"] kw["feed_plain"] = self.site.config["FEED_PLAIN"] kw["feed_link_append_query"] = self.site.config["FEED_LINKS_APPEND_QUERY"] From 7899a3ce6b472151188ba1c132b6de9dd769ee77 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 13:18:44 +0200 Subject: [PATCH 08/74] Improved documentation. --- nikola/plugin_categories.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 5d012e2276..c6de1d6a40 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -511,7 +511,10 @@ def classify(self, post, lang): raise NotImplementedError() def sort_posts(self, posts, classification, lang): - """Sort the given list of posts.""" + """Sort the given list of posts. + + The sort must happen in-place. + """ pass def sort_classifications(self, classifications, lang): @@ -519,6 +522,8 @@ def sort_classifications(self, classifications, lang): For hierarchical taxonomies, the elements of the list are a single path element of the path returned by extract_hierarchy(). + + The sort must happen in-place. """ pass @@ -585,8 +590,8 @@ def postprocess_posts_per_classification(self, posts_per_classification_per_lang For compatibility reasons, the list could be stored somewhere else as well. In case `has_hierarchy` is `True`, `flat_hierarchy_per_lang` is the flat - hierarchy consisting of `TreeNode` elements, and `hierarchy_lookup_per_lang` + hierarchy consisting of `utils.TreeNode` elements, and `hierarchy_lookup_per_lang` is the corresponding hierarchy lookup mapping classification strings to - `TreeNode` objects. + `utils.TreeNode` objects. """ pass From 2d0d6c6a71724fd3e4828adca08672aafac7673e Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 13:20:42 +0200 Subject: [PATCH 09/74] Forgot postprocessing... --- nikola/plugins/misc/taxonomies_classifier.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 8e04eeb075..97c604eb4e 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -169,6 +169,15 @@ def create_hierarchy(cat_hierarchy, parent=None): else: taxonomy.postprocess_posts_per_classification(self.site.posts_per_classification[taxonomy.classification_name], flat_hierarchy, hierarchy_lookup) + # Postprocessing + for taxonomy in taxonomies: + for lang, posts_per_classification in self.site.posts_per_classification[taxonomy.classification_name].items(): + taxonomy.postprocess_posts_per_classification( + posts_per_classification, + self.site.flat_hierarchy_per_classification.get(taxonomy.classification_name, {}).get(lang, None), + self.site.hierarchy_lookup_per_classification.get(taxonomy.classification_name, {}).get(lang, None), + ) + def _postprocess_path(self, path, lang, force_extension=None): if force_extension is not None: if len(path) == 0: From 9f3ad9bcaf03bf1a4c7c97b881549cf29a479acd Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 13:30:05 +0200 Subject: [PATCH 10/74] Forgot some changes. --- .../plugins/misc/taxonomies_classifier.plugin | 2 +- nikola/plugins/misc/taxonomies_classifier.py | 62 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/nikola/plugins/misc/taxonomies_classifier.plugin b/nikola/plugins/misc/taxonomies_classifier.plugin index e7546f30a9..aeec68bf8c 100644 --- a/nikola/plugins/misc/taxonomies_classifier.plugin +++ b/nikola/plugins/misc/taxonomies_classifier.plugin @@ -1,6 +1,6 @@ [Core] name = classify_taxonomies -module = classify_taxonomies +module = taxonomies_classifier [Documentation] author = Roberto Alsina diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 97c604eb4e..29eb6c1985 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -43,22 +43,22 @@ class TaxonomiesClassifier(SignalHandler): name = "render_taxonomies" - def _do_classification(self): - taxonomies = self.site.plugin_manager.getPluginsOfCategory('Taxonomy') - self.site.posts_per_classification = {} + def _do_classification(self, site): + taxonomies = site.plugin_manager.getPluginsOfCategory('Taxonomy') + site.posts_per_classification = {} for taxonomy in taxonomies: - if taxonomy.classification_name in self.site.posts_per_classification: + if taxonomy.classification_name in site.posts_per_classification: raise Exception("Found more than one taxonomy with classification name '{}'!".format(taxonomy.classification_name)) - self.site.posts_per_classification[taxonomy.classification_name] = { + site.posts_per_classification[taxonomy.classification_name] = { lang: defaultdict(set) for lang in self.config['TRANSLATIONS'].keys() } # Classify posts - for post in self.timeline: + for post in site.timeline: for taxonomy in taxonomies: if taxonomy.apply_to_posts if post.is_post else taxonomy.apply_to_pages: classifications = {} - for lang in self.config['TRANSLATIONS'].keys(): + for lang in site.config['TRANSLATIONS'].keys(): # Extract classifications for this language classifications[lang] = taxonomy.classify(post, lang) assert taxonomy.more_than_one_classifications_per_post or len(classifications[lang]) <= 1 @@ -70,21 +70,21 @@ def _do_classification(self): # Add post to sets for classification in classifications[lang]: while classification: - self.site.posts_per_classification[taxonomy.classification_name][lang][classification].add(post) + site.posts_per_classification[taxonomy.classification_name][lang][classification].add(post) if not taxonomy.include_posts_from_subhierarchies or not taxonomy.has_hierarchy: break classification = taxonomy.recombine_classification_from_hierarchy(taxonomy.extract_hierarchy(classification)[:-1]) # Check for valid paths and for collisions - taxonomy_outputs = {lang: dict() for lang in self.config['TRANSLATIONS'].keys()} + taxonomy_outputs = {lang: dict() for lang in site.config['TRANSLATIONS'].keys()} quit = False for taxonomy in taxonomies: # Check for collisions (per language) - for lang in self.config['TRANSLATIONS'].keys(): - for tlang in self.config['TRANSLATIONS'].keys(): + for lang in site.config['TRANSLATIONS'].keys(): + for tlang in site.config['TRANSLATIONS'].keys(): if lang != tlang and not taxonomy.also_create_classifications_from_other_languages: continue - for classification, posts in self.site.posts_per_classification[taxonomy.classification_name][tlang].items(): + for classification, posts in site.posts_per_classification[taxonomy.classification_name][tlang].items(): # Obtain path as tuple if taxonomy.has_hierarchy: path = taxonomy.get_path(taxonomy.extract_hierarchy(classification), lang) @@ -112,12 +112,12 @@ def _do_classification(self): sys.exit(1) # Sort everything. - self.site.hierarchy_per_classification = {} - self.site.flat_hierarchy_per_classification = {} - self.site.hierarchy_lookup_per_classification = {} + site.hierarchy_per_classification = {} + site.flat_hierarchy_per_classification = {} + site.hierarchy_lookup_per_classification = {} for taxonomy in taxonomies: # Sort post lists - for lang, posts_per_classification in self.site.posts_per_classification[taxonomy.classification_name].items(): + for lang, posts_per_classification in site.posts_per_classification[taxonomy.classification_name].items(): # Convert sets to lists and sort them for classification in list(posts_per_classification.keys()): posts = list(posts_per_classification[classification]) @@ -129,10 +129,10 @@ def _do_classification(self): posts_per_classification[classification] = posts # Create hierarchy information if taxonomy.has_hierarchy: - self.site.hierarchy_per_classification[taxonomy.classification_name] = {} - self.site.flat_hierarchy_per_classification[taxonomy.classification_name] = {} - self.site.hierarchy_lookup_per_classification[taxonomy.classification_name] = {} - for lang, posts_per_classification in self.site.posts_per_classification[taxonomy.classification_name].items(): + site.hierarchy_per_classification[taxonomy.classification_name] = {} + site.flat_hierarchy_per_classification[taxonomy.classification_name] = {} + site.hierarchy_lookup_per_classification[taxonomy.classification_name] = {} + for lang, posts_per_classification in site.posts_per_classification[taxonomy.classification_name].items(): # Compose hierarchy hierarchy = {} for classification in posts_per_classification.keys(): @@ -160,22 +160,22 @@ def create_hierarchy(cat_hierarchy, parent=None): root_list = create_hierarchy(hierarchy) flat_hierarchy = utils.flatten_tree_structure(root_list) # Store result - self.site.hierarchy_per_classification[taxonomy.classification_name][lang] = root_list - self.site.flat_hierarchy_per_classification[taxonomy.classification_name][lang] = flat_hierarchy - self.site.hierarchy_lookup_per_classification[taxonomy.classification_name][lang] = hierarchy_lookup - taxonomy.postprocess_posts_per_classification(self.site.posts_per_classification[taxonomy.classification_name], - self.site.flat_hierarchy_per_classification[taxonomy.classification_name], - self.site.hierarchy_lookup_per_classification[taxonomy.classification_name]) + site.hierarchy_per_classification[taxonomy.classification_name][lang] = root_list + site.flat_hierarchy_per_classification[taxonomy.classification_name][lang] = flat_hierarchy + site.hierarchy_lookup_per_classification[taxonomy.classification_name][lang] = hierarchy_lookup + taxonomy.postprocess_posts_per_classification(site.posts_per_classification[taxonomy.classification_name], + site.flat_hierarchy_per_classification[taxonomy.classification_name], + site.hierarchy_lookup_per_classification[taxonomy.classification_name]) else: - taxonomy.postprocess_posts_per_classification(self.site.posts_per_classification[taxonomy.classification_name], flat_hierarchy, hierarchy_lookup) + taxonomy.postprocess_posts_per_classification(site.posts_per_classification[taxonomy.classification_name], flat_hierarchy, hierarchy_lookup) # Postprocessing for taxonomy in taxonomies: - for lang, posts_per_classification in self.site.posts_per_classification[taxonomy.classification_name].items(): + for lang, posts_per_classification in site.posts_per_classification[taxonomy.classification_name].items(): taxonomy.postprocess_posts_per_classification( posts_per_classification, - self.site.flat_hierarchy_per_classification.get(taxonomy.classification_name, {}).get(lang, None), - self.site.hierarchy_lookup_per_classification.get(taxonomy.classification_name, {}).get(lang, None), + site.flat_hierarchy_per_classification.get(taxonomy.classification_name, {}).get(lang, None), + site.hierarchy_lookup_per_classification.get(taxonomy.classification_name, {}).get(lang, None), ) def _postprocess_path(self, path, lang, force_extension=None): @@ -221,5 +221,5 @@ def set_site(self, site): # Add hook for after post scanning blinker.signal("scanned").connect(self._do_classification) # Register path handlers - for taxonomy in self.plugin_manager.getPluginsOfCategory('Taxonomy'): + for taxonomy in self.site.plugin_manager.getPluginsOfCategory('Taxonomy'): self._register_path_handlers(taxonomy) From 959160a97237788fe7cc45c2a74d84b9df2e80a7 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 16:55:19 +0200 Subject: [PATCH 11/74] Allowing to disable taxonomies. --- nikola/plugin_categories.py | 8 ++++++++ nikola/plugins/task/taxonomies.py | 3 +++ 2 files changed, 11 insertions(+) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index c6de1d6a40..2a68a7d84a 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -503,6 +503,14 @@ class Taxonomy(BasePlugin): # pages. also_create_classifications_from_other_languages = True + def is_enabled(self): + """Return True if this taxonomy is enabled, or False otherwise. + + Enabled means that the overview page and the classification lists + are created. + """ + return True + def classify(self, post, lang): """Classify the given post for the given language. diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 637d81b688..19b8a16096 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -236,6 +236,9 @@ def gen_tasks(self): yield self.group_task() for taxonomy in self.site.plugin_manager.getPluginsOfCategory('Taxonomy'): + # Should this taxonomy be considered after all? + if not taxonomy.is_enabled(): + continue for lang in self.site.config["TRANSLATIONS"]: # Generate list of classifications (i.e. classification overview) if taxonomy.template_for_list_of_one_classification is not None: From b0a492d6843af82295d535923c45eb7a31e508e6 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 17:07:06 +0200 Subject: [PATCH 12/74] Forgot more lang args. --- nikola/plugin_categories.py | 4 ++-- nikola/plugins/task/taxonomies.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 2a68a7d84a..02a2abd0ac 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -571,7 +571,7 @@ def recombine_classification_from_hierarchy(self, hierarchy): """ return hierarchy[0] - def provide_list_context_and_uptodate(self): + def provide_list_context_and_uptodate(self, lang): """Provide data for the context and the uptodate list for the list of all classifiations. Must return a tuple of two dicts. The first is merged into the page's context, @@ -581,7 +581,7 @@ def provide_list_context_and_uptodate(self): """ raise NotImplementedError() - def provide_context_and_uptodate(self, classification): + def provide_context_and_uptodate(self, classification, lang): """Provide data for the context and the uptodate list for the list of the given classifiation. Must return a tuple of two dicts. The first is merged into the page's context, diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 19b8a16096..7091569bd7 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -211,7 +211,7 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan if len(filtered_posts) == 0 and taxonomy.omit_empty_classifications: return # Get data - context, kw = taxonomy.provide_context_and_uptodate(classification) + context, kw = taxonomy.provide_context_and_uptodate(classification, lang) kw = copy(kw) kw['filters'] = self.site.config['FILTERS'] kw['site_url'] = self.site.config['SITE_URL'] From 7f6b13e7cf2abeeb6b59a5b2f34919b6e0b86a69 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 17:08:28 +0200 Subject: [PATCH 13/74] Only setting pagekind in context when it wasn't set before. --- nikola/plugins/task/taxonomies.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 7091569bd7..eb09d88c79 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -100,7 +100,8 @@ def acceptor(node): # Prepare rendering context["permalink"] = self.site.link("{}_index".format(taxonomy.classification_name), None, lang) - context["pagekind"] = ["list", "tags_page"] + if "pagekind" not in context: + context["pagekind"] = ["list", "tags_page"] output_name = os.path.join(self.site.config['OUTPUT_FOLDER'], self.site.path('{}_index'.format(taxonomy.classification_name), None, lang)) task = self.site.generic_post_list_renderer( lang, @@ -159,7 +160,8 @@ def page_path(i, displayed_i, num_pages, force_addition, extension=None): rss_link = ("""""".format( taxonomy.classification_name, context['classification_title'], lang, self.site.link('{}_rss'.format(kind), classification, lang))) context['rss_link'] = rss_link - context["pagekind"] = ["index", "tag_page"] + if "pagekind" not in context: + context["pagekind"] = ["index", "tag_page"] template_name = taxonomy.template_for_list_of_one_classification yield self.site.generic_index_renderer(lang, filtered_posts, context['title'], template_name, context, kw, str(self.name), page_link, page_path) @@ -192,7 +194,8 @@ def _generate_classification_page_as_list(self, taxonomy, classification, filter context["posts"] = filtered_posts context["permalink"] = self.site.link(kind, classification, lang) context["kind"] = kind - context["pagekind"] = ["list", "tag_page"] + if "pagekind" not in context: + context["pagekind"] = ["list", "tag_page"] task = self.site.generic_post_list_renderer(lang, filtered_posts, output_name, template_name, kw['filters'], context) task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.taxonomies:list')] task['basename'] = str(self.name) From 5ff9f4a11a675f45c0b3eb94e6bd04eb60f0ede9 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 17:19:43 +0200 Subject: [PATCH 14/74] Made plugin more flexible. --- nikola/plugin_categories.py | 3 +++ nikola/plugins/task/taxonomies.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 02a2abd0ac..5f2d7fcc58 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -470,6 +470,9 @@ class Taxonomy(BasePlugin): # The classification name to be used when storing the classification # in the metadata. metadata_name = "taxonomy" + # Variable for the overview page template which contains the list of + # classifications. + overview_page_variable_name = "taxonomy" # If True, there can be more than one classification per post; in that case, # the classification data in the metadata is stored as a list. If False, # the classification data in the metadata is stored as a string, or None diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index eb09d88c79..a8f8f1b71d 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -88,7 +88,7 @@ def acceptor(node): taxonomy.sort_classifications(classifications, lang) # Set up classifications in context - context[taxonomy.metadata_name] = classifications + context[taxonomy.overview_page_variable_name] = classifications context["items"] = [(classification, self.site.link(taxonomy.classification_name, classification, lang)) for classification in classifications] context["has_hierarchy"] = taxonomy.has_hierarchy if taxonomy.has_hierarchy: From f6c86a232dc11b285ea450959ce17db79175d105 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 17:37:05 +0200 Subject: [PATCH 15/74] Allowing to force complete path. --- nikola/plugin_categories.py | 25 ++++++++++++------ nikola/plugins/misc/taxonomies_classifier.py | 27 ++++++++++---------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 5f2d7fcc58..59e074c5ec 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -541,21 +541,30 @@ def sort_classifications(self, classifications, lang): def get_list_path(self, lang): """A path handler for the list of all classifications. - The last element in the returned path must have no extension, and the - PRETTY_URLS config must be ignored. The return value will be modified - based on the PRETTY_URLS and INDEX_FILE settings. + Must return a list or tuple, together with a boolean indicating + whether INDEX_FILE should always be added (True) or not (False). + + In case INDEX_FILE should not be added, the last element in the returned + path must have no extension, and the PRETTY_URLS config must be ignored + by this handler. The return value will be modified based on the + PRETTY_URLS and INDEX_FILE settings. """ raise NotImplementedError() def get_path(self, classification, lang): """A path handler for the given classification. - The last element in the returned path must have no extension, and the - PRETTY_URLS config must be ignored. The return value will be modified - based on the PRETTY_URLS and INDEX_FILE settings. + Must return a list or tuple, together with a boolean indicating + whether INDEX_FILE should always be added (True) or not (False). + + In case INDEX_FILE should not be added, the last element in the returned + path must have no extension, and the PRETTY_URLS config must be ignored + by this handler. The return value will be modified based on the + PRETTY_URLS and INDEX_FILE settings. - For hierarchical taxonomies, the result of extract_hierarchy is provided. - For non-hierarchical taxonomies, the classification string itself is provided. + For hierarchical taxonomies, the result of extract_hierarchy is provided + as `classification`. For non-hierarchical taxonomies, the classification + string itself is provided as `classification`. """ raise NotImplementedError() diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 29eb6c1985..794514d4ca 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -86,21 +86,19 @@ def _do_classification(self, site): continue for classification, posts in site.posts_per_classification[taxonomy.classification_name][tlang].items(): # Obtain path as tuple - if taxonomy.has_hierarchy: - path = taxonomy.get_path(taxonomy.extract_hierarchy(classification), lang) - else: - path = taxonomy.get_path(classification, lang) - path = tuple(path) + path = self.site.path_handlers[taxonomy.classification_name](classification, lang) # Check that path is OK for path_element in path: if len(path_element) == 0: utils.LOGGER.error("{0} {1} yields invalid path '{2}'!".format(taxonomy.classification_name.title(), classification, '/'.join(path))) quit = True + # Combine path + path = os.path.join([os.path.normpath(p) for p in path if p != '.']) # Determine collisions if path in taxonomy_outputs[lang]: other_classification_name, other_classification, other_posts = taxonomy_outputs[lang][path] - utils.LOGGER.error('You have classifications that are too similar: {0} "{1}" and {1} "{2}"'.format( - taxonomy.classification_name, classification, other_classification_name, other_classification)) + utils.LOGGER.error('You have classifications that are too similar: {0} "{1}" and {2} "{3}" both result in output path {4} for langauge {5}.'.format( + taxonomy.classification_name, classification, other_classification_name, other_classification, path, lang)) utils.LOGGER.error('{0} {1} is used in: {1}'.format( taxonomy.classification_name.title(), classification, ', '.join(sorted([p.source_path for p in posts])))) utils.LOGGER.error('{0} {1} is used in: {1}'.format( @@ -178,12 +176,12 @@ def create_hierarchy(cat_hierarchy, parent=None): site.hierarchy_lookup_per_classification.get(taxonomy.classification_name, {}).get(lang, None), ) - def _postprocess_path(self, path, lang, force_extension=None): + def _postprocess_path(self, path, lang, always_append_index=False, force_extension=None): if force_extension is not None: - if len(path) == 0: + if len(path) == 0 or always_append_index: path = [os.path.splitext(self.site.config['INDEX_FILE'])[0]] path[-1] += force_extension - elif self.site.config['PRETTY_URLS'] or len(path) == 0: + elif self.site.config['PRETTY_URLS'] or len(path) == 0 or always_append_index: path = path + [self.site.config['INDEX_FILE']] else: path[-1] += '.html' @@ -191,15 +189,16 @@ def _postprocess_path(self, path, lang, force_extension=None): def _taxonomy_index_path(self, lang, taxonomy): """Return path to the classification overview.""" - return self._postprocess_path(taxonomy.get_list_path(lang), lang) + path, append_index = taxonomy.get_list_path(lang) + return self._postprocess_path(path, lang, always_append_index=append_index) def _taxonomy_path(self, name, lang, taxonomy, force_extension=None): """Return path to a classification.""" if taxonomy.has_hirearchy: - path = taxonomy.get_path(taxonomy.extract_hierarchy(name), lang) + path, append_index = taxonomy.get_path(taxonomy.extract_hierarchy(name), lang) else: - path = taxonomy.get_path(name, lang) - return self._postprocess_path(path, lang, force_extension=force_extension) + path, append_index = taxonomy.get_path(name, lang) + return self._postprocess_path(path, lang, always_append_index=append_index, force_extension=force_extension) def _taxonomy_atom_path(self, name, lang, taxonomy): """Return path to a classification Atom feed.""" From 0c31b3f7e1d2524fa83755c9f492eb3ad16000bb Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 17:42:18 +0200 Subject: [PATCH 16/74] Rewrote authors plugin as a Taxonomy plugin. --- nikola/plugins/task/authors.py | 348 +++++++-------------------------- 1 file changed, 68 insertions(+), 280 deletions(-) diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index ec618002b3..09cc07b07b 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -27,300 +27,88 @@ """Render the author pages and feeds.""" from __future__ import unicode_literals -import os -import natsort -try: - from urlparse import urljoin -except ImportError: - from urllib.parse import urljoin # NOQA -from collections import defaultdict -from blinker import signal - -from nikola.plugin_categories import Task +from nikola.plugin_categories import Taxonomy from nikola import utils -class RenderAuthors(Task): +class ClassifyAuthors(Taxonomy): """Render the author pages and feeds.""" - name = "render_authors" - posts_per_author = None + name = "classify_authors" + + classification_name = "author" + metadata_name = "author" + overview_page_variable_name = "authors" + more_than_one_classifications_per_post = False + has_hierarchy = False + template_for_classification_overview = "authors.tmpl" + apply_to_posts = True + apply_to_pages = False + minimum_post_count_per_classification_in_overview = 1 + omit_empty_classifications = False + also_create_classifications_from_other_languages = False def set_site(self, site): """Set Nikola site.""" - self.generate_author_pages = False - if site.config["ENABLE_AUTHOR_PAGES"]: - site.register_path_handler('author_index', self.author_index_path) - site.register_path_handler('author', self.author_path) - site.register_path_handler('author_atom', self.author_atom_path) - site.register_path_handler('author_rss', self.author_rss_path) - signal('scanned').connect(self.posts_scanned) - return super(RenderAuthors, self).set_site(site) - - def posts_scanned(self, event): - """Called after posts are scanned via signal.""" - self.generate_author_pages = self.site.config["ENABLE_AUTHOR_PAGES"] and len(self._posts_per_author()) > 1 - self.site.GLOBAL_CONTEXT["author_pages_generated"] = self.generate_author_pages - - def gen_tasks(self): - """Render the author pages and feeds.""" - kw = { - "translations": self.site.config["TRANSLATIONS"], - "blog_title": self.site.config["BLOG_TITLE"], - "site_url": self.site.config["SITE_URL"], - "base_url": self.site.config["BASE_URL"], - "messages": self.site.MESSAGES, - "output_folder": self.site.config['OUTPUT_FOLDER'], - "filters": self.site.config['FILTERS'], - 'author_path': self.site.config['AUTHOR_PATH'], - "author_pages_are_indexes": self.site.config['AUTHOR_PAGES_ARE_INDEXES'], - "generate_rss": self.site.config['GENERATE_RSS'], - "feed_teasers": self.site.config["FEED_TEASERS"], - "feed_plain": self.site.config["FEED_PLAIN"], - "feed_link_append_query": self.site.config["FEED_LINKS_APPEND_QUERY"], - "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'], - "feed_length": self.site.config['FEED_LENGTH'], - "tzinfo": self.site.tzinfo, - "pretty_urls": self.site.config['PRETTY_URLS'], - "strip_indexes": self.site.config['STRIP_INDEXES'], - "index_file": self.site.config['INDEX_FILE'], - } - - self.site.scan_posts() - yield self.group_task() - - if self.generate_author_pages: - yield self.list_authors_page(kw) - - if not self._posts_per_author(): # this may be self.site.posts_per_author - return - - author_list = list(self._posts_per_author().items()) - - def render_lists(author, posts): - """Render author pages as RSS files and lists/indexes.""" - post_list = sorted(posts, key=lambda a: a.date) - post_list.reverse() - for lang in kw["translations"]: - if kw["show_untranslated_posts"]: - filtered_posts = post_list - else: - filtered_posts = [x for x in post_list if x.is_translation_available(lang)] - if kw["generate_rss"]: - yield self.author_rss(author, lang, filtered_posts, kw) - # Render HTML - if kw['author_pages_are_indexes']: - yield self.author_page_as_index(author, lang, filtered_posts, kw) - else: - yield self.author_page_as_list(author, lang, filtered_posts, kw) - - for author, posts in author_list: - for task in render_lists(author, posts): - yield task - - def _create_authors_page(self, kw): - """Create a global "all authors" page for each language.""" - template_name = "authors.tmpl" - kw = kw.copy() - for lang in kw["translations"]: - authors = natsort.natsorted([author for author in self._posts_per_author().keys()], - alg=natsort.ns.F | natsort.ns.IC) - has_authors = (authors != []) - kw['authors'] = authors - output_name = os.path.join( - kw['output_folder'], self.site.path('author_index', None, lang)) - context = {} - if has_authors: - context["title"] = kw["messages"][lang]["Authors"] - context["items"] = [(author, self.site.link("author", author, lang)) for author - in authors] - context["description"] = context["title"] - else: - context["items"] = None - context["permalink"] = self.site.link("author_index", None, lang) - context["pagekind"] = ["list", "authors_page"] - task = self.site.generic_post_list_renderer( - lang, - [], - output_name, - template_name, - kw['filters'], - context, - ) - task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.authors:page')] - task['basename'] = str(self.name) - yield task - - def list_authors_page(self, kw): - """Create a global "all authors" page for each language.""" - yield self._create_authors_page(kw) - - def _get_title(self, author): - return author - - def _get_description(self, author, lang): - descriptions = self.site.config['AUTHOR_PAGES_DESCRIPTIONS'] - return descriptions[lang][author] if lang in descriptions and author in descriptions[lang] else None - - def author_page_as_index(self, author, lang, post_list, kw): - """Render a sort of index page collection using only this author's posts.""" - kind = "author" - - def page_link(i, displayed_i, num_pages, force_addition, extension=None): - feed = "_atom" if extension == ".atom" else "" - return utils.adjust_name_for_index_link(self.site.link(kind + feed, author, lang), i, displayed_i, lang, self.site, force_addition, extension) - - def page_path(i, displayed_i, num_pages, force_addition, extension=None): - feed = "_atom" if extension == ".atom" else "" - return utils.adjust_name_for_index_path(self.site.path(kind + feed, author, lang), i, displayed_i, lang, self.site, force_addition, extension) - - context_source = {} - title = self._get_title(author) - if kw["generate_rss"]: - # On a author page, the feeds include the author's feeds - rss_link = ("""""".format( - title, lang, self.site.link(kind + "_rss", author, lang))) - context_source['rss_link'] = rss_link - context_source["author"] = title - indexes_title = kw["messages"][lang]["Posts by %s"] % title - context_source["description"] = self._get_description(author, lang) - context_source["pagekind"] = ["index", "author_page"] - template_name = "authorindex.tmpl" + self.show_list_as_index = site.config['AUTHOR_PAGES_ARE_INDEXES'] + self.template_for_list_of_one_classification = "authorindex.tmpl" if self.show_list_as_index else "author.tmpl" + return super(ClassifyAuthors, self).set_site(site) - yield self.site.generic_index_renderer(lang, post_list, indexes_title, template_name, context_source, kw, str(self.name), page_link, page_path) + def is_enabled(self): + """Return True if this taxonomy is enabled, or False otherwise.""" + return self.site.config["ENABLE_AUTHOR_PAGES"] - def author_page_as_list(self, author, lang, post_list, kw): - """Render a single flat link list with this author's posts.""" - kind = "author" - template_name = "author.tmpl" - output_name = os.path.join(kw['output_folder'], self.site.path( - kind, author, lang)) - context = {} - context["lang"] = lang - title = self._get_title(author) - context["author"] = title - context["title"] = kw["messages"][lang]["Posts by %s"] % title - context["posts"] = post_list - context["permalink"] = self.site.link(kind, author, lang) - context["kind"] = kind - context["description"] = self._get_description(author, lang) - context["pagekind"] = ["list", "author_page"] - task = self.site.generic_post_list_renderer( - lang, - post_list, - output_name, - template_name, - kw['filters'], - context, - ) - task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.authors:list')] - task['basename'] = str(self.name) - yield task + def classify(self, post, lang): + """Classify the given post for the given language.""" + return [post.author()] - def author_rss(self, author, lang, posts, kw): - """Create a RSS feed for a single author in a given language.""" - kind = "author" - # Render RSS - output_name = os.path.normpath( - os.path.join(kw['output_folder'], - self.site.path(kind + "_rss", author, lang))) - feed_url = urljoin(self.site.config['BASE_URL'], self.site.link(kind + "_rss", author, lang).lstrip('/')) - deps = [] - deps_uptodate = [] - post_list = sorted(posts, key=lambda a: a.date) - post_list.reverse() - for post in post_list: - deps += post.deps(lang) - deps_uptodate += post.deps_uptodate(lang) - task = { - 'basename': str(self.name), - 'name': output_name, - 'file_dep': deps, - 'targets': [output_name], - 'actions': [(utils.generic_rss_renderer, - (lang, "{0} ({1})".format(kw["blog_title"](lang), self._get_title(author)), - kw["site_url"], None, post_list, - output_name, kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], - feed_url, None, kw["feed_link_append_query"]))], - 'clean': True, - 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.authors:rss')] + deps_uptodate, - 'task_dep': ['render_posts'], - } - return utils.apply_filters(task, kw['filters']) + def get_list_path(self, lang): + """A path handler for the list of all classifications.""" + return [self.site.config['AUTHOR_PATH']], True - def slugify_author_name(self, name, lang=None): - """Slugify an author name.""" - if lang is None: # TODO: remove in v8 - utils.LOGGER.warn("RenderAuthors.slugify_author_name() called without language!") - lang = '' + def get_path(self, author, lang): + """A path handler for the given classification.""" if self.site.config['SLUG_AUTHOR_PATH']: - name = utils.slugify(name, lang) - return name - - def author_index_path(self, name, lang): - """Link to the author's index. - - Example: - - link://authors/ => /authors/index.html - """ - return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['AUTHOR_PATH'], - self.site.config['INDEX_FILE']] if _f] - - def author_path(self, name, lang): - """Link to an author's page. - - Example: - - link://author/joe => /authors/joe.html - """ - if self.site.config['PRETTY_URLS']: - return [_f for _f in [ - self.site.config['TRANSLATIONS'][lang], - self.site.config['AUTHOR_PATH'], - self.slugify_author_name(name, lang), - self.site.config['INDEX_FILE']] if _f] + slug = utils.slugify(author, lang) else: - return [_f for _f in [ - self.site.config['TRANSLATIONS'][lang], - self.site.config['AUTHOR_PATH'], - self.slugify_author_name(name, lang) + ".html"] if _f] - - def author_atom_path(self, name, lang): - """Link to an author's Atom feed. - - Example: - - link://author_atom/joe => /authors/joe.atom - """ - return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['AUTHOR_PATH'], self.slugify_author_name(name, lang) + ".atom"] if - _f] - - def author_rss_path(self, name, lang): - """Link to an author's RSS feed. - - Example: + slug = author + return [self.site.config['AUTHOR_PATH'], slug], False - link://author_rss/joe => /authors/joe.rss - """ - return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['AUTHOR_PATH'], self.slugify_author_name(name, lang) + ".xml"] if - _f] - - def _add_extension(self, path, extension): - path[-1] += extension - return path + def provide_list_context_and_uptodate(self, lang): + """Provide data for the context and the uptodate list for the list of all classifiations.""" + kw = { + "messages": self.site.MESSAGES, + } + context = { + "title": kw["messages"][lang]["Authors"], + "description": kw["messages"][lang]["Authors"], + "permalink": self.site.link("author_index", None, lang), + "pagekind": ["list", "authors_page"], + } + kw.update(context) + return context, kw - def _posts_per_author(self): - """Return a dict of posts per author.""" - if self.posts_per_author is None: - self.posts_per_author = defaultdict(list) - for post in self.site.timeline: - if post.is_post: - self.posts_per_author[post.author()].append(post) - return self.posts_per_author + def provide_context_and_uptodate(self, author, lang): + """Provide data for the context and the uptodate list for the list of the given classifiation.""" + descriptions = self.site.config['AUTHOR_PAGES_DESCRIPTIONS'] + kw = { + "messages": self.site.MESSAGES, + } + context = { + "title": kw["messages"][lang]["Posts by %s"] % author, + "classification_title": author, + "description": descriptions[lang][author] if lang in descriptions and author in descriptions[lang] else None, + "pagekind": ["index" if self.show_list_as_index else "list", "author_page"], + } + kw.update(context) + return context, kw + + def postprocess_posts_per_classification(self, posts_per_author_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None): + """Rearrange, modify or otherwise use the list of posts per classification and per language.""" + more_than_one = False + for lang, posts_per_author in posts_per_author_per_language.item(): + if len(posts_per_author.keys()) > 1: + more_than_one = True + self.generate_author_pages = self.site.config["ENABLE_AUTHOR_PAGES"] and more_than_one + self.site.GLOBAL_CONTEXT["author_pages_generated"] = self.generate_author_pages From c02fba4c36c2c235f3be3bbbaf35390c7975c529 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 18:18:24 +0200 Subject: [PATCH 17/74] Improving path creation. --- nikola/plugin_categories.py | 8 ++++++-- nikola/plugins/misc/taxonomies_classifier.py | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 59e074c5ec..536c3d3048 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -538,7 +538,7 @@ def sort_classifications(self, classifications, lang): """ pass - def get_list_path(self, lang): + def get_list_path(self, lang, type='page'): """A path handler for the list of all classifications. Must return a list or tuple, together with a boolean indicating @@ -548,10 +548,12 @@ def get_list_path(self, lang): path must have no extension, and the PRETTY_URLS config must be ignored by this handler. The return value will be modified based on the PRETTY_URLS and INDEX_FILE settings. + + Type can be either 'page', 'feed' (for Atom feed) or 'rss'. """ raise NotImplementedError() - def get_path(self, classification, lang): + def get_path(self, classification, lang, type='page'): """A path handler for the given classification. Must return a list or tuple, together with a boolean indicating @@ -562,6 +564,8 @@ def get_path(self, classification, lang): by this handler. The return value will be modified based on the PRETTY_URLS and INDEX_FILE settings. + Type can be either 'page', 'feed' (for Atom feed) or 'rss'. + For hierarchical taxonomies, the result of extract_hierarchy is provided as `classification`. For non-hierarchical taxonomies, the classification string itself is provided as `classification`. diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 794514d4ca..6e63eccef1 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -177,9 +177,15 @@ def create_hierarchy(cat_hierarchy, parent=None): ) def _postprocess_path(self, path, lang, always_append_index=False, force_extension=None): + if type == 'feed': + force_extension = '.atom' + elif type == 'rss': + force_extension = '.xml' if force_extension is not None: if len(path) == 0 or always_append_index: path = [os.path.splitext(self.site.config['INDEX_FILE'])[0]] + if type == 'rss': + path = ['rss'] path[-1] += force_extension elif self.site.config['PRETTY_URLS'] or len(path) == 0 or always_append_index: path = path + [self.site.config['INDEX_FILE']] @@ -192,21 +198,21 @@ def _taxonomy_index_path(self, lang, taxonomy): path, append_index = taxonomy.get_list_path(lang) return self._postprocess_path(path, lang, always_append_index=append_index) - def _taxonomy_path(self, name, lang, taxonomy, force_extension=None): + def _taxonomy_path(self, name, lang, taxonomy, force_extension=None, type='page'): """Return path to a classification.""" if taxonomy.has_hirearchy: - path, append_index = taxonomy.get_path(taxonomy.extract_hierarchy(name), lang) + path, append_index = taxonomy.get_path(taxonomy.extract_hierarchy(name), lang, type=type) else: - path, append_index = taxonomy.get_path(name, lang) - return self._postprocess_path(path, lang, always_append_index=append_index, force_extension=force_extension) + path, append_index = taxonomy.get_path(name, lang, type=type) + return self._postprocess_path(path, lang, always_append_index=append_index, force_extension=force_extension, type=type) def _taxonomy_atom_path(self, name, lang, taxonomy): """Return path to a classification Atom feed.""" - return self._taxonomy_path(name, lang, taxonomy, force_extension='.atom') + return self._taxonomy_path(name, lang, taxonomy, type='feed') def _taxonomy_rss_path(self, name, lang, taxonomy): """Return path to a classification RSS feed.""" - return self._taxonomy_path(name, lang, taxonomy, force_extension='.xml') + return self._taxonomy_path(name, lang, taxonomy, type='rss') def _register_path_handlers(self, taxonomy): self.site.register_path_handler('{0}_index'.format(taxonomy.classification_name), lambda name, lang: self._tag_index_path(lang, taxonomy)) From 807da56a2a156dd686710928100e057c42545d4e Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 18:18:30 +0200 Subject: [PATCH 18/74] Fixing metadata. --- nikola/plugins/task/authors.plugin | 2 +- nikola/plugins/task/authors.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nikola/plugins/task/authors.plugin b/nikola/plugins/task/authors.plugin index 3fc4ef27e9..32834527a3 100644 --- a/nikola/plugins/task/authors.plugin +++ b/nikola/plugins/task/authors.plugin @@ -1,5 +1,5 @@ [Core] -Name = render_authors +Name = classify_authors Module = authors [Documentation] diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index 09cc07b07b..37f4da7769 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -33,7 +33,7 @@ class ClassifyAuthors(Taxonomy): - """Render the author pages and feeds.""" + """Classify the posts by authors.""" name = "classify_authors" From ac24eb915bf3e0522dcf701da951791ef6e77c3e Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 18:20:07 +0200 Subject: [PATCH 19/74] Moving section taxonomy into own plugin. --- nikola/plugins/task/indexes.py | 140 ---------------------------- nikola/plugins/task/sections.plugin | 13 +++ nikola/plugins/task/sections.py | 102 ++++++++++++++++++++ 3 files changed, 115 insertions(+), 140 deletions(-) create mode 100644 nikola/plugins/task/sections.plugin create mode 100644 nikola/plugins/task/sections.py diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index 8ecd1decd1..e9bd0456a3 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -47,12 +47,8 @@ class Indexes(Task): def set_site(self, site): """Set Nikola site.""" self.number_of_pages = dict() - self.number_of_pages_section = {lang: dict() for lang in site.config['TRANSLATIONS']} site.register_path_handler('index', self.index_path) site.register_path_handler('index_atom', self.index_atom_path) - site.register_path_handler('section_index', self.index_section_path) - site.register_path_handler('section_index_atom', self.index_section_atom_path) - site.register_path_handler('section_index_rss', self.index_section_rss_path) return super(Indexes, self).set_site(site) def _get_filtered_posts(self, lang, show_untranslated_posts): @@ -116,93 +112,6 @@ def page_path(i, displayed_i, num_pages, force_addition, extension=None): yield self.site.generic_index_renderer(lang, filtered_posts, indexes_title, template_name, context, kw, 'render_indexes', page_link, page_path) - if self.site.config['POSTS_SECTIONS']: - index_len = len(kw['index_file']) - - groups = defaultdict(list) - for p in filtered_posts: - groups[p.section_slug(lang)].append(p) - - # don't build sections when there is only one, aka. default setups - if not len(groups.items()) > 1: - continue - - for section_slug, post_list in groups.items(): - self.number_of_pages_section[lang][section_slug] = self._compute_number_of_pages(post_list, kw['index_display_post_count']) - - def cat_link(i, displayed_i, num_pages, force_addition, extension=None): - feed = "_atom" if extension == ".atom" else "" - return utils.adjust_name_for_index_link(self.site.link("section_index" + feed, section_slug, lang), i, displayed_i, - lang, self.site, force_addition, extension) - - def cat_path(i, displayed_i, num_pages, force_addition, extension=None): - feed = "_atom" if extension == ".atom" else "" - return utils.adjust_name_for_index_path(self.site.path("section_index" + feed, section_slug, lang), i, displayed_i, - lang, self.site, force_addition, extension) - - context = {} - - short_destination = os.path.join(section_slug, kw['index_file']) - link = short_destination.replace('\\', '/') - if kw['strip_indexes'] and link[-(1 + index_len):] == '/' + kw['index_file']: - link = link[:-index_len] - context["permalink"] = link - context["pagekind"] = ["section_page"] - context["description"] = self.site.config['POSTS_SECTION_DESCRIPTIONS'](lang)[section_slug] if section_slug in self.site.config['POSTS_SECTION_DESCRIPTIONS'](lang) else "" - - if self.site.config["POSTS_SECTION_ARE_INDEXES"]: - context["pagekind"].append("index") - posts_section_title = self.site.config['POSTS_SECTION_TITLE'](lang) - - section_title = None - if type(posts_section_title) is dict: - if section_slug in posts_section_title: - section_title = posts_section_title[section_slug] - elif type(posts_section_title) is str: - section_title = posts_section_title - if not section_title: - section_title = post_list[0].section_name(lang) - section_title = section_title.format(name=post_list[0].section_name(lang)) - - task = self.site.generic_index_renderer(lang, post_list, section_title, "sectionindex.tmpl", context, kw, self.name, cat_link, cat_path) - else: - context["pagekind"].append("list") - output_name = os.path.join(kw['output_folder'], section_slug, kw['index_file']) - task = self.site.generic_post_list_renderer(lang, post_list, output_name, "list.tmpl", kw['filters'], context) - task['uptodate'] = [utils.config_changed(kw, 'nikola.plugins.task.indexes')] - task['basename'] = self.name - yield task - - # RSS feed for section - deps = [] - deps_uptodate = [] - if kw["show_untranslated_posts"]: - posts = post_list[:kw['feed_length']] - else: - posts = [x for x in post_list if x.is_translation_available(lang)][:kw['feed_length']] - for post in posts: - deps += post.deps(lang) - deps_uptodate += post.deps_uptodate(lang) - - feed_url = urljoin(self.site.config['BASE_URL'], self.site.link('section_index_rss', section_slug, lang).lstrip('/')) - output_name = os.path.join(kw['output_folder'], self.site.path('section_index_rss', section_slug, lang).lstrip(os.sep)) - task = { - 'basename': self.name, - 'name': os.path.normpath(output_name), - 'file_dep': deps, - 'targets': [output_name], - 'actions': [(utils.generic_rss_renderer, - (lang, kw["blog_title"](lang), kw["site_url"], - context["description"], posts, output_name, - kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], feed_url, - _enclosure, kw["feed_links_append_query"]))], - - 'task_dep': ['render_posts'], - 'clean': True, - 'uptodate': [utils.config_changed(kw, 'nikola.plugins.indexes')] + deps_uptodate, - } - yield task - if not self.site.config["PAGE_INDEX"]: return kw = { @@ -287,37 +196,6 @@ def index_path(self, name, lang, is_feed=False): self.site, extension=extension) - def index_section_path(self, name, lang, is_feed=False, is_rss=False): - """Link to the index for a section. - - Example: - - link://section_index/cars => /cars/index.html - """ - extension = None - - if is_feed: - extension = ".atom" - index_file = os.path.splitext(self.site.config['INDEX_FILE'])[0] + extension - elif is_rss: - index_file = 'rss.xml' - else: - index_file = self.site.config['INDEX_FILE'] - if name in self.number_of_pages_section[lang]: - number_of_pages = self.number_of_pages_section[lang][name] - else: - posts = [post for post in self._get_filtered_posts(lang, self.site.config['SHOW_UNTRANSLATED_POSTS']) if post.section_slug(lang) == name] - number_of_pages = self._compute_number_of_pages(posts, self.site.config['INDEX_DISPLAY_POST_COUNT']) - self.number_of_pages_section[lang][name] = number_of_pages - return utils.adjust_name_for_index_path_list([_f for _f in [self.site.config['TRANSLATIONS'][lang], - name, - index_file] if _f], - None, - utils.get_displayed_page_number(None, number_of_pages, self.site), - lang, - self.site, - extension=extension) - def index_atom_path(self, name, lang): """Link to a numbered Atom index. @@ -326,21 +204,3 @@ def index_atom_path(self, name, lang): link://index_atom/3 => /index-3.atom """ return self.index_path(name, lang, is_feed=True) - - def index_section_atom_path(self, name, lang): - """Link to the Atom index for a section. - - Example: - - link://section_index_atom/cars => /cars/index.atom - """ - return self.index_section_path(name, lang, is_feed=True) - - def index_section_rss_path(self, name, lang): - """Link to the RSS feed for a section. - - Example: - - link://section_index_rss/cars => /cars/rss.xml - """ - return self.index_section_path(name, lang, is_rss=True) diff --git a/nikola/plugins/task/sections.plugin b/nikola/plugins/task/sections.plugin new file mode 100644 index 0000000000..57de4ec066 --- /dev/null +++ b/nikola/plugins/task/sections.plugin @@ -0,0 +1,13 @@ +[Core] +name = classify_sections +module = sections + +[Documentation] +author = Roberto Alsina +version = 1.0 +website = https://getnikola.com/ +description = Generates the blog's index pages. + +[Nikola] +plugincategory = Task + diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py new file mode 100644 index 0000000000..cc507be9af --- /dev/null +++ b/nikola/plugins/task/sections.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2012-2016 Roberto Alsina and others. + +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the +# Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice +# shall be included in all copies or substantial portions of +# the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +"""Render the blog indexes.""" + +from __future__ import unicode_literals + +from nikola.plugin_categories import Taxonomy + + +class Indexes(Taxonomy): + """Classify the posts by sections.""" + + name = "classify_sections" + + classification_name = "section_index" + metadata_name = "section" + overview_page_variable_name = "sections" + more_than_one_classifications_per_post = False + has_hierarchy = False + template_for_list_of_one_classification = None + apply_to_posts = True + apply_to_pages = False + omit_empty_classifications = True + also_create_classifications_from_other_languages = False + + def set_site(self, site): + """Set Nikola site.""" + self.show_list_as_index = self.site.config["POSTS_SECTION_ARE_INDEXES"] + self.template_for_classification_overview = "sectionindex.tmpl" if self.show_list_as_index else "list.tmpl" + return super(Indexes, self).set_site(site) + + def is_enabled(self): + """Return True if this taxonomy is enabled, or False otherwise.""" + return self.site.config['POSTS_SECTIONS'] + + def classify(self, post, lang): + """Classify the given post for the given language.""" + raise [post.section_slug(lang)] + + def get_path(self, section, lang): + """A path handler for the given classification.""" + return [_f for _f in [self.site.config['TRANSLATIONS'][lang], section] if _f], True + + def provide_context_and_uptodate(self, section, lang): + """Provide data for the context and the uptodate list for the list of the given classifiation. + + Must return a tuple of two dicts. The first is merged into the page's context, + the second will be put into the uptodate list of all generated tasks. + + Context must contain `title`, which should be something like 'Posts about ', + and `classification_title`, which should be related to the classification string. + """ + kw = { + "messages": self.site.MESSAGES, + } + # Check whether we have a name for this section + if section in self.config['POSTS_SECTION_NAME'](lang): + section_name = self.config['POSTS_SECTION_NAME'](lang)[section] + else: + section_name = section.replace('-', ' ').title() + # Compose section title + section_title = section_name + posts_section_title = self.site.config['POSTS_SECTION_TITLE'](lang) + if type(posts_section_title) is dict: + if section in posts_section_title: + section_title = posts_section_title[section] + elif type(posts_section_title) is str: + section_title = posts_section_title + section_title = section_title.format(name=section_name) + # Compose context + context = { + "title": section_title, + "classification_title": section_name, + "description": self.site.config['POSTS_SECTION_DESCRIPTIONS'](lang)[section] if section in self.site.config['POSTS_SECTION_DESCRIPTIONS'](lang) else "", + "pagekind": ["section_page", "index" if self.show_list_as_index else "list"] + } + kw.update(context) + return context, kw From afc1147e758e11e2d07aec389b47c09d51e2c3f8 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 18:24:09 +0200 Subject: [PATCH 20/74] Fixing usage of getPluginsOfCategory. --- nikola/plugins/misc/taxonomies_classifier.py | 4 ++-- nikola/plugins/task/taxonomies.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 6e63eccef1..f079de05d0 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -44,7 +44,7 @@ class TaxonomiesClassifier(SignalHandler): name = "render_taxonomies" def _do_classification(self, site): - taxonomies = site.plugin_manager.getPluginsOfCategory('Taxonomy') + taxonomies = [p.plugin_object for p in site.plugin_manager.getPluginsOfCategory('Taxonomy')] site.posts_per_classification = {} for taxonomy in taxonomies: if taxonomy.classification_name in site.posts_per_classification: @@ -226,5 +226,5 @@ def set_site(self, site): # Add hook for after post scanning blinker.signal("scanned").connect(self._do_classification) # Register path handlers - for taxonomy in self.site.plugin_manager.getPluginsOfCategory('Taxonomy'): + for taxonomy in [p.plugin_object for p in site.plugin_manager.getPluginsOfCategory('Taxonomy')]: self._register_path_handlers(taxonomy) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index a8f8f1b71d..f4bc95594d 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -238,7 +238,7 @@ def gen_tasks(self): self.site.scan_posts() yield self.group_task() - for taxonomy in self.site.plugin_manager.getPluginsOfCategory('Taxonomy'): + for taxonomy in [p.plugin_object for p in self.site.plugin_manager.getPluginsOfCategory('Taxonomy')]: # Should this taxonomy be considered after all? if not taxonomy.is_enabled(): continue From 5c7bcb9412abcde38f1a59d41e3834008f40b483 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 18:24:21 +0200 Subject: [PATCH 21/74] Fixing name and reference-before-assign bug. --- nikola/plugins/task/sections.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py index cc507be9af..c00989169a 100644 --- a/nikola/plugins/task/sections.py +++ b/nikola/plugins/task/sections.py @@ -31,7 +31,7 @@ from nikola.plugin_categories import Taxonomy -class Indexes(Taxonomy): +class ClassifySections(Taxonomy): """Classify the posts by sections.""" name = "classify_sections" @@ -49,9 +49,9 @@ class Indexes(Taxonomy): def set_site(self, site): """Set Nikola site.""" - self.show_list_as_index = self.site.config["POSTS_SECTION_ARE_INDEXES"] + self.show_list_as_index = site.config["POSTS_SECTION_ARE_INDEXES"] self.template_for_classification_overview = "sectionindex.tmpl" if self.show_list_as_index else "list.tmpl" - return super(Indexes, self).set_site(site) + return super(ClassifySections, self).set_site(site) def is_enabled(self): """Return True if this taxonomy is enabled, or False otherwise.""" From 94845e28db94e014540cdd9beec389087f216cfb Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 18:25:02 +0200 Subject: [PATCH 22/74] Fixed two stupid typos. --- nikola/plugins/misc/taxonomies_classifier.py | 2 +- nikola/plugins/task/sections.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index f079de05d0..292d6617c3 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -50,7 +50,7 @@ def _do_classification(self, site): if taxonomy.classification_name in site.posts_per_classification: raise Exception("Found more than one taxonomy with classification name '{}'!".format(taxonomy.classification_name)) site.posts_per_classification[taxonomy.classification_name] = { - lang: defaultdict(set) for lang in self.config['TRANSLATIONS'].keys() + lang: defaultdict(set) for lang in site.config['TRANSLATIONS'].keys() } # Classify posts diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py index c00989169a..9fddbf5311 100644 --- a/nikola/plugins/task/sections.py +++ b/nikola/plugins/task/sections.py @@ -59,7 +59,7 @@ def is_enabled(self): def classify(self, post, lang): """Classify the given post for the given language.""" - raise [post.section_slug(lang)] + return [post.section_slug(lang)] def get_path(self, section, lang): """A path handler for the given classification.""" From e1e474a87d15550aafffadbfde6b912e440be690 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 18:35:38 +0200 Subject: [PATCH 23/74] Fixing some more bugs. --- nikola/plugins/misc/taxonomies_classifier.py | 25 +++++++------------- nikola/plugins/task/authors.py | 6 ++--- nikola/plugins/task/sections.py | 10 ++++---- nikola/plugins/task/taxonomies.py | 4 +++- 4 files changed, 19 insertions(+), 26 deletions(-) diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 292d6617c3..4c1183c87a 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -93,7 +93,7 @@ def _do_classification(self, site): utils.LOGGER.error("{0} {1} yields invalid path '{2}'!".format(taxonomy.classification_name.title(), classification, '/'.join(path))) quit = True # Combine path - path = os.path.join([os.path.normpath(p) for p in path if p != '.']) + path = os.path.join(*[os.path.normpath(p) for p in path if p != '.']) # Determine collisions if path in taxonomy_outputs[lang]: other_classification_name, other_classification, other_posts = taxonomy_outputs[lang][path] @@ -165,18 +165,9 @@ def create_hierarchy(cat_hierarchy, parent=None): site.flat_hierarchy_per_classification[taxonomy.classification_name], site.hierarchy_lookup_per_classification[taxonomy.classification_name]) else: - taxonomy.postprocess_posts_per_classification(site.posts_per_classification[taxonomy.classification_name], flat_hierarchy, hierarchy_lookup) + taxonomy.postprocess_posts_per_classification(site.posts_per_classification[taxonomy.classification_name]) - # Postprocessing - for taxonomy in taxonomies: - for lang, posts_per_classification in site.posts_per_classification[taxonomy.classification_name].items(): - taxonomy.postprocess_posts_per_classification( - posts_per_classification, - site.flat_hierarchy_per_classification.get(taxonomy.classification_name, {}).get(lang, None), - site.hierarchy_lookup_per_classification.get(taxonomy.classification_name, {}).get(lang, None), - ) - - def _postprocess_path(self, path, lang, always_append_index=False, force_extension=None): + def _postprocess_path(self, path, lang, always_append_index=False, force_extension=None, type='page'): if type == 'feed': force_extension = '.atom' elif type == 'rss': @@ -200,7 +191,7 @@ def _taxonomy_index_path(self, lang, taxonomy): def _taxonomy_path(self, name, lang, taxonomy, force_extension=None, type='page'): """Return path to a classification.""" - if taxonomy.has_hirearchy: + if taxonomy.has_hierarchy: path, append_index = taxonomy.get_path(taxonomy.extract_hierarchy(name), lang, type=type) else: path, append_index = taxonomy.get_path(name, lang, type=type) @@ -215,10 +206,10 @@ def _taxonomy_rss_path(self, name, lang, taxonomy): return self._taxonomy_path(name, lang, taxonomy, type='rss') def _register_path_handlers(self, taxonomy): - self.site.register_path_handler('{0}_index'.format(taxonomy.classification_name), lambda name, lang: self._tag_index_path(lang, taxonomy)) - self.site.register_path_handler('{0}'.format(taxonomy.classification_name), lambda name, lang: self._tag_path(name, lang, taxonomy)) - self.site.register_path_handler('{0}_atom'.format(taxonomy.classification_name), lambda name, lang: self._tag_atom_path(name, lang, taxonomy)) - self.site.register_path_handler('{0}_rss'.format(taxonomy.classification_name), lambda name, lang: self._tag_rss_path(name, lang, taxonomy)) + self.site.register_path_handler('{0}_index'.format(taxonomy.classification_name), lambda name, lang: self._taxonomy_index_path(lang, taxonomy)) + self.site.register_path_handler('{0}'.format(taxonomy.classification_name), lambda name, lang: self._taxonomy_path(name, lang, taxonomy)) + self.site.register_path_handler('{0}_atom'.format(taxonomy.classification_name), lambda name, lang: self._taxonomy_atom_path(name, lang, taxonomy)) + self.site.register_path_handler('{0}_rss'.format(taxonomy.classification_name), lambda name, lang: self._taxonomy_rss_path(name, lang, taxonomy)) def set_site(self, site): """Set site, which is a Nikola instance.""" diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index 37f4da7769..e7dc676515 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -63,11 +63,11 @@ def classify(self, post, lang): """Classify the given post for the given language.""" return [post.author()] - def get_list_path(self, lang): + def get_list_path(self, lang, type='page'): """A path handler for the list of all classifications.""" return [self.site.config['AUTHOR_PATH']], True - def get_path(self, author, lang): + def get_path(self, author, lang, type='page'): """A path handler for the given classification.""" if self.site.config['SLUG_AUTHOR_PATH']: slug = utils.slugify(author, lang) @@ -107,7 +107,7 @@ def provide_context_and_uptodate(self, author, lang): def postprocess_posts_per_classification(self, posts_per_author_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None): """Rearrange, modify or otherwise use the list of posts per classification and per language.""" more_than_one = False - for lang, posts_per_author in posts_per_author_per_language.item(): + for lang, posts_per_author in posts_per_author_per_language.items(): if len(posts_per_author.keys()) > 1: more_than_one = True self.generate_author_pages = self.site.config["ENABLE_AUTHOR_PAGES"] and more_than_one diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py index 9fddbf5311..1e39723710 100644 --- a/nikola/plugins/task/sections.py +++ b/nikola/plugins/task/sections.py @@ -41,7 +41,7 @@ class ClassifySections(Taxonomy): overview_page_variable_name = "sections" more_than_one_classifications_per_post = False has_hierarchy = False - template_for_list_of_one_classification = None + template_for_classification_overview = None apply_to_posts = True apply_to_pages = False omit_empty_classifications = True @@ -50,7 +50,7 @@ class ClassifySections(Taxonomy): def set_site(self, site): """Set Nikola site.""" self.show_list_as_index = site.config["POSTS_SECTION_ARE_INDEXES"] - self.template_for_classification_overview = "sectionindex.tmpl" if self.show_list_as_index else "list.tmpl" + self.template_for_list_of_one_classification = "sectionindex.tmpl" if self.show_list_as_index else "list.tmpl" return super(ClassifySections, self).set_site(site) def is_enabled(self): @@ -61,7 +61,7 @@ def classify(self, post, lang): """Classify the given post for the given language.""" return [post.section_slug(lang)] - def get_path(self, section, lang): + def get_path(self, section, lang, type='page'): """A path handler for the given classification.""" return [_f for _f in [self.site.config['TRANSLATIONS'][lang], section] if _f], True @@ -78,8 +78,8 @@ def provide_context_and_uptodate(self, section, lang): "messages": self.site.MESSAGES, } # Check whether we have a name for this section - if section in self.config['POSTS_SECTION_NAME'](lang): - section_name = self.config['POSTS_SECTION_NAME'](lang)[section] + if section in self.site.config['POSTS_SECTION_NAME'](lang): + section_name = self.site.config['POSTS_SECTION_NAME'](lang)[section] else: section_name = section.replace('-', ' ').title() # Compose section title diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index f4bc95594d..2f1bb280f1 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -70,6 +70,7 @@ def _generate_classification_overview(self, taxonomy, lang): kw = copy(kw) kw['filters'] = self.site.config['FILTERS'] kw["minimum_post_count"] = taxonomy.minimum_post_count_per_classification_in_overview + kw["output_folder"] = self.site.config['OUTPUT_FOLDER'] # Collect all relevant classifications if taxonomy.has_hierarchy: @@ -224,6 +225,7 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan kw["feed_plain"] = self.site.config["FEED_PLAIN"] kw["feed_link_append_query"] = self.site.config["FEED_LINKS_APPEND_QUERY"] kw["feed_length"] = self.site.config['FEED_LENGTH'] + kw["output_folder"] = self.site.config['OUTPUT_FOLDER'] # Generate RSS feed if kw["generate_rss"]: yield self._generate_classification_page_as_rss(taxonomy, classification, filtered_posts, context['title'], kw, lang) @@ -244,7 +246,7 @@ def gen_tasks(self): continue for lang in self.site.config["TRANSLATIONS"]: # Generate list of classifications (i.e. classification overview) - if taxonomy.template_for_list_of_one_classification is not None: + if taxonomy.template_for_classification_overview is not None: for task in self._generate_classification_overview(taxonomy, lang): yield task From 7f86e79bce0e38a0bcac31f8397b8952b8b2d1b1 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 07:37:15 +0200 Subject: [PATCH 24/74] Allow to not store classification into metadata. --- nikola/plugin_categories.py | 1 + nikola/plugins/misc/taxonomies_classifier.py | 9 +++++---- nikola/plugins/task/authors.py | 2 +- nikola/plugins/task/sections.py | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 536c3d3048..f733d0203d 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -468,6 +468,7 @@ class Taxonomy(BasePlugin): # The classification name to be used for path handlers. classification_name = "taxonomy" # The classification name to be used when storing the classification + # in the metadata. If set to None, the classification won't be stored # in the metadata. metadata_name = "taxonomy" # Variable for the overview page template which contains the list of diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 4c1183c87a..33f2564a51 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -63,10 +63,11 @@ def _do_classification(self, site): classifications[lang] = taxonomy.classify(post, lang) assert taxonomy.more_than_one_classifications_per_post or len(classifications[lang]) <= 1 # Store in metadata - if taxonomy.more_than_one_classifications_per_post: - post.meta[lang][taxonomy.metadata_name] = classifications[lang] - else: - post.meta[lang][taxonomy.metadata_name] = classifications[lang][0] if len(classifications[lang]) > 0 else None + if taxonomy.metadata_name is not None: + if taxonomy.more_than_one_classifications_per_post: + post.meta[lang][taxonomy.metadata_name] = classifications[lang] + else: + post.meta[lang][taxonomy.metadata_name] = classifications[lang][0] if len(classifications[lang]) > 0 else None # Add post to sets for classification in classifications[lang]: while classification: diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index e7dc676515..1118dde7bd 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -38,7 +38,7 @@ class ClassifyAuthors(Taxonomy): name = "classify_authors" classification_name = "author" - metadata_name = "author" + metadata_name = None overview_page_variable_name = "authors" more_than_one_classifications_per_post = False has_hierarchy = False diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py index 1e39723710..be6ebf0a22 100644 --- a/nikola/plugins/task/sections.py +++ b/nikola/plugins/task/sections.py @@ -37,7 +37,7 @@ class ClassifySections(Taxonomy): name = "classify_sections" classification_name = "section_index" - metadata_name = "section" + metadata_name = None overview_page_variable_name = "sections" more_than_one_classifications_per_post = False has_hierarchy = False From f95aaa9442980276529516ae267b70dcafbc0719 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 07:47:54 +0200 Subject: [PATCH 25/74] Allowing to enable/disable taxonomies based on stage and language. --- nikola/plugin_categories.py | 8 +++++--- nikola/plugins/misc/taxonomies_classifier.py | 4 ++-- nikola/plugins/task/authors.py | 2 +- nikola/plugins/task/sections.py | 15 +++++++++++++-- nikola/plugins/task/taxonomies.py | 2 ++ 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index f733d0203d..d625d49c93 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -507,11 +507,13 @@ class Taxonomy(BasePlugin): # pages. also_create_classifications_from_other_languages = True - def is_enabled(self): + def is_enabled(self, lang=None): """Return True if this taxonomy is enabled, or False otherwise. - Enabled means that the overview page and the classification lists - are created. + If lang is None, this determins whether the classification is + made at all. If lang is not None, this determines whether the + overview page and the classification lists are created for this + language. """ return True diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 33f2564a51..9f08a7fca5 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -44,7 +44,7 @@ class TaxonomiesClassifier(SignalHandler): name = "render_taxonomies" def _do_classification(self, site): - taxonomies = [p.plugin_object for p in site.plugin_manager.getPluginsOfCategory('Taxonomy')] + taxonomies = [p.plugin_object for p in site.plugin_manager.getPluginsOfCategory('Taxonomy') if p.plugin_object.is_enabled()] site.posts_per_classification = {} for taxonomy in taxonomies: if taxonomy.classification_name in site.posts_per_classification: @@ -218,5 +218,5 @@ def set_site(self, site): # Add hook for after post scanning blinker.signal("scanned").connect(self._do_classification) # Register path handlers - for taxonomy in [p.plugin_object for p in site.plugin_manager.getPluginsOfCategory('Taxonomy')]: + for taxonomy in [p.plugin_object for p in site.plugin_manager.getPluginsOfCategory('Taxonomy') if p.plugin_object.is_enabled()]: self._register_path_handlers(taxonomy) diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index 1118dde7bd..071636edcb 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -55,7 +55,7 @@ def set_site(self, site): self.template_for_list_of_one_classification = "authorindex.tmpl" if self.show_list_as_index else "author.tmpl" return super(ClassifyAuthors, self).set_site(site) - def is_enabled(self): + def is_enabled(self, lang=None): """Return True if this taxonomy is enabled, or False otherwise.""" return self.site.config["ENABLE_AUTHOR_PAGES"] diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py index be6ebf0a22..64b372d897 100644 --- a/nikola/plugins/task/sections.py +++ b/nikola/plugins/task/sections.py @@ -51,11 +51,16 @@ def set_site(self, site): """Set Nikola site.""" self.show_list_as_index = site.config["POSTS_SECTION_ARE_INDEXES"] self.template_for_list_of_one_classification = "sectionindex.tmpl" if self.show_list_as_index else "list.tmpl" + self.enable_for_lang = {} return super(ClassifySections, self).set_site(site) - def is_enabled(self): + def is_enabled(self, lang=None): """Return True if this taxonomy is enabled, or False otherwise.""" - return self.site.config['POSTS_SECTIONS'] + if not self.site.config['POSTS_SECTIONS']: + return False + if lang is not None: + return self.enable_for_lang.get(lang, False) + return True def classify(self, post, lang): """Classify the given post for the given language.""" @@ -100,3 +105,9 @@ def provide_context_and_uptodate(self, section, lang): } kw.update(context) return context, kw + + def postprocess_posts_per_classification(self, posts_per_section_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None): + """Rearrange, modify or otherwise use the list of posts per classification and per language.""" + for lang, posts_per_section in posts_per_section_per_language.items(): + # don't build sections when there is only one, aka. default setups + self.enable_for_lang[lang] = (len(posts_per_section.keys()) > 1) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 2f1bb280f1..2b413511ef 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -245,6 +245,8 @@ def gen_tasks(self): if not taxonomy.is_enabled(): continue for lang in self.site.config["TRANSLATIONS"]: + if not taxonomy.is_enabled(lang): + continue # Generate list of classifications (i.e. classification overview) if taxonomy.template_for_classification_overview is not None: for task in self._generate_classification_overview(taxonomy, lang): From 3e95d10166a0dac9052439d065ad0ae0f02faa2d Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 07:55:36 +0200 Subject: [PATCH 26/74] Fixed bugs. --- nikola/plugins/task/authors.py | 1 + nikola/plugins/task/taxonomies.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index 071636edcb..520b687776 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -96,6 +96,7 @@ def provide_context_and_uptodate(self, author, lang): "messages": self.site.MESSAGES, } context = { + "author": author, "title": kw["messages"][lang]["Posts by %s"] % author, "classification_title": author, "description": descriptions[lang][author] if lang in descriptions and author in descriptions[lang] else None, diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 2b413511ef..32de0e100e 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -188,7 +188,7 @@ def _generate_classification_page_as_list_atom(self, taxonomy, classification, f def _generate_classification_page_as_list(self, taxonomy, classification, filtered_posts, context, kw, lang): """Render a single flat link list with this classification's posts.""" kind = taxonomy.classification_name - template_name = "tag.tmpl" + template_name = taxonomy.template_for_list_of_one_classification output_name = os.path.join(self.site.config['OUTPUT_FOLDER'], self.site.path(kind, classification, lang)) context = copy(context) context["lang"] = lang From 78167512c0b287e211a625cf872c3dcd3c072260 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 08:00:14 +0200 Subject: [PATCH 27/74] Considering only relevant posts. --- nikola/plugins/task/authors.py | 8 +++++++- nikola/plugins/task/sections.py | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index 520b687776..3186a4528a 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -109,7 +109,13 @@ def postprocess_posts_per_classification(self, posts_per_author_per_language, fl """Rearrange, modify or otherwise use the list of posts per classification and per language.""" more_than_one = False for lang, posts_per_author in posts_per_author_per_language.items(): - if len(posts_per_author.keys()) > 1: + authors = set() + for author, posts in posts_per_author.items(): + for post in posts: + if not self.site.config["SHOW_UNTRANSLATED_POSTS"] and not post.is_translation_available(lang): + continue + authors.add(author) + if len(authors) > 1: more_than_one = True self.generate_author_pages = self.site.config["ENABLE_AUTHOR_PAGES"] and more_than_one self.site.GLOBAL_CONTEXT["author_pages_generated"] = self.generate_author_pages diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py index 64b372d897..4ecb5ffbdc 100644 --- a/nikola/plugins/task/sections.py +++ b/nikola/plugins/task/sections.py @@ -109,5 +109,11 @@ def provide_context_and_uptodate(self, section, lang): def postprocess_posts_per_classification(self, posts_per_section_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None): """Rearrange, modify or otherwise use the list of posts per classification and per language.""" for lang, posts_per_section in posts_per_section_per_language.items(): - # don't build sections when there is only one, aka. default setups - self.enable_for_lang[lang] = (len(posts_per_section.keys()) > 1) + # Don't build sections when there is only one, a.k.a. default setups + sections = set() + for section, posts in posts_per_section.items(): + for post in posts: + if not self.site.config["SHOW_UNTRANSLATED_POSTS"] and not post.is_translation_available(lang): + continue + sections.add(section) + self.enable_for_lang[lang] = (len(sections) > 1) From 99270eeddcc1cc1dd200e1a4f5dccf27f6465048 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 08:05:36 +0200 Subject: [PATCH 28/74] Splitting PAGE_INDEX capabilities into own plugin. --- nikola/plugins/task/indexes.py | 63 --------------- nikola/plugins/task/page_index.plugin | 12 +++ nikola/plugins/task/page_index.py | 106 ++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 63 deletions(-) create mode 100644 nikola/plugins/task/page_index.plugin create mode 100644 nikola/plugins/task/page_index.py diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index e9bd0456a3..a39b03ec9f 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -27,16 +27,10 @@ """Render the blog indexes.""" from __future__ import unicode_literals -from collections import defaultdict import os -try: - from urlparse import urljoin -except ImportError: - from urllib.parse import urljoin # NOQA from nikola.plugin_categories import Task from nikola import utils -from nikola.nikola import _enclosure class Indexes(Task): @@ -112,63 +106,6 @@ def page_path(i, displayed_i, num_pages, force_addition, extension=None): yield self.site.generic_index_renderer(lang, filtered_posts, indexes_title, template_name, context, kw, 'render_indexes', page_link, page_path) - if not self.site.config["PAGE_INDEX"]: - return - kw = { - "translations": self.site.config['TRANSLATIONS'], - "post_pages": self.site.config["post_pages"], - "output_folder": self.site.config['OUTPUT_FOLDER'], - "filters": self.site.config['FILTERS'], - "index_file": self.site.config['INDEX_FILE'], - "strip_indexes": self.site.config['STRIP_INDEXES'], - } - template_name = "list.tmpl" - index_len = len(kw['index_file']) - for lang in kw["translations"]: - # Need to group by folder to avoid duplicated tasks (Issue #758) - # Group all pages by path prefix - groups = defaultdict(list) - for p in self.site.timeline: - if not p.is_post: - destpath = p.destination_path(lang) - if destpath[-(1 + index_len):] == '/' + kw['index_file']: - destpath = destpath[:-(1 + index_len)] - dirname = os.path.dirname(destpath) - groups[dirname].append(p) - for dirname, post_list in groups.items(): - context = {} - context["items"] = [] - should_render = True - output_name = os.path.join(kw['output_folder'], dirname, kw['index_file']) - short_destination = os.path.join(dirname, kw['index_file']) - link = short_destination.replace('\\', '/') - if kw['strip_indexes'] and link[-(1 + index_len):] == '/' + kw['index_file']: - link = link[:-index_len] - context["permalink"] = link - context["pagekind"] = ["list"] - if dirname == "/": - context["pagekind"].append("front_page") - - for post in post_list: - # If there is an index.html pending to be created from - # a page, do not generate the PAGE_INDEX - if post.destination_path(lang) == short_destination: - should_render = False - else: - context["items"].append((post.title(lang), - post.permalink(lang), - None)) - - if should_render: - task = self.site.generic_post_list_renderer(lang, post_list, - output_name, - template_name, - kw['filters'], - context) - task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.indexes')] - task['basename'] = self.name - yield task - def index_path(self, name, lang, is_feed=False): """Link to a numbered index. diff --git a/nikola/plugins/task/page_index.plugin b/nikola/plugins/task/page_index.plugin new file mode 100644 index 0000000000..1e728291c9 --- /dev/null +++ b/nikola/plugins/task/page_index.plugin @@ -0,0 +1,12 @@ +[Core] +name = render_page_index +module = page_index + +[Documentation] +author = Roberto Alsina +version = 1.0 +website = https://getnikola.com/ +description = Generates the blog's index pages. + +[Nikola] +plugincategory = Task diff --git a/nikola/plugins/task/page_index.py b/nikola/plugins/task/page_index.py new file mode 100644 index 0000000000..f3d6a1684b --- /dev/null +++ b/nikola/plugins/task/page_index.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2012-2016 Roberto Alsina and others. + +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the +# Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice +# shall be included in all copies or substantial portions of +# the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +"""Render the blog indexes.""" + +from __future__ import unicode_literals +from collections import defaultdict +import os + +from nikola.plugin_categories import Task +from nikola import utils + + +class PageIndex(Task): + """Render the page index.""" + + name = "render_page_index" + + def set_site(self, site): + """Set Nikola site.""" + return super(PageIndex, self).set_site(site) + + def gen_tasks(self): + """Render the blog indexes.""" + self.site.scan_posts() + yield self.group_task() + + if not self.site.config["PAGE_INDEX"]: + return + kw = { + "translations": self.site.config['TRANSLATIONS'], + "post_pages": self.site.config["post_pages"], + "output_folder": self.site.config['OUTPUT_FOLDER'], + "filters": self.site.config['FILTERS'], + "index_file": self.site.config['INDEX_FILE'], + "strip_indexes": self.site.config['STRIP_INDEXES'], + } + template_name = "list.tmpl" + index_len = len(kw['index_file']) + for lang in kw["translations"]: + # Need to group by folder to avoid duplicated tasks (Issue #758) + # Group all pages by path prefix + groups = defaultdict(list) + for p in self.site.timeline: + if not p.is_post: + destpath = p.destination_path(lang) + if destpath[-(1 + index_len):] == '/' + kw['index_file']: + destpath = destpath[:-(1 + index_len)] + dirname = os.path.dirname(destpath) + groups[dirname].append(p) + for dirname, post_list in groups.items(): + context = {} + context["items"] = [] + should_render = True + output_name = os.path.join(kw['output_folder'], dirname, kw['index_file']) + short_destination = os.path.join(dirname, kw['index_file']) + link = short_destination.replace('\\', '/') + if kw['strip_indexes'] and link[-(1 + index_len):] == '/' + kw['index_file']: + link = link[:-index_len] + context["permalink"] = link + context["pagekind"] = ["list"] + if dirname == "/": + context["pagekind"].append("front_page") + + for post in post_list: + # If there is an index.html pending to be created from + # a page, do not generate the PAGE_INDEX + if post.destination_path(lang) == short_destination: + should_render = False + else: + context["items"].append((post.title(lang), + post.permalink(lang), + None)) + + if should_render: + task = self.site.generic_post_list_renderer(lang, post_list, + output_name, + template_name, + kw['filters'], + context) + task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.indexes')] + task['basename'] = self.name + yield task From 03830993b7f20db35819bc3e69f743ca1c75c63d Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 08:29:32 +0200 Subject: [PATCH 29/74] Forgot some logic. --- nikola/plugins/task/authors.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index 3186a4528a..57c2594ec8 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -57,7 +57,11 @@ def set_site(self, site): def is_enabled(self, lang=None): """Return True if this taxonomy is enabled, or False otherwise.""" - return self.site.config["ENABLE_AUTHOR_PAGES"] + if not self.site.config["ENABLE_AUTHOR_PAGES"]: + return False + if lang is not None: + return self.generate_author_pages + return True def classify(self, post, lang): """Classify the given post for the given language.""" From 70e030224591cf843d5d0cb1b13ccb570fba37bd Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 08:31:01 +0200 Subject: [PATCH 30/74] Updated metadata. --- nikola/plugins/task/authors.plugin | 2 ++ nikola/plugins/task/sections.plugin | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/nikola/plugins/task/authors.plugin b/nikola/plugins/task/authors.plugin index 32834527a3..1e10cb1964 100644 --- a/nikola/plugins/task/authors.plugin +++ b/nikola/plugins/task/authors.plugin @@ -8,3 +8,5 @@ Version = 0.1 Website = http://getnikola.com Description = Render the author pages and feeds. +[Nikola] +plugincategory = Taxonomy diff --git a/nikola/plugins/task/sections.plugin b/nikola/plugins/task/sections.plugin index 57de4ec066..146f2f82df 100644 --- a/nikola/plugins/task/sections.plugin +++ b/nikola/plugins/task/sections.plugin @@ -9,5 +9,4 @@ website = https://getnikola.com/ description = Generates the blog's index pages. [Nikola] -plugincategory = Task - +plugincategory = Taxonomy From 3fef949503ff449145e6970d6a600354ff4f45e4 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 16 Oct 2016 12:18:54 +0200 Subject: [PATCH 31/74] Fixing missing lang parameter. --- nikola/plugins/task/tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugins/task/tags.py b/nikola/plugins/task/tags.py index 8b4683e8aa..6ee0e4a987 100644 --- a/nikola/plugins/task/tags.py +++ b/nikola/plugins/task/tags.py @@ -217,7 +217,7 @@ def _create_tags_page(self, kw, lang, include_tags=True, include_categories=True context["cat_items"] = [(tag, self.site.link("category", tag, lang)) for tag in categories] context['cat_hierarchy'] = [(node.name, node.category_name, node.category_path, - self.site.link("category", node.category_name), + self.site.link("category", node.category_name, lang), node.indent_levels, node.indent_change_before, node.indent_change_after) for node in self.site.category_hierarchy] From c9324d2411f119d4e5d1cb84aba8c6ea0f35cedd Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 17:32:11 +0200 Subject: [PATCH 32/74] Allowing to disable specific classification lists. --- nikola/plugin_categories.py | 4 ++++ nikola/plugins/task/taxonomies.py | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index d625d49c93..5bc7c6e33d 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -611,6 +611,10 @@ def provide_context_and_uptodate(self, classification, lang): """ raise NotImplementedError() + def should_generate_classification_list(self, classification, post_list, lang): + """Only generates list of posts for classification if this function returns True.""" + raise True + def postprocess_posts_per_classification(self, posts_per_classification_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None): """Rearrange, modify or otherwise use the list of posts per classification and per language. diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 32de0e100e..9ef84c0397 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -190,10 +190,8 @@ def _generate_classification_page_as_list(self, taxonomy, classification, filter kind = taxonomy.classification_name template_name = taxonomy.template_for_list_of_one_classification output_name = os.path.join(self.site.config['OUTPUT_FOLDER'], self.site.path(kind, classification, lang)) - context = copy(context) context["lang"] = lang context["posts"] = filtered_posts - context["permalink"] = self.site.link(kind, classification, lang) context["kind"] = kind if "pagekind" not in context: context["pagekind"] = ["list", "tag_page"] @@ -214,6 +212,9 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan filtered_posts = [x for x in post_list if x.is_translation_available(lang)] if len(filtered_posts) == 0 and taxonomy.omit_empty_classifications: return + # Should we create this list? + if not taxonomy.generate_classification_list(classification, filtered_posts, lang): + return # Get data context, kw = taxonomy.provide_context_and_uptodate(classification, lang) kw = copy(kw) @@ -226,6 +227,8 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan kw["feed_link_append_query"] = self.site.config["FEED_LINKS_APPEND_QUERY"] kw["feed_length"] = self.site.config['FEED_LENGTH'] kw["output_folder"] = self.site.config['OUTPUT_FOLDER'] + context = copy(context) + context["permalink"] = self.site.link(taxonomy.classification_name, classification, lang) # Generate RSS feed if kw["generate_rss"]: yield self._generate_classification_page_as_rss(taxonomy, classification, filtered_posts, context['title'], kw, lang) From 745c1799745765655c3275ed887cdbbc03de089e Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 17:36:33 +0200 Subject: [PATCH 33/74] Comment at wrong place. --- nikola/plugin_categories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 5bc7c6e33d..0a1950380c 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -488,9 +488,9 @@ class Taxonomy(BasePlugin): # as a post list. show_list_as_index = False # The template to use for the post list for one classification. - # Set to none to avoid generating overviews. template_for_list_of_one_classification = "tagindex.tmpl" # The template to use for the classification overview page. + # Set to None to avoid generating overviews. template_for_classification_overview = "list.tmpl" # Whether this classification applies to posts. apply_to_posts = True From 9dc449a19185c010ad460663a87d0c1731cac62d Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 18:27:20 +0200 Subject: [PATCH 34/74] Converted page_index plugin. --- nikola/plugins/task/page_index.plugin | 4 +- nikola/plugins/task/page_index.py | 123 ++++++++++++-------------- 2 files changed, 60 insertions(+), 67 deletions(-) diff --git a/nikola/plugins/task/page_index.plugin b/nikola/plugins/task/page_index.plugin index 1e728291c9..416e18eace 100644 --- a/nikola/plugins/task/page_index.plugin +++ b/nikola/plugins/task/page_index.plugin @@ -1,5 +1,5 @@ [Core] -name = render_page_index +name = classify_page_index module = page_index [Documentation] @@ -9,4 +9,4 @@ website = https://getnikola.com/ description = Generates the blog's index pages. [Nikola] -plugincategory = Task +plugincategory = Taxonomy diff --git a/nikola/plugins/task/page_index.py b/nikola/plugins/task/page_index.py index f3d6a1684b..0cca55bb8a 100644 --- a/nikola/plugins/task/page_index.py +++ b/nikola/plugins/task/page_index.py @@ -27,80 +27,73 @@ """Render the blog indexes.""" from __future__ import unicode_literals -from collections import defaultdict -import os -from nikola.plugin_categories import Task -from nikola import utils +from nikola.plugin_categories import Taxonomy -class PageIndex(Task): +class PageIndex(Taxonomy): """Render the page index.""" - name = "render_page_index" + name = "classify_page_index" - def set_site(self, site): - """Set Nikola site.""" - return super(PageIndex, self).set_site(site) + classification_name = "page_index_folder" + metadata_name = None + overview_page_variable_name = "page_folder" + more_than_one_classifications_per_post = False + has_hierarchy = True + include_posts_from_subhierarchies = False + show_list_as_index = False + template_for_list_of_one_classification = "list.tmpl" + template_for_classification_overview = None + apply_to_posts = False + apply_to_pages = True + omit_empty_classifications = True + also_create_classifications_from_other_languages = False - def gen_tasks(self): - """Render the blog indexes.""" - self.site.scan_posts() - yield self.group_task() + def is_enabled(self, lang=None): + """Return True if this taxonomy is enabled, or False otherwise.""" + return self.site.config["PAGE_INDEX"] - if not self.site.config["PAGE_INDEX"]: - return + def classify(self, post, lang): + """Classify the given post for the given language.""" + destpath = post.destination_path(lang, sep='/') + index_len = len(self.site.config["INDEX_FILE"]) + if destpath[-(1 + index_len):] == '/' + self.site.config["INDEX_FILE"]: + destpath = destpath[:-(1 + index_len)] + i = destpath.rfind('/') + return destpath[:i] if i >= 0 else '' + + def get_path(self, hierarchy, lang, type='page'): + """A path handler for the given classification.""" + return hierarchy, True + + def extract_hierarchy(self, dirname): + """Given a classification, return a list of parts in the hierarchy.""" + return dirname.split('/') if dirname else [] + + def recombine_classification_from_hierarchy(self, hierarchy): + """Given a list of parts in the hierarchy, return the classification string.""" + return '/'.join(hierarchy) + + def provide_context_and_uptodate(self, dirname, lang): + """Provide data for the context and the uptodate list for the list of the given classifiation.""" kw = { "translations": self.site.config['TRANSLATIONS'], - "post_pages": self.site.config["post_pages"], - "output_folder": self.site.config['OUTPUT_FOLDER'], "filters": self.site.config['FILTERS'], - "index_file": self.site.config['INDEX_FILE'], - "strip_indexes": self.site.config['STRIP_INDEXES'], } - template_name = "list.tmpl" - index_len = len(kw['index_file']) - for lang in kw["translations"]: - # Need to group by folder to avoid duplicated tasks (Issue #758) - # Group all pages by path prefix - groups = defaultdict(list) - for p in self.site.timeline: - if not p.is_post: - destpath = p.destination_path(lang) - if destpath[-(1 + index_len):] == '/' + kw['index_file']: - destpath = destpath[:-(1 + index_len)] - dirname = os.path.dirname(destpath) - groups[dirname].append(p) - for dirname, post_list in groups.items(): - context = {} - context["items"] = [] - should_render = True - output_name = os.path.join(kw['output_folder'], dirname, kw['index_file']) - short_destination = os.path.join(dirname, kw['index_file']) - link = short_destination.replace('\\', '/') - if kw['strip_indexes'] and link[-(1 + index_len):] == '/' + kw['index_file']: - link = link[:-index_len] - context["permalink"] = link - context["pagekind"] = ["list"] - if dirname == "/": - context["pagekind"].append("front_page") - - for post in post_list: - # If there is an index.html pending to be created from - # a page, do not generate the PAGE_INDEX - if post.destination_path(lang) == short_destination: - should_render = False - else: - context["items"].append((post.title(lang), - post.permalink(lang), - None)) - - if should_render: - task = self.site.generic_post_list_renderer(lang, post_list, - output_name, - template_name, - kw['filters'], - context) - task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.indexes')] - task['basename'] = self.name - yield task + context = { + "title": self.site.config['BLOG_TITLE'](lang), + "classification_title": self.site.config['BLOG_TITLE'](lang), + "pagekind": ["list", "front_page"] if dirname == '' else ["list"], + } + kw.update(context) + return context, kw + + def should_generate_classification_list(self, dirname, post_list, lang): + """Only generates list of posts for classification if this function returns True.""" + short_destination = dirname + '/' + self.site.config['INDEX_FILE'] + for post in post_list: + # If there is an index.html pending to be created from a page, do not generate the page index. + if post.destination_path(lang, sep='/') == short_destination: + return False + return True From b7899eaf63942ee4f786f0215d82008fecc79633 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 20:03:24 +0200 Subject: [PATCH 35/74] Forgot one place. --- nikola/plugins/task/taxonomies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 9ef84c0397..32d3c38d8e 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -213,7 +213,7 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan if len(filtered_posts) == 0 and taxonomy.omit_empty_classifications: return # Should we create this list? - if not taxonomy.generate_classification_list(classification, filtered_posts, lang): + if not taxonomy.should_generate_classification_list(classification, filtered_posts, lang): return # Get data context, kw = taxonomy.provide_context_and_uptodate(classification, lang) From d73a148505815d7fd05a81e2742dfc80f247f5e4 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 20:17:58 +0200 Subject: [PATCH 36/74] One more. --- nikola/plugin_categories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 0a1950380c..1d5750d7f0 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -613,7 +613,7 @@ def provide_context_and_uptodate(self, classification, lang): def should_generate_classification_list(self, classification, post_list, lang): """Only generates list of posts for classification if this function returns True.""" - raise True + return True def postprocess_posts_per_classification(self, posts_per_classification_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None): """Rearrange, modify or otherwise use the list of posts per classification and per language. From 8b28e9d73c06aca7595cb0c360108a6e435fa54f Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 23:33:25 +0200 Subject: [PATCH 37/74] Allowing to jump to specific pages in indexes. --- nikola/plugin_categories.py | 22 ++++++-- nikola/plugins/misc/taxonomies_classifier.py | 56 +++++++++++++++++--- nikola/plugins/task/taxonomies.py | 12 +++-- 3 files changed, 76 insertions(+), 14 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 1d5750d7f0..bb55316215 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -544,8 +544,15 @@ def sort_classifications(self, classifications, lang): def get_list_path(self, lang, type='page'): """A path handler for the list of all classifications. - Must return a list or tuple, together with a boolean indicating - whether INDEX_FILE should always be added (True) or not (False). + Must return one to three values (in this order): + * a list or tuple of strings: the path relative to OUTPUT_DIRECTORY; + * a boolean indicating whether INDEX_FILE should always be added or not; + * an integer if a specific page of the index is to be targeted (will be + ignored for post lists), or `None` if the most current page is targeted. + + Note that this function must always return a list or tuple of strings; + the other two return values are optional with default values `False` and + `None`. In case INDEX_FILE should not be added, the last element in the returned path must have no extension, and the PRETTY_URLS config must be ignored @@ -559,8 +566,15 @@ def get_list_path(self, lang, type='page'): def get_path(self, classification, lang, type='page'): """A path handler for the given classification. - Must return a list or tuple, together with a boolean indicating - whether INDEX_FILE should always be added (True) or not (False). + Must return one to three values (in this order): + * a list or tuple of strings: the path relative to OUTPUT_DIRECTORY; + * a boolean indicating whether INDEX_FILE should always be added or not; + * an integer if a specific page of the index is to be targeted (will be + ignored for post lists), or `None` if the most current page is targeted. + + Note that this function must always return a list or tuple of strings; + the other two return values are optional with default values `False` and + `None`. In case INDEX_FILE should not be added, the last element in the returned path must have no extension, and the PRETTY_URLS config must be ignored diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 9f08a7fca5..d2972fbb5a 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -111,12 +111,15 @@ def _do_classification(self, site): sys.exit(1) # Sort everything. + site.page_count_per_classification = {} site.hierarchy_per_classification = {} site.flat_hierarchy_per_classification = {} site.hierarchy_lookup_per_classification = {} for taxonomy in taxonomies: + site.page_count_per_classification[taxonomy.classification_name] = {} # Sort post lists for lang, posts_per_classification in site.posts_per_classification[taxonomy.classification_name].items(): + site.page_count_per_classification[taxonomy.classification_name][lang] = {} # Convert sets to lists and sort them for classification in list(posts_per_classification.keys()): posts = list(posts_per_classification[classification]) @@ -168,7 +171,23 @@ def create_hierarchy(cat_hierarchy, parent=None): else: taxonomy.postprocess_posts_per_classification(site.posts_per_classification[taxonomy.classification_name]) - def _postprocess_path(self, path, lang, always_append_index=False, force_extension=None, type='page'): + def _filter_list(self, post_list, lang): + """Return only the posts which should be shown for this language.""" + if self.site.config["SHOW_UNTRANSLATED_POSTS"]: + return post_list + else: + return [x for x in post_list if x.is_translation_available(lang)] + + def _get_filtered_list(self, taxonomy, classification, lang): + """Return the filtered list of posts for this classification and language.""" + return self._filter_list(self.site.posts_per_classification[taxonomy.classification_name][lang].get(classification, []), lang) + + @staticmethod + def _compute_number_of_pages(self, filtered_posts, posts_count): + """Given a list of posts and the maximal number of posts per page, computes the number of pages needed.""" + return min(1, (len(filtered_posts) + posts_count - 1) // posts_count) + + def _postprocess_path(self, path, lang, always_append_index=False, force_extension=None, type='page', page_info=None): if type == 'feed': force_extension = '.atom' elif type == 'rss': @@ -183,20 +202,45 @@ def _postprocess_path(self, path, lang, always_append_index=False, force_extensi path = path + [self.site.config['INDEX_FILE']] else: path[-1] += '.html' - return [_f for _f in [self.site.config['TRANSLATIONS'][lang]] + path if _f] + result = [_f for _f in [self.site.config['TRANSLATIONS'][lang]] + path if _f] + if page_info is not None and type == 'page': + result = utils.adjust_name_for_index_path_list(result, + page_info[0], + utils.get_displayed_page_number(page_info[0], page_info[1], self.site), + lang, + self.site) + return result + + @staticmethod + def _parse_path_result(result): + """Helps interpreting the return values of taxonomy.get_path() and taxonomy.get_list_path().""" + if not isinstance(result[0], (list, tuple)): + # The result must be a list or tuple of strings. Wrap into a tuple + result = (result, ) + return result[0], result[1] if len(result) >= 1 else False, result[2] if len(result) >= 2 else None def _taxonomy_index_path(self, lang, taxonomy): """Return path to the classification overview.""" - path, append_index = taxonomy.get_list_path(lang) + result = taxonomy.get_list_path(lang) + path, append_index, _ = self._parse_path_result(result) return self._postprocess_path(path, lang, always_append_index=append_index) def _taxonomy_path(self, name, lang, taxonomy, force_extension=None, type='page'): """Return path to a classification.""" if taxonomy.has_hierarchy: - path, append_index = taxonomy.get_path(taxonomy.extract_hierarchy(name), lang, type=type) + result = taxonomy.get_path(taxonomy.extract_hierarchy(name), lang, type=type) else: - path, append_index = taxonomy.get_path(name, lang, type=type) - return self._postprocess_path(path, lang, always_append_index=append_index, force_extension=force_extension, type=type) + result = taxonomy.get_path(name, lang, type=type) + path, append_index, page = self._parse_path_result(result) + if not taxonomy.show_list_as_index: + # Page numbers are only supported for indexes + page = None + elif page is not None: + number_of_pages = self.site.page_count_per_classification[taxonomy.classification_name][lang].get(name) + if number_of_pages is None: + number_of_pages = self._compute_number_of_pages(self._get_filtered_list(name, lang), self.site.config['INDEX_DISPLAY_POST_COUNT']) + self.site.page_count_per_classification[taxonomy.classification_name][lang][name] = number_of_pages + return self._postprocess_path(path, lang, always_append_index=append_index, force_extension=force_extension, type=type, page_info=(page, number_of_pages)) def _taxonomy_atom_path(self, name, lang, taxonomy): """Return path to a classification Atom feed.""" diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 32d3c38d8e..a7cf9060b1 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -203,13 +203,17 @@ def _generate_classification_page_as_list(self, taxonomy, classification, filter if self.site.config['GENERATE_ATOM']: yield self._generate_classification_page_as_list_atom(kind, taxonomy, classification, filtered_posts, context, kw, lang) + def _filter_list(self, post_list, lang): + """Return only the posts which should be shown for this language.""" + if self.site.config["SHOW_UNTRANSLATED_POSTS"]: + return post_list + else: + return [x for x in post_list if x.is_translation_available(lang)] + def _generate_classification_page(self, taxonomy, classification, post_list, lang): """Render index or post list and associated feeds per classification.""" # Filter list - if self.site.config["SHOW_UNTRANSLATED_POSTS"]: - filtered_posts = post_list - else: - filtered_posts = [x for x in post_list if x.is_translation_available(lang)] + filtered_posts = self._filter_list(post_list, lang) if len(filtered_posts) == 0 and taxonomy.omit_empty_classifications: return # Should we create this list? From 3a9d172bf58ddf345363d93b987d1f9b57f12c11 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 23:51:47 +0200 Subject: [PATCH 38/74] Improving title construction. --- nikola/plugins/task/taxonomies.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index a7cf9060b1..39a04ac05e 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -127,14 +127,15 @@ def _generate_classification_page_as_rss(self, taxonomy, classification, filtere for post in filtered_posts: deps += post.deps(lang) deps_uptodate += post.deps_uptodate(lang) + blog_title = kw["blog_title"](lang) task = { 'basename': str(self.name), 'name': output_name, 'file_dep': deps, 'targets': [output_name], 'actions': [(utils.generic_rss_renderer, - (lang, "{0} ({1})".format(kw["blog_title"](lang), title), - kw["site_url"], None, filtered_posts, + (lang, "{0} ({1})".format(blog_title, title) if blog_title != title else blog_title, + kw["site_url"], context.get("description"), filtered_posts, output_name, kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], feed_url, _enclosure, kw["feed_link_append_query"]))], 'clean': True, From 681b0e72f16cd3c21a4ef41beb38f3028017ffbd Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 23:52:17 +0200 Subject: [PATCH 39/74] Merging rss and leftovers of indexes plugin into taxonomy plugin. --- nikola/plugins/task/indexes.plugin | 5 +- nikola/plugins/task/indexes.py | 149 +++++++++-------------------- nikola/plugins/task/rss.plugin | 13 --- nikola/plugins/task/rss.py | 117 ---------------------- 4 files changed, 48 insertions(+), 236 deletions(-) delete mode 100644 nikola/plugins/task/rss.plugin delete mode 100644 nikola/plugins/task/rss.py diff --git a/nikola/plugins/task/indexes.plugin b/nikola/plugins/task/indexes.plugin index 553b5ad16d..897577094d 100644 --- a/nikola/plugins/task/indexes.plugin +++ b/nikola/plugins/task/indexes.plugin @@ -1,5 +1,5 @@ [Core] -name = render_indexes +name = classify_indexes module = indexes [Documentation] @@ -9,5 +9,4 @@ website = https://getnikola.com/ description = Generates the blog's index pages. [Nikola] -plugincategory = Task - +plugincategory = Taxonomy diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index a39b03ec9f..88227d2e03 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -27,117 +27,60 @@ """Render the blog indexes.""" from __future__ import unicode_literals -import os -from nikola.plugin_categories import Task -from nikola import utils +from nikola.plugin_categories import Taxonomy -class Indexes(Task): - """Render the blog indexes.""" +class Indexes(Taxonomy): + """Classify for the blog's main index.""" - name = "render_indexes" + name = "classify_indexes" + + classification_name = "index" + metadata_name = None + overview_page_variable_name = None + more_than_one_classifications_per_post = False + has_hierarchy = False + show_list_as_index = True + template_for_list_of_one_classification = "index.tmpl" + template_for_classification_overview = None + apply_to_posts = True + apply_to_pages = False + omit_empty_classifications = False + also_create_classifications_from_other_languages = False def set_site(self, site): """Set Nikola site.""" - self.number_of_pages = dict() - site.register_path_handler('index', self.index_path) - site.register_path_handler('index_atom', self.index_atom_path) + # Redirect automatically generated 'index_rss' path handler to 'rss' for compatibility with old rss plugin + site.register_path_handler('rss', lambda name, lang: site.path_handlers['index_rss'](name, lang)) return super(Indexes, self).set_site(site) - def _get_filtered_posts(self, lang, show_untranslated_posts): - """Return a filtered list of all posts for the given language. - - If show_untranslated_posts is True, will only include posts which - are translated to the given language. Otherwise, returns all posts. - """ - if show_untranslated_posts: - return self.site.posts - else: - return [x for x in self.site.posts if x.is_translation_available(lang)] - - def _compute_number_of_pages(self, filtered_posts, posts_count): - """Given a list of posts and the maximal number of posts per page, computes the number of pages needed.""" - return min(1, (len(filtered_posts) + posts_count - 1) // posts_count) - - def gen_tasks(self): - """Render the blog indexes.""" - self.site.scan_posts() - yield self.group_task() - + def classify(self, post, lang): + """Classify the given post for the given language.""" + return "" + + def get_path(self, classification, lang, type='page'): + """A path handler for the given classification.""" + if type == 'rss': + return [self.site.config['RSS_PATH']], True + # 'page' (index) or 'feed' (Atom) + page_number = None + if type == 'page': + # Interpret argument as page number + try: + page_number = int(classification) + except: + pass + return [self.site.config['INDEX_PATH']], True, page_number + + def provide_context_and_uptodate(self, classification, lang): + """Provide data for the context and the uptodate list for the list of the given classifiation.""" kw = { - "translations": self.site.config['TRANSLATIONS'], - "messages": self.site.MESSAGES, - "output_folder": self.site.config['OUTPUT_FOLDER'], - "feed_length": self.site.config['FEED_LENGTH'], - "feed_links_append_query": self.site.config["FEED_LINKS_APPEND_QUERY"], - "feed_teasers": self.site.config["FEED_TEASERS"], - "feed_plain": self.site.config["FEED_PLAIN"], - "filters": self.site.config['FILTERS'], - "index_file": self.site.config['INDEX_FILE'], - "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'], - "index_display_post_count": self.site.config['INDEX_DISPLAY_POST_COUNT'], - "indexes_title": self.site.config['INDEXES_TITLE'], - "strip_indexes": self.site.config['STRIP_INDEXES'], - "blog_title": self.site.config["BLOG_TITLE"], - "generate_atom": self.site.config["GENERATE_ATOM"], - "site_url": self.site.config["SITE_URL"], } - - template_name = "index.tmpl" - for lang in kw["translations"]: - def page_link(i, displayed_i, num_pages, force_addition, extension=None): - feed = "_atom" if extension == ".atom" else "" - return utils.adjust_name_for_index_link(self.site.link("index" + feed, None, lang), i, displayed_i, - lang, self.site, force_addition, extension) - - def page_path(i, displayed_i, num_pages, force_addition, extension=None): - feed = "_atom" if extension == ".atom" else "" - return utils.adjust_name_for_index_path(self.site.path("index" + feed, None, lang), i, displayed_i, - lang, self.site, force_addition, extension) - - filtered_posts = self._get_filtered_posts(lang, kw["show_untranslated_posts"]) - - indexes_title = kw['indexes_title'](lang) or kw['blog_title'](lang) - self.number_of_pages[lang] = self._compute_number_of_pages(filtered_posts, kw['index_display_post_count']) - - context = {} - context["pagekind"] = ["main_index", "index"] - - yield self.site.generic_index_renderer(lang, filtered_posts, indexes_title, template_name, context, kw, 'render_indexes', page_link, page_path) - - def index_path(self, name, lang, is_feed=False): - """Link to a numbered index. - - Example: - - link://index/3 => /index-3.html - """ - extension = None - if is_feed: - extension = ".atom" - index_file = os.path.splitext(self.site.config['INDEX_FILE'])[0] + extension - else: - index_file = self.site.config['INDEX_FILE'] - if lang in self.number_of_pages: - number_of_pages = self.number_of_pages[lang] - else: - number_of_pages = self._compute_number_of_pages(self._get_filtered_posts(lang, self.site.config['SHOW_UNTRANSLATED_POSTS']), self.site.config['INDEX_DISPLAY_POST_COUNT']) - self.number_of_pages[lang] = number_of_pages - return utils.adjust_name_for_index_path_list([_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['INDEX_PATH'], - index_file] if _f], - name, - utils.get_displayed_page_number(name, number_of_pages, self.site), - lang, - self.site, - extension=extension) - - def index_atom_path(self, name, lang): - """Link to a numbered Atom index. - - Example: - - link://index_atom/3 => /index-3.atom - """ - return self.index_path(name, lang, is_feed=True) + context = { + "title": self.site.config["BLOG_TITLE"](lang), + "classification_title": "", + "description": self.site.config["BLOG_DESCRIPTION"](lang), + } + kw.update(context) + return context, kw diff --git a/nikola/plugins/task/rss.plugin b/nikola/plugins/task/rss.plugin deleted file mode 100644 index 4dd8aba08f..0000000000 --- a/nikola/plugins/task/rss.plugin +++ /dev/null @@ -1,13 +0,0 @@ -[Core] -name = generate_rss -module = rss - -[Documentation] -author = Roberto Alsina -version = 1.0 -website = https://getnikola.com/ -description = Generate RSS feeds. - -[Nikola] -plugincategory = Task - diff --git a/nikola/plugins/task/rss.py b/nikola/plugins/task/rss.py deleted file mode 100644 index 780559bcf3..0000000000 --- a/nikola/plugins/task/rss.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright © 2012-2016 Roberto Alsina and others. - -# Permission is hereby granted, free of charge, to any -# person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the -# Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the -# Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice -# shall be included in all copies or substantial portions of -# the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY -# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR -# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -"""Generate RSS feeds.""" - -from __future__ import unicode_literals, print_function -import os -try: - from urlparse import urljoin -except ImportError: - from urllib.parse import urljoin # NOQA - -from nikola import utils -from nikola.nikola import _enclosure -from nikola.plugin_categories import Task - - -class GenerateRSS(Task): - """Generate RSS feeds.""" - - name = "generate_rss" - - def set_site(self, site): - """Set Nikola site.""" - site.register_path_handler('rss', self.rss_path) - return super(GenerateRSS, self).set_site(site) - - def gen_tasks(self): - """Generate RSS feeds.""" - kw = { - "translations": self.site.config["TRANSLATIONS"], - "filters": self.site.config["FILTERS"], - "blog_title": self.site.config["BLOG_TITLE"], - "site_url": self.site.config["SITE_URL"], - "base_url": self.site.config["BASE_URL"], - "blog_description": self.site.config["BLOG_DESCRIPTION"], - "output_folder": self.site.config["OUTPUT_FOLDER"], - "feed_teasers": self.site.config["FEED_TEASERS"], - "feed_plain": self.site.config["FEED_PLAIN"], - "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'], - "feed_length": self.site.config['FEED_LENGTH'], - "feed_previewimage": self.site.config["FEED_PREVIEWIMAGE"], - "tzinfo": self.site.tzinfo, - "feed_read_more_link": self.site.config["FEED_READ_MORE_LINK"], - "feed_links_append_query": self.site.config["FEED_LINKS_APPEND_QUERY"], - } - self.site.scan_posts() - # Check for any changes in the state of use_in_feeds for any post. - # Issue #934 - kw['use_in_feeds_status'] = ''.join( - ['T' if x.use_in_feeds else 'F' for x in self.site.timeline] - ) - yield self.group_task() - for lang in kw["translations"]: - output_name = os.path.join(kw['output_folder'], - self.site.path("rss", None, lang)) - deps = [] - deps_uptodate = [] - if kw["show_untranslated_posts"]: - posts = self.site.posts[:kw['feed_length']] - else: - posts = [x for x in self.site.posts if x.is_translation_available(lang)][:kw['feed_length']] - for post in posts: - deps += post.deps(lang) - deps_uptodate += post.deps_uptodate(lang) - - feed_url = urljoin(self.site.config['BASE_URL'], self.site.link("rss", None, lang).lstrip('/')) - - task = { - 'basename': 'generate_rss', - 'name': os.path.normpath(output_name), - 'file_dep': deps, - 'targets': [output_name], - 'actions': [(utils.generic_rss_renderer, - (lang, kw["blog_title"](lang), kw["site_url"], - kw["blog_description"](lang), posts, output_name, - kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], feed_url, - _enclosure, kw["feed_links_append_query"]))], - - 'task_dep': ['render_posts'], - 'clean': True, - 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.rss')] + deps_uptodate, - } - yield utils.apply_filters(task, kw['filters']) - - def rss_path(self, name, lang): - """A link to the RSS feed path. - - Example: - - link://rss => /blog/rss.xml - """ - return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['RSS_PATH'], 'rss.xml'] if _f] From 2348de0d3352ca15c1bd1b2b72e5a2b8798e4e22 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 23:54:12 +0200 Subject: [PATCH 40/74] Fixing bugs. --- nikola/plugins/misc/taxonomies_classifier.py | 11 +++++------ nikola/plugins/task/taxonomies.py | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index d2972fbb5a..8fa5bad931 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -217,7 +217,7 @@ def _parse_path_result(result): if not isinstance(result[0], (list, tuple)): # The result must be a list or tuple of strings. Wrap into a tuple result = (result, ) - return result[0], result[1] if len(result) >= 1 else False, result[2] if len(result) >= 2 else None + return result[0], result[1] if len(result) > 1 else False, result[2] if len(result) > 2 else None def _taxonomy_index_path(self, lang, taxonomy): """Return path to the classification overview.""" @@ -232,15 +232,14 @@ def _taxonomy_path(self, name, lang, taxonomy, force_extension=None, type='page' else: result = taxonomy.get_path(name, lang, type=type) path, append_index, page = self._parse_path_result(result) - if not taxonomy.show_list_as_index: - # Page numbers are only supported for indexes - page = None - elif page is not None: + page_info = None + if not taxonomy.show_list_as_index and page is not None: number_of_pages = self.site.page_count_per_classification[taxonomy.classification_name][lang].get(name) if number_of_pages is None: number_of_pages = self._compute_number_of_pages(self._get_filtered_list(name, lang), self.site.config['INDEX_DISPLAY_POST_COUNT']) self.site.page_count_per_classification[taxonomy.classification_name][lang][name] = number_of_pages - return self._postprocess_path(path, lang, always_append_index=append_index, force_extension=force_extension, type=type, page_info=(page, number_of_pages)) + page_info = (page, number_of_pages) + return self._postprocess_path(path, lang, always_append_index=append_index, force_extension=force_extension, type=type, page_info=page_info) def _taxonomy_atom_path(self, name, lang, taxonomy): """Return path to a classification Atom feed.""" diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 39a04ac05e..81db34a9e1 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -116,7 +116,7 @@ def acceptor(node): task['basename'] = str(self.name) yield task - def _generate_classification_page_as_rss(self, taxonomy, classification, filtered_posts, title, kw, lang): + def _generate_classification_page_as_rss(self, taxonomy, classification, filtered_posts, title, description, kw, lang): """Create a RSS feed for a single classification in a given language.""" kind = taxonomy.classification_name # Render RSS @@ -135,7 +135,7 @@ def _generate_classification_page_as_rss(self, taxonomy, classification, filtere 'targets': [output_name], 'actions': [(utils.generic_rss_renderer, (lang, "{0} ({1})".format(blog_title, title) if blog_title != title else blog_title, - kw["site_url"], context.get("description"), filtered_posts, + kw["site_url"], description, filtered_posts, output_name, kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], feed_url, _enclosure, kw["feed_link_append_query"]))], 'clean': True, @@ -236,7 +236,7 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan context["permalink"] = self.site.link(taxonomy.classification_name, classification, lang) # Generate RSS feed if kw["generate_rss"]: - yield self._generate_classification_page_as_rss(taxonomy, classification, filtered_posts, context['title'], kw, lang) + yield self._generate_classification_page_as_rss(taxonomy, classification, filtered_posts, context['title'], context.get("description"), kw, lang) # Render HTML if taxonomy.show_list_as_index: yield self._generate_classification_page_as_index(taxonomy, classification, filtered_posts, context, kw, lang) From ba16539b4b5362a8e1cc8aebb33e1ec06419b723 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 17 Oct 2016 23:59:40 +0200 Subject: [PATCH 41/74] Making pydocstyle happy. --- nikola/plugins/misc/taxonomies_classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 8fa5bad931..c3781fa810 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -213,7 +213,7 @@ def _postprocess_path(self, path, lang, always_append_index=False, force_extensi @staticmethod def _parse_path_result(result): - """Helps interpreting the return values of taxonomy.get_path() and taxonomy.get_list_path().""" + """Interpret the return values of taxonomy.get_path() and taxonomy.get_list_path() as if all three return values were given.""" if not isinstance(result[0], (list, tuple)): # The result must be a list or tuple of strings. Wrap into a tuple result = (result, ) From e77eae8d7f205b6476ee1caeb385ff0c8e1f1aaa Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 18 Oct 2016 00:15:39 +0200 Subject: [PATCH 42/74] Makeing sure messages are always there. --- nikola/plugins/task/taxonomies.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 81db34a9e1..178ec201cf 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -223,6 +223,7 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan # Get data context, kw = taxonomy.provide_context_and_uptodate(classification, lang) kw = copy(kw) + kw['messages'] = self.site.MESSAGES kw['filters'] = self.site.config['FILTERS'] kw['site_url'] = self.site.config['SITE_URL'] kw['blog_title'] = self.site.config['BLOG_TITLE'] From 9350dcf24d6214ceef84e88d42f54ad3aa6c8c65 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 18 Oct 2016 00:17:17 +0200 Subject: [PATCH 43/74] Forgot pagekind. --- nikola/plugins/task/indexes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index 88227d2e03..c58b1d8f9c 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -81,6 +81,7 @@ def provide_context_and_uptodate(self, classification, lang): "title": self.site.config["BLOG_TITLE"](lang), "classification_title": "", "description": self.site.config["BLOG_DESCRIPTION"](lang), + "pagekind": ["main_index", "index"], } kw.update(context) return context, kw From c03b0542ddb1742e3006d1160337b8fce53a283e Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 18 Oct 2016 00:19:35 +0200 Subject: [PATCH 44/74] Accidentally returned a string instead of a list of that string. --- nikola/plugins/task/indexes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index c58b1d8f9c..b3477adb40 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -57,7 +57,7 @@ def set_site(self, site): def classify(self, post, lang): """Classify the given post for the given language.""" - return "" + return [""] def get_path(self, classification, lang, type='page'): """A path handler for the given classification.""" From 9effcfaa07a6e2d2914b575666405973201f1bf6 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 18 Oct 2016 00:22:55 +0200 Subject: [PATCH 45/74] Forgot to add posts with empty classification string. --- nikola/plugins/misc/taxonomies_classifier.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index c3781fa810..7db2845d5f 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -70,11 +70,14 @@ def _do_classification(self, site): post.meta[lang][taxonomy.metadata_name] = classifications[lang][0] if len(classifications[lang]) > 0 else None # Add post to sets for classification in classifications[lang]: - while classification: + while True: site.posts_per_classification[taxonomy.classification_name][lang][classification].add(post) if not taxonomy.include_posts_from_subhierarchies or not taxonomy.has_hierarchy: break - classification = taxonomy.recombine_classification_from_hierarchy(taxonomy.extract_hierarchy(classification)[:-1]) + classification_path = taxonomy.extract_hierarchy(classification)[:-1] + if len(classification_path) == 0: + break + classification = taxonomy.recombine_classification_from_hierarchy(classification_path) # Check for valid paths and for collisions taxonomy_outputs = {lang: dict() for lang in site.config['TRANSLATIONS'].keys()} From c15e75e7ee835693942ea1b19870463083c4f288 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 18 Oct 2016 00:47:39 +0200 Subject: [PATCH 46/74] Fixing two bugs. --- nikola/plugins/misc/taxonomies_classifier.py | 2 ++ nikola/plugins/task/indexes.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 7db2845d5f..9f241db308 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -55,6 +55,8 @@ def _do_classification(self, site): # Classify posts for post in site.timeline: + if not post.use_in_feeds: + continue for taxonomy in taxonomies: if taxonomy.apply_to_posts if post.is_post else taxonomy.apply_to_pages: classifications = {} diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index b3477adb40..fbea7ce4b7 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -85,3 +85,9 @@ def provide_context_and_uptodate(self, classification, lang): } kw.update(context) return context, kw + + def postprocess_posts_per_classification(self, posts_per_classification_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None): + """Rearrange, modify or otherwise use the list of posts per classification and per language.""" + for lang, posts_per_classification in posts_per_classification_per_language.items(): + if len(posts_per_classification) == 0: + posts_per_classification[""] = [] From a31939ba574694a11d3d1b14682c2665f366d0e1 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 18 Oct 2016 17:26:09 +0200 Subject: [PATCH 47/74] Preparing conversion of archive plugin. --- nikola/plugin_categories.py | 20 +++++++++++++++++ nikola/plugins/task/authors.py | 4 ++++ nikola/plugins/task/indexes.py | 4 ++++ nikola/plugins/task/page_index.py | 4 ++++ nikola/plugins/task/sections.py | 17 +++++++++----- nikola/plugins/task/taxonomies.py | 37 +++++++++++++++++++++++++++++++ 6 files changed, 81 insertions(+), 5 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index bb55316215..b71fde888e 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -484,6 +484,13 @@ class Taxonomy(BasePlugin): # If True, the list for a classification includes all posts with a # sub-classification (in case has_hierarchy is True). include_posts_from_subhierarchies = False + # If not False, for every classification which has at least one + # subclassification, create a list of subcategories instead of a list/index + # of posts. This is only used when has_hierarchy = True. If not False, this + # must be the template name for the list; usually "list.tmpl". + # If this is set to a string, it is recommended to set + # include_posts_from_subhierarchies to True to get correct post counts. + show_list_as_subcategories_list = False # Whether to show the posts for one classification as an index or # as a post list. show_list_as_index = False @@ -541,6 +548,19 @@ def sort_classifications(self, classifications, lang): """ pass + def get_classification_printable_name(self, classification, lang, only_last_component=False): + """Extract a printable name from the classification. + + For hierarchical taxonomies, the result of extract_hierarchy is provided + as `classification`. For non-hierarchical taxonomies, the classification + string itself is provided as `classification`. + + The argument `only_last_component` is only relevant to hierarchical + taxonomies. If it is set, the printable name should only describe the + last component of `classification` if possible. + """ + raise NotImplementedError() + def get_list_path(self, lang, type='page'): """A path handler for the list of all classifications. diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index 57c2594ec8..cae923efb2 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -67,6 +67,10 @@ def classify(self, post, lang): """Classify the given post for the given language.""" return [post.author()] + def get_classification_printable_name(self, author, lang, only_last_component=False): + """Extract a printable name from the classification.""" + return author + def get_list_path(self, lang, type='page'): """A path handler for the list of all classifications.""" return [self.site.config['AUTHOR_PATH']], True diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index fbea7ce4b7..dd3a22c4ed 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -59,6 +59,10 @@ def classify(self, post, lang): """Classify the given post for the given language.""" return [""] + def get_classification_printable_name(self, classification, lang, only_last_component=False): + """Extract a printable name from the classification.""" + return self.site.config["BLOG_TITLE"](lang) + def get_path(self, classification, lang, type='page'): """A path handler for the given classification.""" if type == 'rss': diff --git a/nikola/plugins/task/page_index.py b/nikola/plugins/task/page_index.py index 0cca55bb8a..fba17522e6 100644 --- a/nikola/plugins/task/page_index.py +++ b/nikola/plugins/task/page_index.py @@ -63,6 +63,10 @@ def classify(self, post, lang): i = destpath.rfind('/') return destpath[:i] if i >= 0 else '' + def get_classification_printable_name(self, hierarchy, lang, only_last_component=False): + """Extract a printable name from the classification.""" + return '/'.join(hierarchy) + def get_path(self, hierarchy, lang, type='page'): """A path handler for the given classification.""" return hierarchy, True diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py index 4ecb5ffbdc..8d25d5e199 100644 --- a/nikola/plugins/task/sections.py +++ b/nikola/plugins/task/sections.py @@ -66,6 +66,17 @@ def classify(self, post, lang): """Classify the given post for the given language.""" return [post.section_slug(lang)] + def _get_section_name(self, section, lang): + # Check whether we have a name for this section + if section in self.site.config['POSTS_SECTION_NAME'](lang): + return self.site.config['POSTS_SECTION_NAME'](lang)[section] + else: + return section.replace('-', ' ').title() + + def get_classification_printable_name(self, section, lang, only_last_component=False): + """Extract a printable name from the classification.""" + return self._get_section_name(section, lang) + def get_path(self, section, lang, type='page'): """A path handler for the given classification.""" return [_f for _f in [self.site.config['TRANSLATIONS'][lang], section] if _f], True @@ -82,11 +93,7 @@ def provide_context_and_uptodate(self, section, lang): kw = { "messages": self.site.MESSAGES, } - # Check whether we have a name for this section - if section in self.site.config['POSTS_SECTION_NAME'](lang): - section_name = self.site.config['POSTS_SECTION_NAME'](lang)[section] - else: - section_name = section.replace('-', ' ').title() + section_name = self._get_Section_name(section, lang) # Compose section title section_title = section_name posts_section_title = self.site.config['POSTS_SECTION_TITLE'](lang) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 178ec201cf..762ee93e18 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -211,6 +211,35 @@ def _filter_list(self, post_list, lang): else: return [x for x in post_list if x.is_translation_available(lang)] + def _generate_subclassification_page(self, taxonomy, node, context, kw, lang): + """Render a list of subclassifications.""" + def get_subnode_data(subnode): + return [ + taxonomy.get_classification_printable_name(subnode.classification_path, lang, only_last_component=True), + self.site.link(taxonomy.classification_name, subnode.classification_name, lang), + len(self._filter_list(self.site.posts_per_classification[taxonomy.classification_name][lang][subnode.classification_name])) + ] + + items = [get_subnode_data(subnode) for subnode in node.children] + context = copy(context) + context["lang"] = lang + context["permalink"] = self.site.link(taxonomy.classification_name, node.classification_name, lang) + if "pagekind" not in context: + context["pagekind"] = ["list", "archive_page"] + context["items"] = items + task = self.site.generic_post_list_renderer( + lang, + [], + os.path.join(kw['output_folder'], self.site.path(taxonomy.classification_name, node.classification_name, lang)), + taxonomy.show_list_as_subcategories_list, + kw['filters'], + context, + ) + task_cfg = {1: kw, 2: items} + task['uptodate'] = task['uptodate'] + [utils.config_changed(task_cfg, 'nikola.plugins.task.taxonomy')] + task['basename'] = self.name + return task + def _generate_classification_page(self, taxonomy, classification, post_list, lang): """Render index or post list and associated feeds per classification.""" # Filter list @@ -235,6 +264,14 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan kw["output_folder"] = self.site.config['OUTPUT_FOLDER'] context = copy(context) context["permalink"] = self.site.link(taxonomy.classification_name, classification, lang) + # Decide what to do + if taxonomy.has_hierarchy and taxonomy.show_list_as_subcategories_list: + # Determine whether there are subcategories + node = self.site.hierarchy_lookup_per_classification[taxonomy.classification_name][lang][classification] + # Are there subclassifications? + if len(node.children) > 0: + # Yes: create list with subclassifications instead of list of posts + return self._generate_subclassification_page(taxonomy, node, context, kw, lang) # Generate RSS feed if kw["generate_rss"]: yield self._generate_classification_page_as_rss(taxonomy, classification, filtered_posts, context['title'], context.get("description"), kw, lang) From d8112763af503badf7cce310c8ebd27650aad536 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Wed, 19 Oct 2016 07:51:53 +0200 Subject: [PATCH 48/74] Improving classification sorting. --- nikola/plugin_categories.py | 5 +++-- nikola/plugins/misc/taxonomies_classifier.py | 13 +++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index b71fde888e..1fad6135d1 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -538,11 +538,12 @@ def sort_posts(self, posts, classification, lang): """ pass - def sort_classifications(self, classifications, lang): + def sort_classifications(self, classifications, lang, level=None): """Sort the given list of classification strings. For hierarchical taxonomies, the elements of the list are a single - path element of the path returned by extract_hierarchy(). + path element of the path returned by `extract_hierarchy()`. The index + of the path element in the path will be provided in `level`. The sort must happen in-place. """ diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 9f241db308..fcaf6732af 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -151,18 +151,19 @@ def _do_classification(self, site): node = node[he] hierarchy_lookup = {} - def create_hierarchy(cat_hierarchy, parent=None): + def create_hierarchy(cat_hierarchy, parent=None, level=0): """Create category hierarchy.""" - result = [] + result = {} for name, children in cat_hierarchy.items(): node = utils.TreeNode(name, parent) - node.children = create_hierarchy(children, node) + node.children = create_hierarchy(children, node, level + 1) node.classification_path = [pn.name for pn in node.get_path()] node.classification_name = taxonomy.recombine_classification_from_hierarchy(node.classification_path) hierarchy_lookup[node.classification_name] = node - classifications = natsort.natsorted(result, key=lambda e: e.name, alg=natsort.ns.F | natsort.ns.IC) - taxonomy.sort_classifications(classifications, lang) - return classifications + result[node.name] = node + classifications = natsort.natsorted(result.keys(), key=lambda e: e.name, alg=natsort.ns.F | natsort.ns.IC) + taxonomy.sort_classifications(classifications, lang, level=level) + return [result[classification] for classification in classifications] root_list = create_hierarchy(hierarchy) flat_hierarchy = utils.flatten_tree_structure(root_list) From 4765190a28c656bd9d492d4aec37e355d6afd337 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Wed, 19 Oct 2016 20:13:32 +0200 Subject: [PATCH 49/74] Allowing root node to be created as well. Fixing some bugs. --- nikola/plugin_categories.py | 3 +++ nikola/plugins/misc/taxonomies_classifier.py | 24 +++++++++++++------- nikola/plugins/task/taxonomies.py | 7 +++--- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 1fad6135d1..80146b19f3 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -484,6 +484,9 @@ class Taxonomy(BasePlugin): # If True, the list for a classification includes all posts with a # sub-classification (in case has_hierarchy is True). include_posts_from_subhierarchies = False + # If True, include_posts_from_subhierarchies == True will also insert + # posts into the list for the empty hierarchy []. + include_posts_into_hierarchy_root = False # If not False, for every classification which has at least one # subclassification, create a list of subcategories instead of a list/index # of posts. This is only used when has_hierarchy = True. If not False, this diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index fcaf6732af..c50eed996b 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -76,10 +76,11 @@ def _do_classification(self, site): site.posts_per_classification[taxonomy.classification_name][lang][classification].add(post) if not taxonomy.include_posts_from_subhierarchies or not taxonomy.has_hierarchy: break - classification_path = taxonomy.extract_hierarchy(classification)[:-1] - if len(classification_path) == 0: - break - classification = taxonomy.recombine_classification_from_hierarchy(classification_path) + classification_path = taxonomy.extract_hierarchy(classification) + if len(classification_path) <= 1: + if len(classification_path) == 0 or not taxonomy.include_posts_into_hierarchy_root: + break + classification = taxonomy.recombine_classification_from_hierarchy(classification_path[:-1]) # Check for valid paths and for collisions taxonomy_outputs = {lang: dict() for lang in site.config['TRANSLATIONS'].keys()} @@ -151,21 +152,28 @@ def _do_classification(self, site): node = node[he] hierarchy_lookup = {} - def create_hierarchy(cat_hierarchy, parent=None, level=0): - """Create category hierarchy.""" + def create_hierarchy(hierarchy, parent=None, level=0): + """Create hierarchy.""" result = {} - for name, children in cat_hierarchy.items(): + for name, children in hierarchy.items(): node = utils.TreeNode(name, parent) node.children = create_hierarchy(children, node, level + 1) node.classification_path = [pn.name for pn in node.get_path()] node.classification_name = taxonomy.recombine_classification_from_hierarchy(node.classification_path) hierarchy_lookup[node.classification_name] = node result[node.name] = node - classifications = natsort.natsorted(result.keys(), key=lambda e: e.name, alg=natsort.ns.F | natsort.ns.IC) + classifications = natsort.natsorted(result.keys(), alg=natsort.ns.F | natsort.ns.IC) taxonomy.sort_classifications(classifications, lang, level=level) return [result[classification] for classification in classifications] root_list = create_hierarchy(hierarchy) + if '' in posts_per_classification: + node = utils.TreeNode('', parent=None) + node.children = root_list + node.classification_path = [] + node.classification_name = '' + hierarchy_lookup[node.name] = node + root_list = [node] flat_hierarchy = utils.flatten_tree_structure(root_list) # Store result site.hierarchy_per_classification[taxonomy.classification_name][lang] = root_list diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 762ee93e18..aff6a87510 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -58,7 +58,7 @@ def _clone_treenode(treenode, parent=None, acceptor=lambda x: True): class RenderTaxonomies(Task): - """Render the tag/category pages and feeds.""" + """Render taxonomy pages and feeds.""" name = "render_taxonomies" @@ -217,7 +217,7 @@ def get_subnode_data(subnode): return [ taxonomy.get_classification_printable_name(subnode.classification_path, lang, only_last_component=True), self.site.link(taxonomy.classification_name, subnode.classification_name, lang), - len(self._filter_list(self.site.posts_per_classification[taxonomy.classification_name][lang][subnode.classification_name])) + len(self._filter_list(self.site.posts_per_classification[taxonomy.classification_name][lang][subnode.classification_name], lang)) ] items = [get_subnode_data(subnode) for subnode in node.children] @@ -271,7 +271,8 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan # Are there subclassifications? if len(node.children) > 0: # Yes: create list with subclassifications instead of list of posts - return self._generate_subclassification_page(taxonomy, node, context, kw, lang) + yield self._generate_subclassification_page(taxonomy, node, context, kw, lang) + return # Generate RSS feed if kw["generate_rss"]: yield self._generate_classification_page_as_rss(taxonomy, classification, filtered_posts, context['title'], context.get("description"), kw, lang) From 5c7f33379ceb78d9acac86c373981103b571de59 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Wed, 19 Oct 2016 20:13:59 +0200 Subject: [PATCH 50/74] Converting archive plugin. --- nikola/plugins/task/archive.plugin | 4 +- nikola/plugins/task/archive.py | 324 ++++++++++------------------- 2 files changed, 117 insertions(+), 211 deletions(-) diff --git a/nikola/plugins/task/archive.plugin b/nikola/plugins/task/archive.plugin index eb079da4eb..c64dcee40f 100644 --- a/nikola/plugins/task/archive.plugin +++ b/nikola/plugins/task/archive.plugin @@ -1,5 +1,5 @@ [Core] -name = render_archive +name = classify_archive module = archive [Documentation] @@ -9,5 +9,5 @@ website = https://getnikola.com/ description = Generates the blog's archive pages. [Nikola] -plugincategory = Task +plugincategory = Taxonomy diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 303d349cb0..2fb9537657 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -24,231 +24,137 @@ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Render the post archives.""" +"""Classify the posts in archives.""" -import copy import os - -# for tearDown with _reload we cannot use 'import from' to access LocaleBorg import nikola.utils import datetime -from nikola.plugin_categories import Task -from nikola.utils import config_changed, adjust_name_for_index_path, adjust_name_for_index_link +from nikola.plugin_categories import Taxonomy + +class Archive(Taxonomy): + """Classify the post archives.""" -class Archive(Task): - """Render the post archives.""" + name = "classify_archive" - name = "render_archive" + classification_name = "archive" + metadata_name = None + overview_page_variable_name = "archive" + more_than_one_classifications_per_post = False + has_hierarchy = True + include_posts_from_subhierarchies = True + include_posts_into_hierarchy_root = True + template_for_classification_overview = None + apply_to_posts = True + apply_to_pages = False + minimum_post_count_per_classification_in_overview = 1 + omit_empty_classifications = False + also_create_classifications_from_other_languages = False def set_site(self, site): """Set Nikola site.""" - site.register_path_handler('archive', self.archive_path) - site.register_path_handler('archive_atom', self.archive_atom_path) + # Sanity checks + if (site.config['CREATE_MONTHLY_ARCHIVE'] and site.config['CREATE_SINGLE_ARCHIVE']) and not site.config['CREATE_FULL_ARCHIVES']: + raise Exception('Cannot create monthly and single archives at the same time.') + # Finish setup + self.show_list_as_subcategories_list = False if site.config['CREATE_FULL_ARCHIVES'] else "list.tmpl" + self.show_list_as_index = site.config['ARCHIVES_ARE_INDEXES'] + self.template_for_list_of_one_classification = "archiveindex.tmpl" if site.config['ARCHIVES_ARE_INDEXES'] else "list_post.tmpl" + # Determine maximal hierarchy height + if site.config['CREATE_DAILY_ARCHIVE'] or site.config['CREATE_FULL_ARCHIVES']: + self.max_levels = 3 + elif site.config['CREATE_MONTHLY_ARCHIVE']: + self.max_levels = 2 + elif site.config['CREATE_SINGLE_ARCHIVE']: + self.max_levels = 0 + else: + self.max_levels = 1 return super(Archive, self).set_site(site) - def _prepare_task(self, kw, name, lang, posts, items, template_name, - title, deps_translatable=None): - """Prepare an archive task.""" - # name: used to build permalink and destination - # posts, items: posts or items; only one of them should be used, - # the other should be None - # template_name: name of the template to use - # title: the (translated) title for the generated page - # deps_translatable: dependencies (None if not added) - assert posts is not None or items is not None - task_cfg = [copy.copy(kw)] - context = {} - context["lang"] = lang - context["title"] = title - context["permalink"] = self.site.link("archive", name, lang) - context["pagekind"] = ["list", "archive_page"] - if posts is not None: - context["posts"] = posts - # Depend on all post metadata because it can be used in templates (Issue #1931) - task_cfg.append([repr(p) for p in posts]) + def is_enabled(self, lang=None): + """Return True if this taxonomy is enabled, or False otherwise.""" + return True + + def classify(self, post, lang): + """Classify the given post for the given language.""" + levels = ['{year:04d}', '{month:02d}', '{day:02d}'][:self.max_levels] + levels = [level.format(year=post.date.year, month=post.date.month, day=post.date.day) for level in levels] + return ['/'.join(levels)] + + def sort_classifications(self, classifications, lang, level=None): + """Sort the given list of classification strings.""" + if level in (0, 1): + # Years or months: sort descending + classifications.sort() + classifications.reverse() + + def get_classification_printable_name(self, classification, lang, only_last_component=False): + """Extract a printable name from the classification.""" + if len(classification) == 0: + return "" + elif len(classification) == 1: + return classification[0] + elif len(classification) == 2: + nikola.utils.LocaleBorg().get_month_name(int(classification[1]), lang) else: - # Depend on the content of items, to rebuild if links change (Issue #1931) - context["items"] = items - task_cfg.append(items) - task = self.site.generic_post_list_renderer( - lang, - [], - os.path.join(kw['output_folder'], self.site.path("archive", name, lang)), - template_name, - kw['filters'], - context, - ) - - task_cfg = {i: x for i, x in enumerate(task_cfg)} - if deps_translatable is not None: - task_cfg[3] = deps_translatable - task['uptodate'] = task['uptodate'] + [config_changed(task_cfg, 'nikola.plugins.task.archive')] - task['basename'] = self.name - return task - - def _generate_posts_task(self, kw, name, lang, posts, title, deps_translatable=None): - """Genereate a task for an archive with posts.""" - posts = sorted(posts, key=lambda a: a.date) - posts.reverse() - if kw['archives_are_indexes']: - def page_link(i, displayed_i, num_pages, force_addition, extension=None): - feed = "_atom" if extension == ".atom" else "" - return adjust_name_for_index_link(self.site.link("archive" + feed, name, lang), i, displayed_i, - lang, self.site, force_addition, extension) - - def page_path(i, displayed_i, num_pages, force_addition, extension=None): - feed = "_atom" if extension == ".atom" else "" - return adjust_name_for_index_path(self.site.path("archive" + feed, name, lang), i, displayed_i, - lang, self.site, force_addition, extension) - - uptodate = [] - if deps_translatable is not None: - uptodate += [config_changed(deps_translatable, 'nikola.plugins.task.archive')] - context = {"archive_name": name, - "is_feed_stale": kw["is_feed_stale"], - "pagekind": ["index", "archive_page"]} - yield self.site.generic_index_renderer( - lang, - posts, - title, - "archiveindex.tmpl", - context, - kw, - str(self.name), - page_link, - page_path, - uptodate) + # Fallback + return '/'.join(classification) + + def get_path(self, classification, lang, type='page'): + """A path handler for the given classification.""" + components = [self.site.config['ARCHIVE_PATH']] + if classification: + components.extend(classification) + file = self.site.config['INDEX_FILE'] else: - yield self._prepare_task(kw, name, lang, posts, None, "list_post.tmpl", title, deps_translatable) + file = self.site.config['ARCHIVE_FILENAME'] + components.append(os.path.splitext(file)[0]) + return [_f for _f in components if _f], False + + def extract_hierarchy(self, classification): + """Given a classification, return a list of parts in the hierarchy.""" + return classification.split('/') if classification else [] + + def recombine_classification_from_hierarchy(self, hierarchy): + """Given a list of parts in the hierarchy, return the classification string.""" + return '/'.join(hierarchy) - def gen_tasks(self): - """Generate archive tasks.""" + def provide_context_and_uptodate(self, classification, lang): + """Provide data for the context and the uptodate list for the list of the given classifiation.""" + hierarchy = self.extract_hierarchy(classification) kw = { "messages": self.site.MESSAGES, - "translations": self.site.config['TRANSLATIONS'], - "output_folder": self.site.config['OUTPUT_FOLDER'], - "filters": self.site.config['FILTERS'], - "archives_are_indexes": self.site.config['ARCHIVES_ARE_INDEXES'], - "create_monthly_archive": self.site.config['CREATE_MONTHLY_ARCHIVE'], - "create_single_archive": self.site.config['CREATE_SINGLE_ARCHIVE'], - "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'], - "create_full_archives": self.site.config['CREATE_FULL_ARCHIVES'], - "create_daily_archive": self.site.config['CREATE_DAILY_ARCHIVE'], - "pretty_urls": self.site.config['PRETTY_URLS'], - "strip_indexes": self.site.config['STRIP_INDEXES'], - "index_file": self.site.config['INDEX_FILE'], - "generate_atom": self.site.config["GENERATE_ATOM"], } - self.site.scan_posts() - yield self.group_task() - # TODO add next/prev links for years - if (kw['create_monthly_archive'] and kw['create_single_archive']) and not kw['create_full_archives']: - raise Exception('Cannot create monthly and single archives at the same time.') - for lang in kw["translations"]: - if kw['create_single_archive'] and not kw['create_full_archives']: - # if we are creating one single archive - archdata = {} - else: - # if we are not creating one single archive, start with all years - archdata = self.site.posts_per_year.copy() - if kw['create_single_archive'] or kw['create_full_archives']: - # if we are creating one single archive, or full archives - archdata[None] = self.site.posts # for create_single_archive - - for year, posts in archdata.items(): - # Filter untranslated posts (Issue #1360) - if not kw["show_untranslated_posts"]: - posts = [p for p in posts if lang in p.translated_to] - - # Add archive per year or total archive - if year: - title = kw["messages"][lang]["Posts for year %s"] % year - kw["is_feed_stale"] = (datetime.datetime.utcnow().strftime("%Y") != year) - else: - title = kw["messages"][lang]["Archive"] - kw["is_feed_stale"] = False - deps_translatable = {} - for k in self.site._GLOBAL_CONTEXT_TRANSLATABLE: - deps_translatable[k] = self.site.GLOBAL_CONTEXT[k](lang) - if not kw["create_monthly_archive"] or kw["create_full_archives"]: - yield self._generate_posts_task(kw, year, lang, posts, title, deps_translatable) - else: - months = set([(m.split('/')[1], self.site.link("archive", m, lang), len(self.site.posts_per_month[m])) for m in self.site.posts_per_month.keys() if m.startswith(str(year))]) - months = sorted(list(months)) - months.reverse() - items = [[nikola.utils.LocaleBorg().get_month_name(int(month), lang), link, count] for month, link, count in months] - yield self._prepare_task(kw, year, lang, None, items, "list.tmpl", title, deps_translatable) - - if not kw["create_monthly_archive"] and not kw["create_full_archives"] and not kw["create_daily_archive"]: - continue # Just to avoid nesting the other loop in this if - for yearmonth, posts in self.site.posts_per_month.items(): - # Add archive per month - year, month = yearmonth.split('/') - - kw["is_feed_stale"] = (datetime.datetime.utcnow().strftime("%Y/%m") != yearmonth) - - # Filter untranslated posts (via Issue #1360) - if not kw["show_untranslated_posts"]: - posts = [p for p in posts if lang in p.translated_to] - - if kw["create_monthly_archive"] or kw["create_full_archives"]: - title = kw["messages"][lang]["Posts for {month} {year}"].format( - year=year, month=nikola.utils.LocaleBorg().get_month_name(int(month), lang)) - yield self._generate_posts_task(kw, yearmonth, lang, posts, title) - - if not kw["create_full_archives"] and not kw["create_daily_archive"]: - continue # Just to avoid nesting the other loop in this if - # Add archive per day - days = dict() - for p in posts: - if p.date.day not in days: - days[p.date.day] = list() - days[p.date.day].append(p) - for day, posts in days.items(): - title = kw["messages"][lang]["Posts for {month} {day}, {year}"].format( - year=year, month=nikola.utils.LocaleBorg().get_month_name(int(month), lang), day=day) - yield self._generate_posts_task(kw, yearmonth + '/{0:02d}'.format(day), lang, posts, title) - - if not kw['create_single_archive'] and not kw['create_full_archives']: - # And an "all your years" page for yearly and monthly archives - if "is_feed_stale" in kw: - del kw["is_feed_stale"] - years = list(self.site.posts_per_year.keys()) - years.sort(reverse=True) - kw['years'] = years - for lang in kw["translations"]: - items = [(y, self.site.link("archive", y, lang), len(self.site.posts_per_year[y])) for y in years] - yield self._prepare_task(kw, None, lang, None, items, "list.tmpl", kw["messages"][lang]["Archive"]) - - def archive_path(self, name, lang, is_feed=False): - """Link to archive path, name is the year. - - Example: - - link://archive/2013 => /archives/2013/index.html - """ - if is_feed: - extension = ".atom" - archive_file = os.path.splitext(self.site.config['ARCHIVE_FILENAME'])[0] + extension - index_file = os.path.splitext(self.site.config['INDEX_FILE'])[0] + extension - else: - archive_file = self.site.config['ARCHIVE_FILENAME'] - index_file = self.site.config['INDEX_FILE'] - if name: - return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['ARCHIVE_PATH'], name, - index_file] if _f] + page_kind = "list" + if self.show_list_as_index: + if not self.show_list_as_subcategories_list or len(hierarchy) == self.max_levels: + page_kind = "index" + if len(hierarchy) == 0: + title = kw["messages"][lang]["Archive"] + kw["is_feed_stale"] = False + elif len(hierarchy) == 1: + title = kw["messages"][lang]["Posts for year %s"] % hierarchy[0] + kw["is_feed_stale"] = (datetime.datetime.utcnow().strftime("%Y") != hierarchy[0]) + elif len(hierarchy) == 2: + title = kw["messages"][lang]["Posts for {month} {year}"].format( + year=hierarchy[0], + month=nikola.utils.LocaleBorg().get_month_name(int(hierarchy[1]), lang)) + kw["is_feed_stale"] = (datetime.datetime.utcnow().strftime("%Y/%m") != classification) + elif len(hierarchy) == 3: + title = kw["messages"][lang]["Posts for {month} {day}, {year}"].format( + year=hierarchy[0], + month=nikola.utils.LocaleBorg().get_month_name(int(hierarchy[1]), lang), + day=hierarchy[2]) + kw["is_feed_stale"] = (datetime.datetime.utcnow().strftime("%Y/%m/%d") != classification) else: - return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['ARCHIVE_PATH'], - archive_file] if _f] - - def archive_atom_path(self, name, lang): - """Link to atom archive path, name is the year. - - Example: - - link://archive_atom/2013 => /archives/2013/index.atom - """ - return self.archive_path(name, lang, is_feed=True) + raise Exception("Cannot interpret classification {}!".format(repr(classification))) + context = { + "title": title, + "classification_title": '/'.join(classification), + "pagekind": [page_kind, "archive_page"], + } + if page_kind == 'index': + context["archive_name"] = title + context["is_feed_stale"] = kw["is_feed_stale"] + kw.update(context) + return context, kw From 3b457f512550cf0da49162cdabde0465f9ef0346 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Wed, 19 Oct 2016 20:25:14 +0200 Subject: [PATCH 51/74] Allow to ensure that some classifications are always present. --- nikola/plugin_categories.py | 4 ++++ nikola/plugins/misc/taxonomies_classifier.py | 4 ++++ nikola/plugins/task/archive.py | 4 ++++ nikola/plugins/task/indexes.py | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 80146b19f3..f7b8cede1e 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -527,6 +527,10 @@ def is_enabled(self, lang=None): """ return True + def get_implicit_classifications(self, lang): + """Returns a list of classification strings which should always appear in posts_per_classification.""" + return [] + def classify(self, post, lang): """Classify the given post for the given language. diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index c50eed996b..3dc40a26d3 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -125,6 +125,10 @@ def _do_classification(self, site): site.page_count_per_classification[taxonomy.classification_name] = {} # Sort post lists for lang, posts_per_classification in site.posts_per_classification[taxonomy.classification_name].items(): + # Ensure implicit classifications are inserted + for classification in taxonomy.get_implicit_classifications(lang): + if classification not in posts_per_classification: + posts_per_classification[classification] = [] site.page_count_per_classification[taxonomy.classification_name][lang] = {} # Convert sets to lists and sort them for classification in list(posts_per_classification.keys()): diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 2fb9537657..efea5a6353 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -75,6 +75,10 @@ def is_enabled(self, lang=None): """Return True if this taxonomy is enabled, or False otherwise.""" return True + def get_implicit_classifications(self, lang): + """Returns a list of classification strings which should always appear in posts_per_classification.""" + return [''] + def classify(self, post, lang): """Classify the given post for the given language.""" levels = ['{year:04d}', '{month:02d}', '{day:02d}'][:self.max_levels] diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index dd3a22c4ed..0503c1d7af 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -55,6 +55,10 @@ def set_site(self, site): site.register_path_handler('rss', lambda name, lang: site.path_handlers['index_rss'](name, lang)) return super(Indexes, self).set_site(site) + def get_implicit_classifications(self, lang): + """Returns a list of classification strings which should always appear in posts_per_classification.""" + return [""] + def classify(self, post, lang): """Classify the given post for the given language.""" return [""] From 966cb9421d5c0465d2d6fc8b580860698dc7ba32 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Wed, 19 Oct 2016 20:29:13 +0200 Subject: [PATCH 52/74] Making pydoctest happy. --- nikola/plugin_categories.py | 2 +- nikola/plugins/task/archive.py | 2 +- nikola/plugins/task/indexes.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index f7b8cede1e..45a6b5031f 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -528,7 +528,7 @@ def is_enabled(self, lang=None): return True def get_implicit_classifications(self, lang): - """Returns a list of classification strings which should always appear in posts_per_classification.""" + """Return a list of classification strings which should always appear in posts_per_classification.""" return [] def classify(self, post, lang): diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index efea5a6353..2bb6f5f6fe 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -76,7 +76,7 @@ def is_enabled(self, lang=None): return True def get_implicit_classifications(self, lang): - """Returns a list of classification strings which should always appear in posts_per_classification.""" + """Return a list of classification strings which should always appear in posts_per_classification.""" return [''] def classify(self, post, lang): diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index 0503c1d7af..37d77358f8 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -56,7 +56,7 @@ def set_site(self, site): return super(Indexes, self).set_site(site) def get_implicit_classifications(self, lang): - """Returns a list of classification strings which should always appear in posts_per_classification.""" + """Return a list of classification strings which should always appear in posts_per_classification.""" return [""] def classify(self, post, lang): From 9e55cbb08d0e8cac0291463e5cdb4d56bae3f011 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Wed, 19 Oct 2016 20:54:44 +0200 Subject: [PATCH 53/74] Allowing to prevent Atom feed generation for classification post lists. (Nobody wants that except tag and category pages.) --- nikola/plugin_categories.py | 2 ++ nikola/plugins/task/archive.py | 1 + nikola/plugins/task/authors.py | 1 + nikola/plugins/task/page_index.py | 1 + nikola/plugins/task/sections.py | 1 + nikola/plugins/task/taxonomies.py | 2 +- 6 files changed, 7 insertions(+), 1 deletion(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 45a6b5031f..c7dadfb397 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -497,6 +497,8 @@ class Taxonomy(BasePlugin): # Whether to show the posts for one classification as an index or # as a post list. show_list_as_index = False + # Whether to generate Atom feeds for post lists in case GENERATE_ATOM is set. + generate_atom_feeds_for_post_lists = False # The template to use for the post list for one classification. template_for_list_of_one_classification = "tagindex.tmpl" # The template to use for the classification overview page. diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 2bb6f5f6fe..eff8360251 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -44,6 +44,7 @@ class Archive(Taxonomy): has_hierarchy = True include_posts_from_subhierarchies = True include_posts_into_hierarchy_root = True + generate_atom_feeds_for_post_lists = False template_for_classification_overview = None apply_to_posts = True apply_to_pages = False diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index cae923efb2..abd6005b37 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -42,6 +42,7 @@ class ClassifyAuthors(Taxonomy): overview_page_variable_name = "authors" more_than_one_classifications_per_post = False has_hierarchy = False + generate_atom_feeds_for_post_lists = False template_for_classification_overview = "authors.tmpl" apply_to_posts = True apply_to_pages = False diff --git a/nikola/plugins/task/page_index.py b/nikola/plugins/task/page_index.py index fba17522e6..2abb81762a 100644 --- a/nikola/plugins/task/page_index.py +++ b/nikola/plugins/task/page_index.py @@ -43,6 +43,7 @@ class PageIndex(Taxonomy): has_hierarchy = True include_posts_from_subhierarchies = False show_list_as_index = False + generate_atom_feeds_for_post_lists = False template_for_list_of_one_classification = "list.tmpl" template_for_classification_overview = None apply_to_posts = False diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py index 8d25d5e199..01e216b18e 100644 --- a/nikola/plugins/task/sections.py +++ b/nikola/plugins/task/sections.py @@ -41,6 +41,7 @@ class ClassifySections(Taxonomy): overview_page_variable_name = "sections" more_than_one_classifications_per_post = False has_hierarchy = False + generate_atom_feeds_for_post_lists = False template_for_classification_overview = None apply_to_posts = True apply_to_pages = False diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index aff6a87510..1287407d06 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -201,7 +201,7 @@ def _generate_classification_page_as_list(self, taxonomy, classification, filter task['basename'] = str(self.name) yield task - if self.site.config['GENERATE_ATOM']: + if taxonomy.generate_atom_feeds_for_post_lists and self.site.config['GENERATE_ATOM']: yield self._generate_classification_page_as_list_atom(kind, taxonomy, classification, filtered_posts, context, kw, lang) def _filter_list(self, post_list, lang): From 0886a963cc9e17ac7133323f3b69466efcdb315d Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Wed, 19 Oct 2016 21:13:27 +0200 Subject: [PATCH 54/74] Some pecularities regarding RSS feeds. --- nikola/plugin_categories.py | 2 ++ nikola/plugins/task/archive.py | 1 + nikola/plugins/task/page_index.py | 1 + nikola/plugins/task/taxonomies.py | 2 +- 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index c7dadfb397..87efea1781 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -504,6 +504,8 @@ class Taxonomy(BasePlugin): # The template to use for the classification overview page. # Set to None to avoid generating overviews. template_for_classification_overview = "list.tmpl" + # Whether to always disable RSS feed generation + always_disable_rss = False # Whether this classification applies to posts. apply_to_posts = True # Whether this classification applies to pages. diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index eff8360251..074ebf7bf6 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -46,6 +46,7 @@ class Archive(Taxonomy): include_posts_into_hierarchy_root = True generate_atom_feeds_for_post_lists = False template_for_classification_overview = None + always_disable_rss = True apply_to_posts = True apply_to_pages = False minimum_post_count_per_classification_in_overview = 1 diff --git a/nikola/plugins/task/page_index.py b/nikola/plugins/task/page_index.py index 2abb81762a..e1c6fea185 100644 --- a/nikola/plugins/task/page_index.py +++ b/nikola/plugins/task/page_index.py @@ -46,6 +46,7 @@ class PageIndex(Taxonomy): generate_atom_feeds_for_post_lists = False template_for_list_of_one_classification = "list.tmpl" template_for_classification_overview = None + always_disable_rss = True apply_to_posts = False apply_to_pages = True omit_empty_classifications = True diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 1287407d06..6fdd34d67d 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -274,7 +274,7 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan yield self._generate_subclassification_page(taxonomy, node, context, kw, lang) return # Generate RSS feed - if kw["generate_rss"]: + if kw["generate_rss"] and not taxonomy.always_disable_rss: yield self._generate_classification_page_as_rss(taxonomy, classification, filtered_posts, context['title'], context.get("description"), kw, lang) # Render HTML if taxonomy.show_list_as_index: From 15756b31e3ca2d8e4ecbb4ee8384b5b3d5964bea Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 07:29:39 +0200 Subject: [PATCH 55/74] Fixing titles. --- nikola/plugins/task/archive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 074ebf7bf6..034114ad89 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -150,7 +150,7 @@ def provide_context_and_uptodate(self, classification, lang): title = kw["messages"][lang]["Posts for {month} {day}, {year}"].format( year=hierarchy[0], month=nikola.utils.LocaleBorg().get_month_name(int(hierarchy[1]), lang), - day=hierarchy[2]) + day=int(hierarchy[2])) kw["is_feed_stale"] = (datetime.datetime.utcnow().strftime("%Y/%m/%d") != classification) else: raise Exception("Cannot interpret classification {}!".format(repr(classification))) From 4563630d098925881bb674e10a9cc02c4e4190c8 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 07:49:53 +0200 Subject: [PATCH 56/74] Fixing stupid bug in filename generation. --- nikola/plugins/misc/taxonomies_classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 3dc40a26d3..7f5d079915 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -212,7 +212,7 @@ def _postprocess_path(self, path, lang, always_append_index=False, force_extensi force_extension = '.xml' if force_extension is not None: if len(path) == 0 or always_append_index: - path = [os.path.splitext(self.site.config['INDEX_FILE'])[0]] + path = path + [os.path.splitext(self.site.config['INDEX_FILE'])[0]] if type == 'rss': path = ['rss'] path[-1] += force_extension From 469d26607318bed8fc421bb420bfeb0bccc11115 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 07:50:10 +0200 Subject: [PATCH 57/74] Improving archive path generation. --- nikola/plugins/task/archive.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 034114ad89..f3c82de40a 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -111,11 +111,11 @@ def get_path(self, classification, lang, type='page'): components = [self.site.config['ARCHIVE_PATH']] if classification: components.extend(classification) - file = self.site.config['INDEX_FILE'] + always_add_index = True else: - file = self.site.config['ARCHIVE_FILENAME'] - components.append(os.path.splitext(file)[0]) - return [_f for _f in components if _f], False + components.append(os.path.splitext(self.site.config['ARCHIVE_FILENAME'])[0]) + always_add_index = False + return [_f for _f in components if _f], always_add_index def extract_hierarchy(self, classification): """Given a classification, return a list of parts in the hierarchy.""" From 333dae1553e800557c1bf74cbc16c7bed72a473b Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 07:52:14 +0200 Subject: [PATCH 58/74] Fixing name screwup. --- nikola/plugins/task/archive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index f3c82de40a..3764a1e9bd 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -156,7 +156,7 @@ def provide_context_and_uptodate(self, classification, lang): raise Exception("Cannot interpret classification {}!".format(repr(classification))) context = { "title": title, - "classification_title": '/'.join(classification), + "classification_title": classification, "pagekind": [page_kind, "archive_page"], } if page_kind == 'index': From 642afd28fa37b0d1c1148479e4c66d64dfb504ed Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 16:55:00 +0200 Subject: [PATCH 59/74] Fixing wrongly chosen variable. --- nikola/plugins/task/archive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 3764a1e9bd..11513994d0 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -160,7 +160,7 @@ def provide_context_and_uptodate(self, classification, lang): "pagekind": [page_kind, "archive_page"], } if page_kind == 'index': - context["archive_name"] = title + context["archive_name"] = classification context["is_feed_stale"] = kw["is_feed_stale"] kw.update(context) return context, kw From 816b14a5f7fee65c2619d60957b35ab0df7df5a4 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 17:06:29 +0200 Subject: [PATCH 60/74] Forgot to not insert RSS links when RSS is disabled for the taxonomy. --- nikola/plugins/task/taxonomies.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index 6fdd34d67d..eec1ad7782 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -157,8 +157,7 @@ def page_path(i, displayed_i, num_pages, force_addition, extension=None): return utils.adjust_name_for_index_path(self.site.path(feed.format(kind), classification, lang), i, displayed_i, lang, self.site, force_addition, extension) context = copy(context) - if kw["generate_rss"]: - # On a tag page, the feeds include the tag's feeds + if kw["generate_rss"] and not taxonomy.always_disable_rss: rss_link = ("""""".format( taxonomy.classification_name, context['classification_title'], lang, self.site.link('{}_rss'.format(kind), classification, lang))) context['rss_link'] = rss_link From 27cf468b88ff49d48eb11e35ee0dc4c615433a55 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 17:08:20 +0200 Subject: [PATCH 61/74] Avoiding third argument of get_list_path() for the overview list page. --- nikola/plugin_categories.py | 7 ++----- nikola/plugins/misc/taxonomies_classifier.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 87efea1781..83be02763c 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -576,15 +576,12 @@ def get_classification_printable_name(self, classification, lang, only_last_comp def get_list_path(self, lang, type='page'): """A path handler for the list of all classifications. - Must return one to three values (in this order): + Must return one or two values (in this order): * a list or tuple of strings: the path relative to OUTPUT_DIRECTORY; * a boolean indicating whether INDEX_FILE should always be added or not; - * an integer if a specific page of the index is to be targeted (will be - ignored for post lists), or `None` if the most current page is targeted. Note that this function must always return a list or tuple of strings; - the other two return values are optional with default values `False` and - `None`. + the other return value is optional with default value `False`. In case INDEX_FILE should not be added, the last element in the returned path must have no extension, and the PRETTY_URLS config must be ignored diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 7f5d079915..9f8d254ce1 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -241,7 +241,7 @@ def _taxonomy_index_path(self, lang, taxonomy): """Return path to the classification overview.""" result = taxonomy.get_list_path(lang) path, append_index, _ = self._parse_path_result(result) - return self._postprocess_path(path, lang, always_append_index=append_index) + return self._postprocess_path(path, lang, always_append_index=append_index, type='list') def _taxonomy_path(self, name, lang, taxonomy, force_extension=None, type='page'): """Return path to a classification.""" From 095395613e5ef829c99161c0f44cec8881c221d1 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 17:10:37 +0200 Subject: [PATCH 62/74] Fixing URL for some feeds and making some stuff clearer. --- nikola/plugins/misc/taxonomies_classifier.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 9f8d254ce1..0fc345c70c 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -205,21 +205,25 @@ def _compute_number_of_pages(self, filtered_posts, posts_count): """Given a list of posts and the maximal number of posts per page, computes the number of pages needed.""" return min(1, (len(filtered_posts) + posts_count - 1) // posts_count) - def _postprocess_path(self, path, lang, always_append_index=False, force_extension=None, type='page', page_info=None): + def _postprocess_path(self, path, lang, always_append_index=False, type='page', page_info=None): + # Forcing extension for Atom feeds and RSS feeds + force_extension = None if type == 'feed': force_extension = '.atom' elif type == 'rss': force_extension = '.xml' + # Determine how to extend path if force_extension is not None: - if len(path) == 0 or always_append_index: + if len(path) == 0 and type == 'rss': + path = ['rss'] + elif len(path) == 0 or always_append_index: path = path + [os.path.splitext(self.site.config['INDEX_FILE'])[0]] - if type == 'rss': - path = ['rss'] path[-1] += force_extension elif self.site.config['PRETTY_URLS'] or len(path) == 0 or always_append_index: path = path + [self.site.config['INDEX_FILE']] else: path[-1] += '.html' + # Create path result = [_f for _f in [self.site.config['TRANSLATIONS'][lang]] + path if _f] if page_info is not None and type == 'page': result = utils.adjust_name_for_index_path_list(result, @@ -243,7 +247,7 @@ def _taxonomy_index_path(self, lang, taxonomy): path, append_index, _ = self._parse_path_result(result) return self._postprocess_path(path, lang, always_append_index=append_index, type='list') - def _taxonomy_path(self, name, lang, taxonomy, force_extension=None, type='page'): + def _taxonomy_path(self, name, lang, taxonomy, type='page'): """Return path to a classification.""" if taxonomy.has_hierarchy: result = taxonomy.get_path(taxonomy.extract_hierarchy(name), lang, type=type) @@ -257,7 +261,7 @@ def _taxonomy_path(self, name, lang, taxonomy, force_extension=None, type='page' number_of_pages = self._compute_number_of_pages(self._get_filtered_list(name, lang), self.site.config['INDEX_DISPLAY_POST_COUNT']) self.site.page_count_per_classification[taxonomy.classification_name][lang][name] = number_of_pages page_info = (page, number_of_pages) - return self._postprocess_path(path, lang, always_append_index=append_index, force_extension=force_extension, type=type, page_info=page_info) + return self._postprocess_path(path, lang, always_append_index=append_index, type=type, page_info=page_info) def _taxonomy_atom_path(self, name, lang, taxonomy): """Return path to a classification Atom feed.""" From d3f05d990fdad792d4bd9df5e247911327865405 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 18:40:12 +0200 Subject: [PATCH 63/74] Improved path generation. --- nikola/plugin_categories.py | 26 +++++++++++--------- nikola/plugins/misc/taxonomies_classifier.py | 13 +++++----- nikola/plugins/task/archive.py | 8 +++--- nikola/plugins/task/authors.py | 4 +-- nikola/plugins/task/indexes.py | 2 +- nikola/plugins/task/page_index.py | 2 +- nikola/plugins/task/sections.py | 2 +- 7 files changed, 30 insertions(+), 27 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 83be02763c..80043b6593 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -578,15 +578,16 @@ def get_list_path(self, lang, type='page'): Must return one or two values (in this order): * a list or tuple of strings: the path relative to OUTPUT_DIRECTORY; - * a boolean indicating whether INDEX_FILE should always be added or not; + * a string with values 'auto', 'always' or 'never', indicating whether + INDEX_FILE should be added or not. Note that this function must always return a list or tuple of strings; - the other return value is optional with default value `False`. + the other return value is optional with default value `'auto'`. - In case INDEX_FILE should not be added, the last element in the returned - path must have no extension, and the PRETTY_URLS config must be ignored - by this handler. The return value will be modified based on the - PRETTY_URLS and INDEX_FILE settings. + In case INDEX_FILE should potentially be added, the last element in the + returned path must have no extension, and the PRETTY_URLS config must + be ignored by this handler. The return value will be modified based on + the PRETTY_URLS and INDEX_FILE settings. Type can be either 'page', 'feed' (for Atom feed) or 'rss'. """ @@ -597,18 +598,19 @@ def get_path(self, classification, lang, type='page'): Must return one to three values (in this order): * a list or tuple of strings: the path relative to OUTPUT_DIRECTORY; - * a boolean indicating whether INDEX_FILE should always be added or not; + * a string with values 'auto', 'always' or 'never', indicating whether + INDEX_FILE should be added or not; * an integer if a specific page of the index is to be targeted (will be ignored for post lists), or `None` if the most current page is targeted. Note that this function must always return a list or tuple of strings; - the other two return values are optional with default values `False` and + the other two return values are optional with default values `'auto'` and `None`. - In case INDEX_FILE should not be added, the last element in the returned - path must have no extension, and the PRETTY_URLS config must be ignored - by this handler. The return value will be modified based on the - PRETTY_URLS and INDEX_FILE settings. + In case INDEX_FILE should potentially be added, the last element in the + returned path must have no extension, and the PRETTY_URLS config must + be ignored by this handler. The return value will be modified based on + the PRETTY_URLS and INDEX_FILE settings. Type can be either 'page', 'feed' (for Atom feed) or 'rss'. diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 0fc345c70c..68f31599bb 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -205,7 +205,7 @@ def _compute_number_of_pages(self, filtered_posts, posts_count): """Given a list of posts and the maximal number of posts per page, computes the number of pages needed.""" return min(1, (len(filtered_posts) + posts_count - 1) // posts_count) - def _postprocess_path(self, path, lang, always_append_index=False, type='page', page_info=None): + def _postprocess_path(self, path, lang, append_index='auto', type='page', page_info=None): # Forcing extension for Atom feeds and RSS feeds force_extension = None if type == 'feed': @@ -213,13 +213,14 @@ def _postprocess_path(self, path, lang, always_append_index=False, type='page', elif type == 'rss': force_extension = '.xml' # Determine how to extend path + path = [_f for _f in path if _f] if force_extension is not None: if len(path) == 0 and type == 'rss': path = ['rss'] - elif len(path) == 0 or always_append_index: + elif len(path) == 0 or append_index == 'always': path = path + [os.path.splitext(self.site.config['INDEX_FILE'])[0]] path[-1] += force_extension - elif self.site.config['PRETTY_URLS'] or len(path) == 0 or always_append_index: + elif (self.site.config['PRETTY_URLS'] and append_index != 'never') or len(path) == 0 or append_index == 'always': path = path + [self.site.config['INDEX_FILE']] else: path[-1] += '.html' @@ -239,13 +240,13 @@ def _parse_path_result(result): if not isinstance(result[0], (list, tuple)): # The result must be a list or tuple of strings. Wrap into a tuple result = (result, ) - return result[0], result[1] if len(result) > 1 else False, result[2] if len(result) > 2 else None + return result[0], result[1] if len(result) > 1 else 'auto', result[2] if len(result) > 2 else None def _taxonomy_index_path(self, lang, taxonomy): """Return path to the classification overview.""" result = taxonomy.get_list_path(lang) path, append_index, _ = self._parse_path_result(result) - return self._postprocess_path(path, lang, always_append_index=append_index, type='list') + return self._postprocess_path(path, lang, append_index=append_index, type='list') def _taxonomy_path(self, name, lang, taxonomy, type='page'): """Return path to a classification.""" @@ -261,7 +262,7 @@ def _taxonomy_path(self, name, lang, taxonomy, type='page'): number_of_pages = self._compute_number_of_pages(self._get_filtered_list(name, lang), self.site.config['INDEX_DISPLAY_POST_COUNT']) self.site.page_count_per_classification[taxonomy.classification_name][lang][name] = number_of_pages page_info = (page, number_of_pages) - return self._postprocess_path(path, lang, always_append_index=append_index, type=type, page_info=page_info) + return self._postprocess_path(path, lang, append_index=append_index, type=type, page_info=page_info) def _taxonomy_atom_path(self, name, lang, taxonomy): """Return path to a classification Atom feed.""" diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 11513994d0..9f70e1e398 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -111,11 +111,11 @@ def get_path(self, classification, lang, type='page'): components = [self.site.config['ARCHIVE_PATH']] if classification: components.extend(classification) - always_add_index = True + add_index = 'always' else: components.append(os.path.splitext(self.site.config['ARCHIVE_FILENAME'])[0]) - always_add_index = False - return [_f for _f in components if _f], always_add_index + add_index = 'never' + return [_f for _f in components if _f], add_index def extract_hierarchy(self, classification): """Given a classification, return a list of parts in the hierarchy.""" @@ -160,7 +160,7 @@ def provide_context_and_uptodate(self, classification, lang): "pagekind": [page_kind, "archive_page"], } if page_kind == 'index': - context["archive_name"] = classification + context["archive_name"] = classification if classification else None context["is_feed_stale"] = kw["is_feed_stale"] kw.update(context) return context, kw diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index abd6005b37..496cf5a838 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -74,7 +74,7 @@ def get_classification_printable_name(self, author, lang, only_last_component=Fa def get_list_path(self, lang, type='page'): """A path handler for the list of all classifications.""" - return [self.site.config['AUTHOR_PATH']], True + return [self.site.config['AUTHOR_PATH']], 'always' def get_path(self, author, lang, type='page'): """A path handler for the given classification.""" @@ -82,7 +82,7 @@ def get_path(self, author, lang, type='page'): slug = utils.slugify(author, lang) else: slug = author - return [self.site.config['AUTHOR_PATH'], slug], False + return [self.site.config['AUTHOR_PATH'], slug], 'auto' def provide_list_context_and_uptodate(self, lang): """Provide data for the context and the uptodate list for the list of all classifiations.""" diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index 37d77358f8..4fef190e68 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -79,7 +79,7 @@ def get_path(self, classification, lang, type='page'): page_number = int(classification) except: pass - return [self.site.config['INDEX_PATH']], True, page_number + return [self.site.config['INDEX_PATH']], 'always', page_number def provide_context_and_uptodate(self, classification, lang): """Provide data for the context and the uptodate list for the list of the given classifiation.""" diff --git a/nikola/plugins/task/page_index.py b/nikola/plugins/task/page_index.py index e1c6fea185..0556628d0f 100644 --- a/nikola/plugins/task/page_index.py +++ b/nikola/plugins/task/page_index.py @@ -71,7 +71,7 @@ def get_classification_printable_name(self, hierarchy, lang, only_last_component def get_path(self, hierarchy, lang, type='page'): """A path handler for the given classification.""" - return hierarchy, True + return hierarchy, 'always' def extract_hierarchy(self, dirname): """Given a classification, return a list of parts in the hierarchy.""" diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py index 01e216b18e..d57a684036 100644 --- a/nikola/plugins/task/sections.py +++ b/nikola/plugins/task/sections.py @@ -80,7 +80,7 @@ def get_classification_printable_name(self, section, lang, only_last_component=F def get_path(self, section, lang, type='page'): """A path handler for the given classification.""" - return [_f for _f in [self.site.config['TRANSLATIONS'][lang], section] if _f], True + return [_f for _f in [self.site.config['TRANSLATIONS'][lang], section] if _f], 'always' def provide_context_and_uptodate(self, section, lang): """Provide data for the context and the uptodate list for the list of the given classifiation. From fc8d6f3c09b621c8316901b8456e6e306a94bb63 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 18:45:19 +0200 Subject: [PATCH 64/74] Restored old title for main index. Thanks to @Kwpolska for noticing. --- nikola/plugins/task/indexes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index 4fef190e68..fd3e11cd63 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -86,7 +86,7 @@ def provide_context_and_uptodate(self, classification, lang): kw = { } context = { - "title": self.site.config["BLOG_TITLE"](lang), + "title": self.site.config["INDEXES_TITLE"](lang) or self.site.config["BLOG_TITLE"](lang), "classification_title": "", "description": self.site.config["BLOG_DESCRIPTION"](lang), "pagekind": ["main_index", "index"], From 7b4d25ceb02277c1c5fc3379491a6a9eaed60918 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 19:09:52 +0200 Subject: [PATCH 65/74] Make sure that empty archive pages are not generated except for the main archive page. --- nikola/plugins/task/archive.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 9f70e1e398..f034aa460f 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -164,3 +164,7 @@ def provide_context_and_uptodate(self, classification, lang): context["is_feed_stale"] = kw["is_feed_stale"] kw.update(context) return context, kw + + def should_generate_classification_list(self, classification, post_list, lang): + """Only generates list of posts for classification if this function returns True.""" + return classification == "" or len(post_list) > 0 From 3d40b402af254ce082c8f30f8331eada57b71baf Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 19:43:53 +0200 Subject: [PATCH 66/74] Fixed confusion about rss_link. --- nikola/plugin_categories.py | 3 +-- nikola/plugins/task/archive.py | 1 - nikola/plugins/task/authors.py | 5 ++++- nikola/plugins/task/indexes.py | 1 - nikola/plugins/task/page_index.py | 1 - nikola/plugins/task/sections.py | 10 +--------- nikola/plugins/task/taxonomies.py | 4 ---- 7 files changed, 6 insertions(+), 19 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 80043b6593..cc942ba486 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -651,8 +651,7 @@ def provide_context_and_uptodate(self, classification, lang): Must return a tuple of two dicts. The first is merged into the page's context, the second will be put into the uptodate list of all generated tasks. - Context must contain `title`, which should be something like 'Posts about ', - and `classification_title`, which should be related to the classification string. + Context must contain `title`, which should be something like 'Posts about '. """ raise NotImplementedError() diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index f034aa460f..7e5b58f3de 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -156,7 +156,6 @@ def provide_context_and_uptodate(self, classification, lang): raise Exception("Cannot interpret classification {}!".format(repr(classification))) context = { "title": title, - "classification_title": classification, "pagekind": [page_kind, "archive_page"], } if page_kind == 'index': diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index 496cf5a838..11ac9ddb01 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -107,10 +107,13 @@ def provide_context_and_uptodate(self, author, lang): context = { "author": author, "title": kw["messages"][lang]["Posts by %s"] % author, - "classification_title": author, "description": descriptions[lang][author] if lang in descriptions and author in descriptions[lang] else None, "pagekind": ["index" if self.show_list_as_index else "list", "author_page"], } + if self.site.config["GENERATE_RSS"]: + rss_link = ("""""".format( + author, lang, self.site.link('author_rss', classification, lang))) + context['rss_link'] = rss_link kw.update(context) return context, kw diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index fd3e11cd63..f48c08d179 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -87,7 +87,6 @@ def provide_context_and_uptodate(self, classification, lang): } context = { "title": self.site.config["INDEXES_TITLE"](lang) or self.site.config["BLOG_TITLE"](lang), - "classification_title": "", "description": self.site.config["BLOG_DESCRIPTION"](lang), "pagekind": ["main_index", "index"], } diff --git a/nikola/plugins/task/page_index.py b/nikola/plugins/task/page_index.py index 0556628d0f..9773eae463 100644 --- a/nikola/plugins/task/page_index.py +++ b/nikola/plugins/task/page_index.py @@ -89,7 +89,6 @@ def provide_context_and_uptodate(self, dirname, lang): } context = { "title": self.site.config['BLOG_TITLE'](lang), - "classification_title": self.site.config['BLOG_TITLE'](lang), "pagekind": ["list", "front_page"] if dirname == '' else ["list"], } kw.update(context) diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py index d57a684036..20440fcb4f 100644 --- a/nikola/plugins/task/sections.py +++ b/nikola/plugins/task/sections.py @@ -83,14 +83,7 @@ def get_path(self, section, lang, type='page'): return [_f for _f in [self.site.config['TRANSLATIONS'][lang], section] if _f], 'always' def provide_context_and_uptodate(self, section, lang): - """Provide data for the context and the uptodate list for the list of the given classifiation. - - Must return a tuple of two dicts. The first is merged into the page's context, - the second will be put into the uptodate list of all generated tasks. - - Context must contain `title`, which should be something like 'Posts about ', - and `classification_title`, which should be related to the classification string. - """ + """Provide data for the context and the uptodate list for the list of the given classifiation.""" kw = { "messages": self.site.MESSAGES, } @@ -107,7 +100,6 @@ def provide_context_and_uptodate(self, section, lang): # Compose context context = { "title": section_title, - "classification_title": section_name, "description": self.site.config['POSTS_SECTION_DESCRIPTIONS'](lang)[section] if section in self.site.config['POSTS_SECTION_DESCRIPTIONS'](lang) else "", "pagekind": ["section_page", "index" if self.show_list_as_index else "list"] } diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index eec1ad7782..f1e809048b 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -157,10 +157,6 @@ def page_path(i, displayed_i, num_pages, force_addition, extension=None): return utils.adjust_name_for_index_path(self.site.path(feed.format(kind), classification, lang), i, displayed_i, lang, self.site, force_addition, extension) context = copy(context) - if kw["generate_rss"] and not taxonomy.always_disable_rss: - rss_link = ("""""".format( - taxonomy.classification_name, context['classification_title'], lang, self.site.link('{}_rss'.format(kind), classification, lang))) - context['rss_link'] = rss_link if "pagekind" not in context: context["pagekind"] = ["index", "tag_page"] template_name = taxonomy.template_for_list_of_one_classification From 45ee5411ca0f9d033d8d483fba02adf00c1db773 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 19:51:47 +0200 Subject: [PATCH 67/74] Fixing stupid typo. --- nikola/plugins/task/authors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index 11ac9ddb01..31425aada7 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -112,7 +112,7 @@ def provide_context_and_uptodate(self, author, lang): } if self.site.config["GENERATE_RSS"]: rss_link = ("""""".format( - author, lang, self.site.link('author_rss', classification, lang))) + author, lang, self.site.link('author_rss', author, lang))) context['rss_link'] = rss_link kw.update(context) return context, kw From 650affef3ceb1895b18fcc7f8ef25165d76ffce9 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 20 Oct 2016 19:53:03 +0200 Subject: [PATCH 68/74] Replicating strange behavior of archives plugin. --- nikola/plugins/task/archive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 7e5b58f3de..f73e272f39 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -166,4 +166,4 @@ def provide_context_and_uptodate(self, classification, lang): def should_generate_classification_list(self, classification, post_list, lang): """Only generates list of posts for classification if this function returns True.""" - return classification == "" or len(post_list) > 0 + return len(classification.split('/')) < 3 or len(post_list) > 0 From 0a3927daf5d8955b23e52507093a844de53cd9b3 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 22 Nov 2016 21:42:12 +0100 Subject: [PATCH 69/74] Trying to improve documentation of taxonomy plugin class by putting attribute docs into docstring. --- nikola/plugin_categories.py | 117 ++++++++++++++++++++++++------------ 1 file changed, 78 insertions(+), 39 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index 988d4a453f..f59f0ec30c 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -478,67 +478,106 @@ class Taxonomy(BasePlugin): """Taxonomy for posts. A taxonomy plugin allows to classify posts (see #2107) by - classification strings. + classification strings. Classification plugins must adjust + a set of options to determine certain aspects. + + The following options are class attributes with their default + values: + + classification_name = "taxonomy": + The classification name to be used for path handlers. + + metadata_name = "taxonomy": + The classification name to be used when storing the classification + in the metadata. If set to None, the classification won't be stored + in the metadata. + + overview_page_variable_name = "taxonomy": + Variable for the overview page template which contains the list of + classifications. + + more_than_one_classifications_per_post = False: + If True, there can be more than one classification per post; in that case, + the classification data in the metadata is stored as a list. If False, + the classification data in the metadata is stored as a string, or None + when no classification is given. + + has_hierarchy = False: + Whether the classification has a hierarchy. + + include_posts_from_subhierarchies = False: + If True, the list for a classification includes all posts with a + sub-classification (in case has_hierarchy is True). + + include_posts_into_hierarchy_root = False: + If True, include_posts_from_subhierarchies == True will also insert + posts into the list for the empty hierarchy []. + + show_list_as_subcategories_list = False: + If not False, for every classification which has at least one + subclassification, create a list of subcategories instead of a list/index + of posts. This is only used when has_hierarchy = True. If not False, this + must be the template name for the list; usually "list.tmpl". + If this is set to a string, it is recommended to set + include_posts_from_subhierarchies to True to get correct post counts. + + show_list_as_index = False: + Whether to show the posts for one classification as an index or + as a post list. + + generate_atom_feeds_for_post_lists = False: + Whether to generate Atom feeds for post lists in case GENERATE_ATOM is set. + + template_for_list_of_one_classification = "tagindex.tmpl": + The template to use for the post list for one classification. + + template_for_classification_overview = "list.tmpl": + The template to use for the classification overview page. + Set to None to avoid generating overviews. + + always_disable_rss = False: + Whether to always disable RSS feed generation + + apply_to_posts = True: + Whether this classification applies to posts. + + apply_to_pages = False: + Whether this classification applies to pages. + + minimum_post_count_per_classification_in_overview = 1: + The minimum number of posts a classification must have to be listed in + the overview. + + omit_empty_classifications = False: + Whether post lists resp. indexes should be created for empty + classifications. + + also_create_classifications_from_other_languages = True: + Whether to include all classifications for all languages in every + language, or only the classifications for one language in its language's + pages. """ name = "dummy_taxonomy" # Adjust the following values in your plugin! - - # The classification name to be used for path handlers. classification_name = "taxonomy" - # The classification name to be used when storing the classification - # in the metadata. If set to None, the classification won't be stored - # in the metadata. metadata_name = "taxonomy" - # Variable for the overview page template which contains the list of - # classifications. overview_page_variable_name = "taxonomy" - # If True, there can be more than one classification per post; in that case, - # the classification data in the metadata is stored as a list. If False, - # the classification data in the metadata is stored as a string, or None - # when no classification is given. more_than_one_classifications_per_post = False - # Whether the classification has a hierarchy. has_hierarchy = False - # If True, the list for a classification includes all posts with a - # sub-classification (in case has_hierarchy is True). include_posts_from_subhierarchies = False - # If True, include_posts_from_subhierarchies == True will also insert - # posts into the list for the empty hierarchy []. include_posts_into_hierarchy_root = False - # If not False, for every classification which has at least one - # subclassification, create a list of subcategories instead of a list/index - # of posts. This is only used when has_hierarchy = True. If not False, this - # must be the template name for the list; usually "list.tmpl". - # If this is set to a string, it is recommended to set - # include_posts_from_subhierarchies to True to get correct post counts. show_list_as_subcategories_list = False - # Whether to show the posts for one classification as an index or - # as a post list. show_list_as_index = False - # Whether to generate Atom feeds for post lists in case GENERATE_ATOM is set. generate_atom_feeds_for_post_lists = False - # The template to use for the post list for one classification. template_for_list_of_one_classification = "tagindex.tmpl" - # The template to use for the classification overview page. - # Set to None to avoid generating overviews. template_for_classification_overview = "list.tmpl" - # Whether to always disable RSS feed generation always_disable_rss = False - # Whether this classification applies to posts. apply_to_posts = True - # Whether this classification applies to pages. apply_to_pages = False - # The minimum number of posts a classification must have to be listed in - # the overview. minimum_post_count_per_classification_in_overview = 1 - # Whether post lists resp. indexes should be created for empty - # classifications. omit_empty_classifications = False - # Whether to include all classifications for all languages in every - # language, or only the classifications for one language in its language's - # pages. also_create_classifications_from_other_languages = True def is_enabled(self, lang=None): From 2e88e0283e818d61d9d9c8ea75a29b11922750bb Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 27 Nov 2016 21:30:45 +0100 Subject: [PATCH 70/74] Improving various documentations and namings. --- nikola/plugin_categories.py | 28 ++++++++----- nikola/plugins/misc/taxonomies_classifier.py | 43 +++++++++++++++----- nikola/plugins/task/archive.py | 6 +-- nikola/plugins/task/authors.py | 6 +-- nikola/plugins/task/indexes.py | 6 +-- nikola/plugins/task/page_index.py | 6 +-- nikola/plugins/task/sections.py | 6 +-- nikola/plugins/task/taxonomies.py | 2 +- 8 files changed, 67 insertions(+), 36 deletions(-) diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index f59f0ec30c..1d0090776f 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -482,7 +482,8 @@ class Taxonomy(BasePlugin): a set of options to determine certain aspects. The following options are class attributes with their default - values: + values. These variables should be set in the class definition, + in the constructor or latest in the `set_site` function. classification_name = "taxonomy": The classification name to be used for path handlers. @@ -493,8 +494,8 @@ class Taxonomy(BasePlugin): in the metadata. overview_page_variable_name = "taxonomy": - Variable for the overview page template which contains the list of - classifications. + When rendering the overview page, its template will have a list + of classifications available in a variable by this name. more_than_one_classifications_per_post = False: If True, there can be more than one classification per post; in that case, @@ -528,7 +529,7 @@ class Taxonomy(BasePlugin): generate_atom_feeds_for_post_lists = False: Whether to generate Atom feeds for post lists in case GENERATE_ATOM is set. - template_for_list_of_one_classification = "tagindex.tmpl": + template_for_single_list = "tagindex.tmpl": The template to use for the post list for one classification. template_for_classification_overview = "list.tmpl": @@ -571,7 +572,7 @@ class Taxonomy(BasePlugin): show_list_as_subcategories_list = False show_list_as_index = False generate_atom_feeds_for_post_lists = False - template_for_list_of_one_classification = "tagindex.tmpl" + template_for_single_list = "tagindex.tmpl" template_for_classification_overview = "list.tmpl" always_disable_rss = False apply_to_posts = True @@ -604,23 +605,30 @@ def classify(self, post, lang): def sort_posts(self, posts, classification, lang): """Sort the given list of posts. - The sort must happen in-place. + Allows the plugin to order the posts per classification as it wants. + The posts will be ordered by date (latest first) before calling + this function. This function must sort in-place. """ pass def sort_classifications(self, classifications, lang, level=None): """Sort the given list of classification strings. + Allows the plugin to order the classifications as it wants. The + classifications will be ordered by `natsort` before calling this + function. This function must sort in-place. + For hierarchical taxonomies, the elements of the list are a single path element of the path returned by `extract_hierarchy()`. The index of the path element in the path will be provided in `level`. - - The sort must happen in-place. """ pass - def get_classification_printable_name(self, classification, lang, only_last_component=False): - """Extract a printable name from the classification. + def get_classification_friendly_name(self, classification, lang, only_last_component=False): + """Extract a friendly name from the classification. + + The result of this function is usually displayed to the user, instead + of using the classification string. For hierarchical taxonomies, the result of extract_hierarchy is provided as `classification`. For non-hierarchical taxonomies, the classification diff --git a/nikola/plugins/misc/taxonomies_classifier.py b/nikola/plugins/misc/taxonomies_classifier.py index 68f31599bb..d33cb66e2b 100644 --- a/nikola/plugins/misc/taxonomies_classifier.py +++ b/nikola/plugins/misc/taxonomies_classifier.py @@ -44,7 +44,10 @@ class TaxonomiesClassifier(SignalHandler): name = "render_taxonomies" def _do_classification(self, site): - taxonomies = [p.plugin_object for p in site.plugin_manager.getPluginsOfCategory('Taxonomy') if p.plugin_object.is_enabled()] + # Get list of enabled taxonomy plugins + taxonomies = [p.plugin_object for p in site.plugin_manager.getPluginsOfCategory('Taxonomy')] + taxonomies = [taxonomy for taxonomy in taxonomies if taxonomy.is_enabled()] + # Prepare classification and check for collisions site.posts_per_classification = {} for taxonomy in taxonomies: if taxonomy.classification_name in site.posts_per_classification: @@ -189,23 +192,38 @@ def create_hierarchy(hierarchy, parent=None, level=0): else: taxonomy.postprocess_posts_per_classification(site.posts_per_classification[taxonomy.classification_name]) - def _filter_list(self, post_list, lang): - """Return only the posts which should be shown for this language.""" + def _get_filtered_list(self, taxonomy, classification, lang): + """Return the filtered list of posts for this classification and language.""" + post_list = self.site.posts_per_classification[taxonomy.classification_name][lang].get(classification, []) if self.site.config["SHOW_UNTRANSLATED_POSTS"]: return post_list else: return [x for x in post_list if x.is_translation_available(lang)] - def _get_filtered_list(self, taxonomy, classification, lang): - """Return the filtered list of posts for this classification and language.""" - return self._filter_list(self.site.posts_per_classification[taxonomy.classification_name][lang].get(classification, []), lang) - @staticmethod - def _compute_number_of_pages(self, filtered_posts, posts_count): + def _compute_number_of_pages(filtered_posts, posts_count): """Given a list of posts and the maximal number of posts per page, computes the number of pages needed.""" return min(1, (len(filtered_posts) + posts_count - 1) // posts_count) def _postprocess_path(self, path, lang, append_index='auto', type='page', page_info=None): + """Postprocess a generated path. + + Takes the path `path` for language `lang`, and postprocesses it. + + It appends `site.config['INDEX_FILE']` depending on `append_index` + (which can have the values `'always'`, `'never'` and `'auto'`) and + `site.config['PRETTY_URLS']`. + + It also modifies/adds the extension of the last path element resp. + `site.config['INDEX_FILE']` depending on `type`, which can be + `'feed'`, `'rss'` or `'page'`. + + Finally, if `type` is `'page'`, `page_info` can be `None` or a tuple + of two integers: the page number and the number of pages. This will + be used to append the correct page number by calling + `utils.adjust_name_for_index_path_list` and + `utils.get_displayed_page_number`. + """ # Forcing extension for Atom feeds and RSS feeds force_extension = None if type == 'feed': @@ -240,7 +258,10 @@ def _parse_path_result(result): if not isinstance(result[0], (list, tuple)): # The result must be a list or tuple of strings. Wrap into a tuple result = (result, ) - return result[0], result[1] if len(result) > 1 else 'auto', result[2] if len(result) > 2 else None + path = result[0] + append_index = result[1] if len(result) > 1 else 'auto' + page_info = result[2] if len(result) > 2 else None + return path, append_index, page_info def _taxonomy_index_path(self, lang, taxonomy): """Return path to the classification overview.""" @@ -284,5 +305,7 @@ def set_site(self, site): # Add hook for after post scanning blinker.signal("scanned").connect(self._do_classification) # Register path handlers - for taxonomy in [p.plugin_object for p in site.plugin_manager.getPluginsOfCategory('Taxonomy') if p.plugin_object.is_enabled()]: + for taxonomy in [p.plugin_object for p in site.plugin_manager.getPluginsOfCategory('Taxonomy')]: + if not taxonomy.is_enabled(): + continue self._register_path_handlers(taxonomy) diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index f73e272f39..4c4c2d9007 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -61,7 +61,7 @@ def set_site(self, site): # Finish setup self.show_list_as_subcategories_list = False if site.config['CREATE_FULL_ARCHIVES'] else "list.tmpl" self.show_list_as_index = site.config['ARCHIVES_ARE_INDEXES'] - self.template_for_list_of_one_classification = "archiveindex.tmpl" if site.config['ARCHIVES_ARE_INDEXES'] else "list_post.tmpl" + self.template_for_single_list = "archiveindex.tmpl" if site.config['ARCHIVES_ARE_INDEXES'] else "list_post.tmpl" # Determine maximal hierarchy height if site.config['CREATE_DAILY_ARCHIVE'] or site.config['CREATE_FULL_ARCHIVES']: self.max_levels = 3 @@ -94,8 +94,8 @@ def sort_classifications(self, classifications, lang, level=None): classifications.sort() classifications.reverse() - def get_classification_printable_name(self, classification, lang, only_last_component=False): - """Extract a printable name from the classification.""" + def get_classification_friendly_name(self, classification, lang, only_last_component=False): + """Extract a friendly name from the classification.""" if len(classification) == 0: return "" elif len(classification) == 1: diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index 31425aada7..214a06e050 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -53,7 +53,7 @@ class ClassifyAuthors(Taxonomy): def set_site(self, site): """Set Nikola site.""" self.show_list_as_index = site.config['AUTHOR_PAGES_ARE_INDEXES'] - self.template_for_list_of_one_classification = "authorindex.tmpl" if self.show_list_as_index else "author.tmpl" + self.template_for_single_list = "authorindex.tmpl" if self.show_list_as_index else "author.tmpl" return super(ClassifyAuthors, self).set_site(site) def is_enabled(self, lang=None): @@ -68,8 +68,8 @@ def classify(self, post, lang): """Classify the given post for the given language.""" return [post.author()] - def get_classification_printable_name(self, author, lang, only_last_component=False): - """Extract a printable name from the classification.""" + def get_classification_friendly_name(self, author, lang, only_last_component=False): + """Extract a friendly name from the classification.""" return author def get_list_path(self, lang, type='page'): diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index f48c08d179..2fb99eba44 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -42,7 +42,7 @@ class Indexes(Taxonomy): more_than_one_classifications_per_post = False has_hierarchy = False show_list_as_index = True - template_for_list_of_one_classification = "index.tmpl" + template_for_single_list = "index.tmpl" template_for_classification_overview = None apply_to_posts = True apply_to_pages = False @@ -63,8 +63,8 @@ def classify(self, post, lang): """Classify the given post for the given language.""" return [""] - def get_classification_printable_name(self, classification, lang, only_last_component=False): - """Extract a printable name from the classification.""" + def get_classification_friendly_name(self, classification, lang, only_last_component=False): + """Extract a friendly name from the classification.""" return self.site.config["BLOG_TITLE"](lang) def get_path(self, classification, lang, type='page'): diff --git a/nikola/plugins/task/page_index.py b/nikola/plugins/task/page_index.py index 9773eae463..1bf98c7d56 100644 --- a/nikola/plugins/task/page_index.py +++ b/nikola/plugins/task/page_index.py @@ -44,7 +44,7 @@ class PageIndex(Taxonomy): include_posts_from_subhierarchies = False show_list_as_index = False generate_atom_feeds_for_post_lists = False - template_for_list_of_one_classification = "list.tmpl" + template_for_single_list = "list.tmpl" template_for_classification_overview = None always_disable_rss = True apply_to_posts = False @@ -65,8 +65,8 @@ def classify(self, post, lang): i = destpath.rfind('/') return destpath[:i] if i >= 0 else '' - def get_classification_printable_name(self, hierarchy, lang, only_last_component=False): - """Extract a printable name from the classification.""" + def get_classification_friendly_name(self, hierarchy, lang, only_last_component=False): + """Extract a friendly name from the classification.""" return '/'.join(hierarchy) def get_path(self, hierarchy, lang, type='page'): diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py index 20440fcb4f..c0e583c88a 100644 --- a/nikola/plugins/task/sections.py +++ b/nikola/plugins/task/sections.py @@ -51,7 +51,7 @@ class ClassifySections(Taxonomy): def set_site(self, site): """Set Nikola site.""" self.show_list_as_index = site.config["POSTS_SECTION_ARE_INDEXES"] - self.template_for_list_of_one_classification = "sectionindex.tmpl" if self.show_list_as_index else "list.tmpl" + self.template_for_single_list = "sectionindex.tmpl" if self.show_list_as_index else "list.tmpl" self.enable_for_lang = {} return super(ClassifySections, self).set_site(site) @@ -74,8 +74,8 @@ def _get_section_name(self, section, lang): else: return section.replace('-', ' ').title() - def get_classification_printable_name(self, section, lang, only_last_component=False): - """Extract a printable name from the classification.""" + def get_classification_friendly_name(self, section, lang, only_last_component=False): + """Extract a friendly name from the classification.""" return self._get_section_name(section, lang) def get_path(self, section, lang, type='page'): diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index f1e809048b..baf7703711 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -210,7 +210,7 @@ def _generate_subclassification_page(self, taxonomy, node, context, kw, lang): """Render a list of subclassifications.""" def get_subnode_data(subnode): return [ - taxonomy.get_classification_printable_name(subnode.classification_path, lang, only_last_component=True), + taxonomy.get_classification_friendly_name(subnode.classification_path, lang, only_last_component=True), self.site.link(taxonomy.classification_name, subnode.classification_name, lang), len(self._filter_list(self.site.posts_per_classification[taxonomy.classification_name][lang][subnode.classification_name], lang)) ] From cf19dc2400f06ba167525223a0b5556070793bf2 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 27 Nov 2016 21:42:34 +0100 Subject: [PATCH 71/74] Improved archives plugin. --- nikola/plugins/task/archive.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 4c4c2d9007..fb3b5e51b2 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -62,7 +62,7 @@ def set_site(self, site): self.show_list_as_subcategories_list = False if site.config['CREATE_FULL_ARCHIVES'] else "list.tmpl" self.show_list_as_index = site.config['ARCHIVES_ARE_INDEXES'] self.template_for_single_list = "archiveindex.tmpl" if site.config['ARCHIVES_ARE_INDEXES'] else "list_post.tmpl" - # Determine maximal hierarchy height + # Determine maximum hierarchy height if site.config['CREATE_DAILY_ARCHIVE'] or site.config['CREATE_FULL_ARCHIVES']: self.max_levels = 3 elif site.config['CREATE_MONTHLY_ARCHIVE']: @@ -73,19 +73,14 @@ def set_site(self, site): self.max_levels = 1 return super(Archive, self).set_site(site) - def is_enabled(self, lang=None): - """Return True if this taxonomy is enabled, or False otherwise.""" - return True - def get_implicit_classifications(self, lang): """Return a list of classification strings which should always appear in posts_per_classification.""" return [''] def classify(self, post, lang): """Classify the given post for the given language.""" - levels = ['{year:04d}', '{month:02d}', '{day:02d}'][:self.max_levels] - levels = [level.format(year=post.date.year, month=post.date.month, day=post.date.day) for level in levels] - return ['/'.join(levels)] + levels = [str(post.date.year).zfill(4), str(post.date.month).zfill(2), str(post.date.day).zfill(2)] + return ['/'.join(levels[:self.max_levels])] def sort_classifications(self, classifications, lang, level=None): """Sort the given list of classification strings.""" From 0bb4a84b5b86ad97506efb1d4710f416e6311cee Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 27 Nov 2016 21:50:32 +0100 Subject: [PATCH 72/74] Improved some more plugins. --- nikola/plugins/task/indexes.py | 8 +------- nikola/plugins/task/page_index.py | 4 ++-- nikola/plugins/task/sections.py | 6 +++--- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index 2fb99eba44..50aeb70017 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -24,7 +24,7 @@ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Render the blog indexes.""" +"""Render the blog's main index.""" from __future__ import unicode_literals @@ -92,9 +92,3 @@ def provide_context_and_uptodate(self, classification, lang): } kw.update(context) return context, kw - - def postprocess_posts_per_classification(self, posts_per_classification_per_language, flat_hierarchy_per_lang=None, hierarchy_lookup_per_lang=None): - """Rearrange, modify or otherwise use the list of posts per classification and per language.""" - for lang, posts_per_classification in posts_per_classification_per_language.items(): - if len(posts_per_classification) == 0: - posts_per_classification[""] = [] diff --git a/nikola/plugins/task/page_index.py b/nikola/plugins/task/page_index.py index 1bf98c7d56..818b7a29b1 100644 --- a/nikola/plugins/task/page_index.py +++ b/nikola/plugins/task/page_index.py @@ -24,7 +24,7 @@ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Render the blog indexes.""" +"""Render the page index.""" from __future__ import unicode_literals @@ -32,7 +32,7 @@ class PageIndex(Taxonomy): - """Render the page index.""" + """Classify for the page index.""" name = "classify_page_index" diff --git a/nikola/plugins/task/sections.py b/nikola/plugins/task/sections.py index c0e583c88a..42be9acf91 100644 --- a/nikola/plugins/task/sections.py +++ b/nikola/plugins/task/sections.py @@ -87,14 +87,14 @@ def provide_context_and_uptodate(self, section, lang): kw = { "messages": self.site.MESSAGES, } - section_name = self._get_Section_name(section, lang) + section_name = self._get_section_name(section, lang) # Compose section title section_title = section_name posts_section_title = self.site.config['POSTS_SECTION_TITLE'](lang) - if type(posts_section_title) is dict: + if isinstance(posts_section_title, dict): if section in posts_section_title: section_title = posts_section_title[section] - elif type(posts_section_title) is str: + elif isinstance(posts_section_title, utils.bytes_str, utils.unicode_str): section_title = posts_section_title section_title = section_title.format(name=section_name) # Compose context From 6e9c5740830f7eed07a7aca0df0fd5f4e71dbfae Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 27 Nov 2016 21:52:09 +0100 Subject: [PATCH 73/74] Forgot something. --- nikola/plugins/task/taxonomies.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index baf7703711..baa5681032 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -159,7 +159,7 @@ def page_path(i, displayed_i, num_pages, force_addition, extension=None): context = copy(context) if "pagekind" not in context: context["pagekind"] = ["index", "tag_page"] - template_name = taxonomy.template_for_list_of_one_classification + template_name = taxonomy.template_for_single_list yield self.site.generic_index_renderer(lang, filtered_posts, context['title'], template_name, context, kw, str(self.name), page_link, page_path) @@ -184,7 +184,7 @@ def _generate_classification_page_as_list_atom(self, taxonomy, classification, f def _generate_classification_page_as_list(self, taxonomy, classification, filtered_posts, context, kw, lang): """Render a single flat link list with this classification's posts.""" kind = taxonomy.classification_name - template_name = taxonomy.template_for_list_of_one_classification + template_name = taxonomy.template_for_single_list output_name = os.path.join(self.site.config['OUTPUT_FOLDER'], self.site.path(kind, classification, lang)) context["lang"] = lang context["posts"] = filtered_posts From c816b63a29cdce2b392170c4477d9e223cb7c2ef Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 27 Nov 2016 21:54:38 +0100 Subject: [PATCH 74/74] Fixing typos. --- nikola/nikola.py | 2 +- nikola/plugins/task/tags.py | 4 ++-- nikola/plugins/task/taxonomies.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nikola/nikola.py b/nikola/nikola.py index 9a367aff19..96e71d3af8 100644 --- a/nikola/nikola.py +++ b/nikola/nikola.py @@ -2346,7 +2346,7 @@ def generic_index_renderer(self, lang, posts, indexes_title, template_name, cont kw['indexes_prety_page_url'] = self.config["INDEXES_PRETTY_PAGE_URL"] kw['demote_headers'] = self.config['DEMOTE_HEADERS'] kw['generate_atom'] = self.config["GENERATE_ATOM"] - kw['feed_link_append_query'] = self.config["FEED_LINKS_APPEND_QUERY"] + kw['feed_links_append_query'] = self.config["FEED_LINKS_APPEND_QUERY"] kw['currentfeed'] = None # Split in smaller lists diff --git a/nikola/plugins/task/tags.py b/nikola/plugins/task/tags.py index 6ee0e4a987..b18d5c63af 100644 --- a/nikola/plugins/task/tags.py +++ b/nikola/plugins/task/tags.py @@ -75,7 +75,7 @@ def gen_tasks(self): "generate_rss": self.site.config['GENERATE_RSS'], "feed_teasers": self.site.config["FEED_TEASERS"], "feed_plain": self.site.config["FEED_PLAIN"], - "feed_link_append_query": self.site.config["FEED_LINKS_APPEND_QUERY"], + "feed_links_append_query": self.site.config["FEED_LINKS_APPEND_QUERY"], "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'], "feed_length": self.site.config['FEED_LENGTH'], "taglist_minimum_post_count": self.site.config['TAGLIST_MINIMUM_POSTS'], @@ -379,7 +379,7 @@ def tag_rss(self, tag, lang, posts, kw, is_category): (lang, "{0} ({1})".format(kw["blog_title"](lang), self._get_title(tag, is_category)), kw["site_url"], None, post_list, output_name, kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], - feed_url, _enclosure, kw["feed_link_append_query"]))], + feed_url, _enclosure, kw["feed_links_append_query"]))], 'clean': True, 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.tags:rss')] + deps_uptodate, 'task_dep': ['render_posts'], diff --git a/nikola/plugins/task/taxonomies.py b/nikola/plugins/task/taxonomies.py index baa5681032..fbb306ff99 100644 --- a/nikola/plugins/task/taxonomies.py +++ b/nikola/plugins/task/taxonomies.py @@ -137,7 +137,7 @@ def _generate_classification_page_as_rss(self, taxonomy, classification, filtere (lang, "{0} ({1})".format(blog_title, title) if blog_title != title else blog_title, kw["site_url"], description, filtered_posts, output_name, kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], - feed_url, _enclosure, kw["feed_link_append_query"]))], + feed_url, _enclosure, kw["feed_links_append_query"]))], 'clean': True, 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.taxonomies:rss')] + deps_uptodate, 'task_dep': ['render_posts'], @@ -254,7 +254,7 @@ def _generate_classification_page(self, taxonomy, classification, post_list, lan kw['generate_rss'] = self.site.config['GENERATE_RSS'] kw["feed_teasers"] = self.site.config["FEED_TEASERS"] kw["feed_plain"] = self.site.config["FEED_PLAIN"] - kw["feed_link_append_query"] = self.site.config["FEED_LINKS_APPEND_QUERY"] + kw["feed_links_append_query"] = self.site.config["FEED_LINKS_APPEND_QUERY"] kw["feed_length"] = self.site.config['FEED_LENGTH'] kw["output_folder"] = self.site.config['OUTPUT_FOLDER'] context = copy(context)