diff options
| author | 2015-11-11 16:34:34 -0300 | |
|---|---|---|
| committer | 2015-11-11 16:34:34 -0300 | |
| commit | 4e3224c012df9f74f010eb92203520515e8537b9 (patch) | |
| tree | 19322dc0c595268cb6864f21d7e92fd93cb826e9 /nikola/plugins/task | |
| parent | 787b97a4cb24330b36f11297c6d3a7a473a907d0 (diff) | |
Imported Upstream version 7.7.3upstream/7.7.3
Diffstat (limited to 'nikola/plugins/task')
38 files changed, 821 insertions, 121 deletions
diff --git a/nikola/plugins/task/archive.plugin b/nikola/plugins/task/archive.plugin index 25f1195..eb079da 100644 --- a/nikola/plugins/task/archive.plugin +++ b/nikola/plugins/task/archive.plugin @@ -5,7 +5,7 @@ module = archive [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Generates the blog's archive pages. [Nikola] diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 126aed4..3cdd33b 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -37,7 +37,6 @@ from nikola.utils import config_changed, adjust_name_for_index_path, adjust_name class Archive(Task): - """Render the post archives.""" name = "render_archive" @@ -53,7 +52,7 @@ class Archive(Task): """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 be None + # 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) @@ -175,10 +174,10 @@ class Archive(Task): 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)) for m in self.site.posts_per_month.keys() if m.startswith(str(year))]) + 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] for month, link in months] + 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"]: @@ -219,11 +218,16 @@ class Archive(Task): years.sort(reverse=True) kw['years'] = years for lang in kw["translations"]: - items = [(y, self.site.link("archive", y, lang)) for y in years] + 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): - """Return archive paths.""" + """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 @@ -241,5 +245,10 @@ class Archive(Task): archive_file] if _f] def archive_atom_path(self, name, lang): - """Return Atom archive paths.""" + """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) diff --git a/nikola/plugins/task/authors.plugin b/nikola/plugins/task/authors.plugin new file mode 100644 index 0000000..3fc4ef2 --- /dev/null +++ b/nikola/plugins/task/authors.plugin @@ -0,0 +1,10 @@ +[Core] +Name = render_authors +Module = authors + +[Documentation] +Author = Juanjo Conti +Version = 0.1 +Website = http://getnikola.com +Description = Render the author pages and feeds. + diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py new file mode 100644 index 0000000..081d21d --- /dev/null +++ b/nikola/plugins/task/authors.py @@ -0,0 +1,316 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2015 Juanjo Conti. + +# 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 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 nikola.plugin_categories import Task +from nikola import utils + + +class RenderAuthors(Task): + """Render the author pages and feeds.""" + + name = "render_authors" + posts_per_author = None + + def set_site(self, site): + """Set Nikola site.""" + 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) + return super(RenderAuthors, self).set_site(site) + + 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'], + } + + yield self.group_task() + self.site.scan_posts() + + generate_author_pages = self.site.config["ENABLE_AUTHOR_PAGES"] and len(self._posts_per_author()) > 1 + self.site.GLOBAL_CONTEXT["author_pages_generated"] = generate_author_pages + if 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 = ("""<link rel="alternate" type="application/rss+xml" """ + """title="RSS for author """ + """{0} ({1})" href="{2}">""".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" + + yield self.site.generic_index_renderer(lang, post_list, indexes_title, template_name, context_source, kw, str(self.name), page_link, page_path) + + 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 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 slugify_author_name(self, name): + """Slugify an author name.""" + if self.site.config['SLUG_AUTHOR_PATH']: + name = utils.slugify(name) + 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), + self.site.config['INDEX_FILE']] if _f] + else: + return [_f for _f in [ + self.site.config['TRANSLATIONS'][lang], + self.site.config['AUTHOR_PATH'], + self.slugify_author_name(name) + ".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) + ".atom"] if + _f] + + def author_rss_path(self, name, lang): + """Link to an author's RSS feed. + + Example: + + 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) + ".xml"] if + _f] + + def _add_extension(self, path, extension): + path[-1] += extension + return path + + 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 diff --git a/nikola/plugins/task/bundles.plugin b/nikola/plugins/task/bundles.plugin index ca997d0..b5bf6e4 100644 --- a/nikola/plugins/task/bundles.plugin +++ b/nikola/plugins/task/bundles.plugin @@ -5,7 +5,7 @@ module = bundles [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Theme bundles using WebAssets [Nikola] diff --git a/nikola/plugins/task/bundles.py b/nikola/plugins/task/bundles.py index b9c57b9..e709133 100644 --- a/nikola/plugins/task/bundles.py +++ b/nikola/plugins/task/bundles.py @@ -40,7 +40,6 @@ from nikola import utils class BuildBundles(LateTask): - """Bundle assets using WebAssets.""" name = "create_bundles" @@ -52,6 +51,7 @@ class BuildBundles(LateTask): utils.req_missing(['webassets'], 'USE_BUNDLES', optional=True) self.logger.warn('Setting USE_BUNDLES to False.') site.config['USE_BUNDLES'] = False + site._GLOBAL_CONTEXT['use_bundles'] = False super(BuildBundles, self).set_site(site) def gen_tasks(self): @@ -100,7 +100,11 @@ class BuildBundles(LateTask): files.append(os.path.join(dname, fname)) file_dep = [os.path.join(kw['output_folder'], fname) for fname in files if - utils.get_asset_path(fname, self.site.THEMES, self.site.config['FILES_FOLDERS']) or fname == os.path.join('assets', 'css', 'code.css')] + utils.get_asset_path( + fname, + self.site.THEMES, + self.site.config['FILES_FOLDERS'], + output_dir=kw['output_folder']) or fname == os.path.join('assets', 'css', 'code.css')] # code.css will be generated by us if it does not exist in # FILES_FOLDERS or theme assets. It is guaranteed that the # generation will happen before this task. diff --git a/nikola/plugins/task/copy_assets.plugin b/nikola/plugins/task/copy_assets.plugin index c182150..ddd38df 100644 --- a/nikola/plugins/task/copy_assets.plugin +++ b/nikola/plugins/task/copy_assets.plugin @@ -5,7 +5,7 @@ module = copy_assets [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Copy theme assets into output. [Nikola] diff --git a/nikola/plugins/task/copy_assets.py b/nikola/plugins/task/copy_assets.py index 58521d4..2cab71a 100644 --- a/nikola/plugins/task/copy_assets.py +++ b/nikola/plugins/task/copy_assets.py @@ -36,7 +36,6 @@ from nikola import utils class CopyAssets(Task): - """Copy theme assets into output.""" name = "copy_assets" @@ -61,10 +60,7 @@ class CopyAssets(Task): code_css_path = os.path.join(kw['output_folder'], 'assets', 'css', 'code.css') code_css_input = utils.get_asset_path('assets/css/code.css', themes=kw['themes'], - files_folders=kw['files_folders']) - - kw["code.css_input"] = code_css_input - + files_folders=kw['files_folders'], output_dir=None) yield self.group_task() for theme_name in kw['themes']: @@ -77,7 +73,9 @@ class CopyAssets(Task): task['uptodate'] = [utils.config_changed(kw, 'nikola.plugins.task.copy_assets')] task['basename'] = self.name if code_css_input: - task['file_dep'] = [code_css_input] + if 'file_dep' not in task: + task['file_dep'] = [] + task['file_dep'].append(code_css_input) yield utils.apply_filters(task, kw['filters']) # Check whether or not there is a code.css file around. diff --git a/nikola/plugins/task/copy_files.plugin b/nikola/plugins/task/copy_files.plugin index ce8f5d0..e4bb1cf 100644 --- a/nikola/plugins/task/copy_files.plugin +++ b/nikola/plugins/task/copy_files.plugin @@ -5,7 +5,7 @@ module = copy_files [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Copy static files into the output. [Nikola] diff --git a/nikola/plugins/task/copy_files.py b/nikola/plugins/task/copy_files.py index 1232248..0488011 100644 --- a/nikola/plugins/task/copy_files.py +++ b/nikola/plugins/task/copy_files.py @@ -33,7 +33,6 @@ from nikola import utils class CopyFiles(Task): - """Copy static files into the output folder.""" name = "copy_files" diff --git a/nikola/plugins/task/galleries.plugin b/nikola/plugins/task/galleries.plugin index 9d3fa28..2064e68 100644 --- a/nikola/plugins/task/galleries.plugin +++ b/nikola/plugins/task/galleries.plugin @@ -5,7 +5,7 @@ module = galleries [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create image galleries automatically. [Nikola] diff --git a/nikola/plugins/task/galleries.py b/nikola/plugins/task/galleries.py index c0df4a4..d3f1db7 100644 --- a/nikola/plugins/task/galleries.py +++ b/nikola/plugins/task/galleries.py @@ -57,7 +57,6 @@ _image_size_cache = {} class Galleries(Task, ImageProcessor): - """Render image galleries.""" name = 'render_galleries' @@ -122,20 +121,45 @@ class Galleries(Task, ImageProcessor): sys.exit(1) def gallery_path(self, name, lang): - """Return a gallery path.""" + """Link to an image gallery's path. + + It will try to find a gallery with that name if it's not ambiguous + or with that path. For example: + + link://gallery/london => /galleries/trips/london/index.html + + link://gallery/trips/london => /galleries/trips/london/index.html + """ gallery_path = self._find_gallery_path(name) return [_f for _f in [self.site.config['TRANSLATIONS'][lang]] + gallery_path.split(os.sep) + [self.site.config['INDEX_FILE']] if _f] def gallery_global_path(self, name, lang): - """Return the global gallery path, which contains images.""" + """Link to the global gallery path, which contains all the images in galleries. + + There is only one copy of an image on multilingual blogs, in the site root. + + link://gallery_global/london => /galleries/trips/london/index.html + + link://gallery_global/trips/london => /galleries/trips/london/index.html + + (a ``gallery`` link could lead to eg. /en/galleries/trips/london/index.html) + """ gallery_path = self._find_gallery_path(name) return [_f for _f in gallery_path.split(os.sep) + [self.site.config['INDEX_FILE']] if _f] def gallery_rss_path(self, name, lang): - """Return path to the RSS file for a gallery.""" + """Link to an image gallery's RSS feed. + + It will try to find a gallery with that name if it's not ambiguous + or with that path. For example: + + link://gallery_rss/london => /galleries/trips/london/rss.xml + + link://gallery_rss/trips/london => /galleries/trips/london/rss.xml + """ gallery_path = self._find_gallery_path(name) return [_f for _f in [self.site.config['TRANSLATIONS'][lang]] + gallery_path.split(os.sep) + @@ -538,9 +562,12 @@ class Galleries(Task, ImageProcessor): for img, thumb, title in zip(img_list, thumbs, img_titles): w, h = _image_size_cache.get(thumb, (None, None)) if w is None: - im = Image.open(thumb) - w, h = im.size - _image_size_cache[thumb] = w, h + if os.path.splitext(thumb)[1] in ['.svg', '.svgz']: + w, h = 200, 200 + else: + im = Image.open(thumb) + w, h = im.size + _image_size_cache[thumb] = w, h # Thumbs are files in output, we need URLs photo_array.append({ 'url': url_from_path(img), @@ -587,7 +614,7 @@ class Galleries(Task, ImageProcessor): description='', lastBuildDate=datetime.datetime.utcnow(), items=items, - generator='http://getnikola.com/', + generator='https://getnikola.com/', language=lang ) diff --git a/nikola/plugins/task/gzip.plugin b/nikola/plugins/task/gzip.plugin index 7834d22..d3a34ee 100644 --- a/nikola/plugins/task/gzip.plugin +++ b/nikola/plugins/task/gzip.plugin @@ -5,7 +5,7 @@ module = gzip [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create gzipped copies of files [Nikola] diff --git a/nikola/plugins/task/gzip.py b/nikola/plugins/task/gzip.py index cf16f63..aaa213d 100644 --- a/nikola/plugins/task/gzip.py +++ b/nikola/plugins/task/gzip.py @@ -35,7 +35,6 @@ from nikola.plugin_categories import TaskMultiplier class GzipFiles(TaskMultiplier): - """If appropiate, create tasks to create gzipped versions of files.""" name = "gzip" diff --git a/nikola/plugins/task/indexes.plugin b/nikola/plugins/task/indexes.plugin index d9b0e5f..553b5ad 100644 --- a/nikola/plugins/task/indexes.plugin +++ b/nikola/plugins/task/indexes.plugin @@ -5,7 +5,7 @@ module = indexes [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Generates the blog's index pages. [Nikola] diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index c02818e..2ab97fa 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -29,23 +29,45 @@ 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 class Indexes(Task): - """Render the blog indexes.""" name = "render_indexes" 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) 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() @@ -56,16 +78,16 @@ class Indexes(Task): "messages": self.site.MESSAGES, "output_folder": self.site.config['OUTPUT_FOLDER'], "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"], } template_name = "index.tmpl" - posts = self.site.posts - self.number_of_pages = dict() for lang in kw["translations"]: def page_link(i, displayed_i, num_pages, force_addition, extension=None): feed = "_atom" if extension == ".atom" else "" @@ -77,19 +99,75 @@ class Indexes(Task): return utils.adjust_name_for_index_path(self.site.path("index" + feed, None, lang), i, displayed_i, lang, self.site, force_addition, extension) - if kw["show_untranslated_posts"]: - filtered_posts = posts - else: - filtered_posts = [x for x in posts if x.is_translation_available(lang)] + 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] = (len(filtered_posts) + kw['index_display_post_count'] - 1) // kw['index_display_post_count'] + self.number_of_pages[lang] = self._compute_number_of_pages(filtered_posts, kw['index_display_post_count']) context = {} - context["pagekind"] = ["index"] + 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) + if self.site.config['POSTS_SECTIONS']: + + kw["posts_section_are_indexes"] = self.site.config['POSTS_SECTION_ARE_INDEXES'] + 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 kw["posts_section_are_indexes"]: + context["pagekind"].append("index") + kw["posts_section_title"] = self.site.config['POSTS_SECTION_TITLE'](lang) + + section_title = None + if type(kw["posts_section_title"]) is dict: + if section_slug in kw["posts_section_title"]: + section_title = kw["posts_section_title"][section_slug] + elif type(kw["posts_section_title"]) is str: + section_title = kw["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 + if not self.site.config["STORY_INDEX"]: return kw = { @@ -134,7 +212,8 @@ class Indexes(Task): should_render = False else: context["items"].append((post.title(lang), - post.permalink(lang))) + post.permalink(lang), + None)) if should_render: task = self.site.generic_post_list_renderer(lang, post_list, @@ -147,22 +226,75 @@ class Indexes(Task): yield task def index_path(self, name, lang, is_feed=False): - """Return path to an index.""" + """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, self.number_of_pages[lang], self.site), + utils.get_displayed_page_number(name, number_of_pages, self.site), + lang, + self.site, + extension=extension) + + def index_section_path(self, name, lang, is_feed=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 + 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): - """Return path to an Atom index.""" + """Link to a numbered Atom index. + + Example: + + 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) diff --git a/nikola/plugins/task/listings.plugin b/nikola/plugins/task/listings.plugin index 435234b..8fc2e2d 100644 --- a/nikola/plugins/task/listings.plugin +++ b/nikola/plugins/task/listings.plugin @@ -5,7 +5,7 @@ module = listings [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Render code listings into output [Nikola] diff --git a/nikola/plugins/task/listings.py b/nikola/plugins/task/listings.py index 5f79724..891f361 100644 --- a/nikola/plugins/task/listings.py +++ b/nikola/plugins/task/listings.py @@ -28,6 +28,7 @@ from __future__ import unicode_literals, print_function +from collections import defaultdict import sys import os import lxml.html @@ -41,16 +42,13 @@ from nikola import utils class Listings(Task): - """Render code listings.""" name = "render_listings" def register_output_name(self, input_folder, rel_name, rel_output_name): """Register proper and improper file mappings.""" - if rel_name not in self.improper_input_file_mapping: - self.improper_input_file_mapping[rel_name] = [] - self.improper_input_file_mapping[rel_name].append(rel_output_name) + self.improper_input_file_mapping[rel_name].add(rel_output_name) self.proper_input_file_mapping[os.path.join(input_folder, rel_name)] = rel_output_name self.proper_input_file_mapping[rel_output_name] = rel_output_name @@ -85,7 +83,7 @@ class Listings(Task): # a list is needed. This is needed for compatibility to previous Nikola # versions, where there was no need to specify the input directory name # when asking for a link via site.link('listing', ...). - self.improper_input_file_mapping = {} + self.improper_input_file_mapping = defaultdict(set) # proper_input_file_mapping maps relative input file (relative to CWD) # to a generated output file. Since we don't allow an input directory @@ -255,7 +253,16 @@ class Listings(Task): }, self.kw["filters"]) def listing_path(self, namep, lang): - """Return path to a listing.""" + """A link to a listing. + + It will try to use the file name if it's not ambiguous, or the file path. + + Example: + + link://listing/hello.py => /listings/tutorial/hello.py.html + + link://listing/tutorial/hello.py => /listings/tutorial/hello.py.html + """ namep = namep.replace('/', os.sep) nameh = namep + '.html' for name in (namep, nameh): @@ -271,7 +278,7 @@ class Listings(Task): sys.exit(1) if len(self.site.config['LISTINGS_FOLDERS']) > 1: utils.LOGGER.notice("Using listings names in site.link() without input directory prefix while configuration's LISTINGS_FOLDERS has more than one entry.") - name = self.improper_input_file_mapping[name][0] + name = list(self.improper_input_file_mapping[name])[0] break else: utils.LOGGER.error("Unknown listing name {0}!".format(namep)) diff --git a/nikola/plugins/task/pages.plugin b/nikola/plugins/task/pages.plugin index 023d41b..1bdc7f4 100644 --- a/nikola/plugins/task/pages.plugin +++ b/nikola/plugins/task/pages.plugin @@ -5,7 +5,7 @@ module = pages [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create pages in the output. [Nikola] diff --git a/nikola/plugins/task/pages.py b/nikola/plugins/task/pages.py index e6a8a82..8d41035 100644 --- a/nikola/plugins/task/pages.py +++ b/nikola/plugins/task/pages.py @@ -32,7 +32,6 @@ from nikola.utils import config_changed class RenderPages(Task): - """Render pages into output.""" name = "render_pages" diff --git a/nikola/plugins/task/posts.plugin b/nikola/plugins/task/posts.plugin index 79b7c51..c9578bc 100644 --- a/nikola/plugins/task/posts.plugin +++ b/nikola/plugins/task/posts.plugin @@ -5,7 +5,7 @@ module = posts [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create HTML fragments out of posts. [Nikola] diff --git a/nikola/plugins/task/posts.py b/nikola/plugins/task/posts.py index a3a8375..8735beb 100644 --- a/nikola/plugins/task/posts.py +++ b/nikola/plugins/task/posts.py @@ -44,7 +44,6 @@ def update_deps(post, lang, task): class RenderPosts(Task): - """Build HTML fragments from metadata and text.""" name = "render_posts" @@ -77,6 +76,8 @@ class RenderPosts(Task): deps_dict = copy(kw) deps_dict.pop('timeline') for post in kw['timeline']: + if not post.is_translation_available(lang) and not self.site.config['SHOW_UNTRANSLATED_POSTS']: + continue # Extra config dependencies picked from config for p in post.fragment_deps(lang): if p.startswith('####MAGIC####CONFIG:'): @@ -114,7 +115,7 @@ class RenderPosts(Task): pass else: flist.append(f) - yield utils.apply_filters(task, {os.path.splitext(dest): flist}) + yield utils.apply_filters(task, {os.path.splitext(dest)[-1]: flist}) def dependence_on_timeline(self, post, lang): """Check if a post depends on the timeline.""" diff --git a/nikola/plugins/task/py3_switch.plugin b/nikola/plugins/task/py3_switch.plugin new file mode 100644 index 0000000..b0014e1 --- /dev/null +++ b/nikola/plugins/task/py3_switch.plugin @@ -0,0 +1,13 @@ +[Core] +name = py3_switch +module = py3_switch + +[Documentation] +author = Roberto Alsina +version = 1.0 +website = https://getnikola.com/ +description = Beg the user to switch to Python 3 + +[Nikola] +plugincategory = Task + diff --git a/nikola/plugins/task/py3_switch.py b/nikola/plugins/task/py3_switch.py new file mode 100644 index 0000000..930c593 --- /dev/null +++ b/nikola/plugins/task/py3_switch.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2012-2015 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. + +"""Beg the user to switch to python 3.""" + +import datetime +import os +import random +import sys + +import doit.tools + +from nikola.utils import get_logger, STDERR_HANDLER +from nikola.plugin_categories import LateTask + +PY2_AND_NO_PY3_WARNING = """Nikola is going to deprecate Python 2 support in 2016. Your current +version will continue to work, but please consider upgrading to Python 3. + +Please check http://bit.ly/1FKEsiX for details. +""" +PY2_WARNING = """Nikola is going to deprecate Python 2 support in 2016. You already have Python 3 +available in your system. Why not switch? + +Please check http://bit.ly/1FKEsiX for details. +""" +PY2_BARBS = [ + "Python 2 has been deprecated for years. Stop clinging to your long gone youth and switch to Python3.", + "Python 2 is the safety blanket of languages. Be a big kid and switch to Python 3", + "Python 2 is old and busted. Python 3 is the new hotness.", + "Nice unicode you have there, would be a shame something happened to it.. switch to python 3!.", + "Don’t get in the way of progress! Upgrade to Python 3 and save a developer’s mind today!", + "Winners don't use Python 2 -- Signed: The FBI", + "Python 2? What year is it?", + "I just wanna tell you how I'm feeling\n" + "Gotta make you understand\n" + "Never gonna give you up [But Python 2 has to go]", + "The year 2009 called, and they want their Python 2.7 back.", +] + + +LOGGER = get_logger('Nikola', STDERR_HANDLER) + + +def has_python_3(): + """Check if python 3 is available.""" + if 'win' in sys.platform: + py_bin = 'py.exe' + else: + py_bin = 'python3' + for path in os.environ["PATH"].split(os.pathsep): + if os.access(os.path.join(path, py_bin), os.X_OK): + return True + return False + + +class Py3Switch(LateTask): + """Beg the user to switch to python 3.""" + + name = "_switch to py3" + + def gen_tasks(self): + """Beg the user to switch to python 3.""" + def give_warning(): + if sys.version_info[0] == 3: + return + if has_python_3(): + LOGGER.warn(random.choice(PY2_BARBS)) + LOGGER.warn(PY2_WARNING) + else: + LOGGER.warn(PY2_AND_NO_PY3_WARNING) + + task = { + 'basename': self.name, + 'name': 'please!', + 'actions': [give_warning], + 'clean': True, + 'uptodate': [doit.tools.timeout(datetime.timedelta(days=3))] + } + + return task diff --git a/nikola/plugins/task/redirect.plugin b/nikola/plugins/task/redirect.plugin index c3137b9..c5a3042 100644 --- a/nikola/plugins/task/redirect.plugin +++ b/nikola/plugins/task/redirect.plugin @@ -5,7 +5,7 @@ module = redirect [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create redirect pages. [Nikola] diff --git a/nikola/plugins/task/redirect.py b/nikola/plugins/task/redirect.py index 8530f5e..2d4eba4 100644 --- a/nikola/plugins/task/redirect.py +++ b/nikola/plugins/task/redirect.py @@ -35,7 +35,6 @@ from nikola import utils class Redirect(Task): - """Generate redirections.""" name = "redirect" diff --git a/nikola/plugins/task/robots.plugin b/nikola/plugins/task/robots.plugin index 72ce31f..7ae56c6 100644 --- a/nikola/plugins/task/robots.plugin +++ b/nikola/plugins/task/robots.plugin @@ -5,7 +5,7 @@ module = robots [Documentation] author = Daniel Aleksandersen version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Generate /robots.txt exclusion file and promote sitemap. [Nikola] diff --git a/nikola/plugins/task/robots.py b/nikola/plugins/task/robots.py index 65254b6..7c7f5df 100644 --- a/nikola/plugins/task/robots.py +++ b/nikola/plugins/task/robots.py @@ -39,7 +39,6 @@ from nikola import utils class RobotsFile(LateTask): - """Generate a robots.txt file.""" name = "robots_file" @@ -64,14 +63,15 @@ class RobotsFile(LateTask): with io.open(robots_path, 'w+', encoding='utf8') as outf: outf.write("Sitemap: {0}\n\n".format(sitemapindex_url)) + outf.write("User-Agent: *\n") if kw["robots_exclusions"]: - outf.write("User-Agent: *\n") for loc in kw["robots_exclusions"]: outf.write("Disallow: {0}\n".format(loc)) + outf.write("Host: {0}\n".format(urlparse(kw["base_url"]).netloc)) yield self.group_task() - if not utils.get_asset_path("robots.txt", [], files_folders=kw["files_folders"]): + if not utils.get_asset_path("robots.txt", [], files_folders=kw["files_folders"], output_dir=False): yield utils.apply_filters({ "basename": self.name, "name": robots_path, @@ -82,6 +82,6 @@ class RobotsFile(LateTask): "task_dep": ["sitemap"] }, kw["filters"]) elif kw["robots_exclusions"]: - utils.LOGGER.warn('Did not generate robots.txt as one already exists in FILES_FOLDERS. ROBOTS_EXCLUSIONS will not have any affect on the copied fie.') + utils.LOGGER.warn('Did not generate robots.txt as one already exists in FILES_FOLDERS. ROBOTS_EXCLUSIONS will not have any affect on the copied file.') else: utils.LOGGER.debug('Did not generate robots.txt as one already exists in FILES_FOLDERS.') diff --git a/nikola/plugins/task/rss.plugin b/nikola/plugins/task/rss.plugin index cf9b7a7..4dd8aba 100644 --- a/nikola/plugins/task/rss.plugin +++ b/nikola/plugins/task/rss.plugin @@ -5,7 +5,7 @@ module = rss [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Generate RSS feeds. [Nikola] diff --git a/nikola/plugins/task/rss.py b/nikola/plugins/task/rss.py index 9020a06..be57f5c 100644 --- a/nikola/plugins/task/rss.py +++ b/nikola/plugins/task/rss.py @@ -38,7 +38,6 @@ from nikola.plugin_categories import Task class GenerateRSS(Task): - """Generate RSS feeds.""" name = "generate_rss" @@ -58,13 +57,14 @@ class GenerateRSS(Task): "base_url": self.site.config["BASE_URL"], "blog_description": self.site.config["BLOG_DESCRIPTION"], "output_folder": self.site.config["OUTPUT_FOLDER"], - "rss_teasers": self.site.config["RSS_TEASERS"], - "rss_plain": self.site.config["RSS_PLAIN"], + "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, - "rss_read_more_link": self.site.config["RSS_READ_MORE_LINK"], - "rss_links_append_query": self.site.config["RSS_LINKS_APPEND_QUERY"], + "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. @@ -96,8 +96,8 @@ class GenerateRSS(Task): 'actions': [(utils.generic_rss_renderer, (lang, kw["blog_title"](lang), kw["site_url"], kw["blog_description"](lang), posts, output_name, - kw["rss_teasers"], kw["rss_plain"], kw['feed_length'], feed_url, - None, kw["rss_links_append_query"]))], + kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], feed_url, + None, kw["feed_links_append_query"]))], 'task_dep': ['render_posts'], 'clean': True, @@ -106,6 +106,11 @@ class GenerateRSS(Task): yield utils.apply_filters(task, kw['filters']) def rss_path(self, name, lang): - """Return RSS path.""" + """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] diff --git a/nikola/plugins/task/scale_images.plugin b/nikola/plugins/task/scale_images.plugin index d906b8c..3edd0c6 100644 --- a/nikola/plugins/task/scale_images.plugin +++ b/nikola/plugins/task/scale_images.plugin @@ -5,7 +5,7 @@ module = scale_images [Documentation] author = Pelle Nilsson version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Create down-scaled images and thumbnails. [Nikola] diff --git a/nikola/plugins/task/scale_images.py b/nikola/plugins/task/scale_images.py index 22ed2ab..e55dc6c 100644 --- a/nikola/plugins/task/scale_images.py +++ b/nikola/plugins/task/scale_images.py @@ -34,7 +34,6 @@ from nikola import utils class ScaleImage(Task, ImageProcessor): - """Resize images and create thumbnails for them.""" name = "scale_images" diff --git a/nikola/plugins/task/sitemap.plugin b/nikola/plugins/task/sitemap.plugin index e3c991f..83e72c4 100644 --- a/nikola/plugins/task/sitemap.plugin +++ b/nikola/plugins/task/sitemap.plugin @@ -5,7 +5,7 @@ module = sitemap [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Generate google sitemap. [Nikola] diff --git a/nikola/plugins/task/sitemap/__init__.py b/nikola/plugins/task/sitemap/__init__.py index fd781d6..90acdd3 100644 --- a/nikola/plugins/task/sitemap/__init__.py +++ b/nikola/plugins/task/sitemap/__init__.py @@ -31,6 +31,7 @@ import io import datetime import dateutil.tz import os +import sys try: from urlparse import urljoin, urlparse import robotparser as robotparser @@ -39,7 +40,7 @@ except ImportError: import urllib.robotparser as robotparser # NOQA from nikola.plugin_categories import LateTask -from nikola.utils import config_changed, apply_filters +from nikola.utils import apply_filters, config_changed, encodelink urlset_header = """<?xml version="1.0" encoding="UTF-8"?> @@ -106,7 +107,6 @@ def get_base_path(base): class Sitemap(LateTask): - """Generate a sitemap.""" name = "sitemap" @@ -146,7 +146,10 @@ class Sitemap(LateTask): continue # Totally empty, not on sitemap path = os.path.relpath(root, output) # ignore the current directory. - path = (path.replace(os.sep, '/') + '/').replace('./', '') + if path == '.': + path = '' + else: + path = path.replace(os.sep, '/') + '/' lastmod = self.get_lastmod(root) loc = urljoin(base_url, base_path + path) if kw['index_file'] in files and kw['strip_indexes']: # ignore folders when not stripping urls @@ -157,10 +160,10 @@ class Sitemap(LateTask): if post: for lang in kw['translations']: alt_url = post.permalink(lang=lang, absolute=True) - if loc == alt_url: + if encodelink(loc) == alt_url: continue alternates.append(alternates_format.format(lang, alt_url)) - urlset[loc] = loc_format.format(loc, lastmod, ''.join(alternates)) + urlset[loc] = loc_format.format(encodelink(loc), lastmod, ''.join(alternates)) for fname in files: if kw['strip_indexes'] and fname == kw['index_file']: continue # We already mapped the folder @@ -200,7 +203,7 @@ class Sitemap(LateTask): path = path.replace(os.sep, '/') lastmod = self.get_lastmod(real_path) loc = urljoin(base_url, base_path + path) - sitemapindex[loc] = sitemap_format.format(loc, lastmod) + sitemapindex[loc] = sitemap_format.format(encodelink(loc), lastmod) continue else: continue # ignores all XML files except those presumed to be RSS @@ -214,18 +217,22 @@ class Sitemap(LateTask): if post: for lang in kw['translations']: alt_url = post.permalink(lang=lang, absolute=True) - if loc == alt_url: + if encodelink(loc) == alt_url: continue alternates.append(alternates_format.format(lang, alt_url)) - urlset[loc] = loc_format.format(loc, lastmod, '\n'.join(alternates)) + urlset[loc] = loc_format.format(encodelink(loc), lastmod, '\n'.join(alternates)) def robot_fetch(path): """Check if robots can fetch a file.""" for rule in kw["robots_exclusions"]: robot = robotparser.RobotFileParser() robot.parse(["User-Agent: *", "Disallow: {0}".format(rule)]) - if not robot.can_fetch("*", '/' + path): - return False # not robot food + if sys.version_info[0] == 3: + if not robot.can_fetch("*", '/' + path): + return False # not robot food + else: + if not robot.can_fetch("*", ('/' + path).encode('utf-8')): + return False # not robot food return True def write_sitemap(): diff --git a/nikola/plugins/task/sources.plugin b/nikola/plugins/task/sources.plugin index d232c2b..66856f1 100644 --- a/nikola/plugins/task/sources.plugin +++ b/nikola/plugins/task/sources.plugin @@ -5,7 +5,7 @@ module = sources [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Copy page sources into the output. [Nikola] diff --git a/nikola/plugins/task/sources.py b/nikola/plugins/task/sources.py index 87b4ae7..f782ad4 100644 --- a/nikola/plugins/task/sources.py +++ b/nikola/plugins/task/sources.py @@ -33,7 +33,6 @@ from nikola import utils class Sources(Task): - """Copy page sources into the output.""" name = "render_sources" diff --git a/nikola/plugins/task/tags.plugin b/nikola/plugins/task/tags.plugin index 283a16a..c3a5be3 100644 --- a/nikola/plugins/task/tags.plugin +++ b/nikola/plugins/task/tags.plugin @@ -5,7 +5,7 @@ module = tags [Documentation] author = Roberto Alsina version = 1.0 -website = http://getnikola.com +website = https://getnikola.com/ description = Render the tag pages and feeds. [Nikola] diff --git a/nikola/plugins/task/tags.py b/nikola/plugins/task/tags.py index 3186636..6d9d495 100644 --- a/nikola/plugins/task/tags.py +++ b/nikola/plugins/task/tags.py @@ -41,7 +41,6 @@ from nikola import utils class RenderTags(Task): - """Render the tag/category pages and feeds.""" name = "render_tags" @@ -74,9 +73,9 @@ class RenderTags(Task): 'category_prefix': self.site.config['CATEGORY_PREFIX'], "category_pages_are_indexes": self.site.config['CATEGORY_PAGES_ARE_INDEXES'], "generate_rss": self.site.config['GENERATE_RSS'], - "rss_teasers": self.site.config["RSS_TEASERS"], - "rss_plain": self.site.config["RSS_PLAIN"], - "rss_link_append_query": self.site.config["RSS_LINKS_APPEND_QUERY"], + "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'], "taglist_minimum_post_count": self.site.config['TAGLIST_MINIMUM_POSTS'], @@ -84,6 +83,10 @@ class RenderTags(Task): "pretty_urls": self.site.config['PRETTY_URLS'], "strip_indexes": self.site.config['STRIP_INDEXES'], "index_file": self.site.config['INDEX_FILE'], + "category_pages_descriptions": self.site.config['CATEGORY_PAGES_DESCRIPTIONS'], + "category_pages_titles": self.site.config['CATEGORY_PAGES_TITLES'], + "tag_pages_descriptions": self.site.config['TAG_PAGES_DESCRIPTIONS'], + "tag_pages_titles": self.site.config['TAG_PAGES_TITLES'], } self.site.scan_posts() @@ -168,7 +171,7 @@ class RenderTags(Task): """Write tag data into JSON file, for use in tag clouds.""" utils.makedirs(os.path.dirname(output_name)) with open(output_name, 'w+') as fd: - json.dump(data, fd) + json.dump(data, fd, sort_keys=True) if self.site.config['WRITE_TAG_CLOUD']: task = { @@ -199,7 +202,6 @@ class RenderTags(Task): kw['tags'] = tags output_name = os.path.join( kw['output_folder'], self.site.path('tag_index' if has_tags else 'category_index', None, lang)) - output_name = output_name context = {} if has_categories and has_tags: context["title"] = kw["messages"][lang]["Tags and Categories"] @@ -251,6 +253,10 @@ class RenderTags(Task): else: return tag + def _get_indexes_title(self, tag, is_category, lang, messages): + titles = self.site.config['CATEGORY_PAGES_TITLES'] if is_category else self.site.config['TAG_PAGES_TITLES'] + return titles[lang][tag] if lang in titles and tag in titles[lang] else messages[lang]["Posts about %s"] % tag + def _get_description(self, tag, is_category, lang): descriptions = self.site.config['CATEGORY_PAGES_DESCRIPTIONS'] if is_category else self.site.config['TAG_PAGES_DESCRIPTIONS'] return descriptions[lang][tag] if lang in descriptions and tag in descriptions[lang] else None @@ -276,7 +282,7 @@ class RenderTags(Task): if kw["generate_rss"]: # On a tag page, the feeds include the tag's feeds rss_link = ("""<link rel="alternate" type="application/rss+xml" """ - """type="application/rss+xml" title="RSS for tag """ + """title="RSS for tag """ """{0} ({1})" href="{2}">""".format( title, lang, self.site.link(kind + "_rss", tag, lang))) context_source['rss_link'] = rss_link @@ -284,7 +290,7 @@ class RenderTags(Task): context_source["category"] = tag context_source["category_path"] = self.site.parse_category_name(tag) context_source["tag"] = title - indexes_title = kw["messages"][lang]["Posts about %s"] % title + indexes_title = self._get_indexes_title(title, is_category, lang, kw["messages"]) context_source["description"] = self._get_description(tag, is_category, lang) if is_category: context_source["subcategories"] = self._get_subcategories(tag) @@ -306,7 +312,7 @@ class RenderTags(Task): context["category"] = tag context["category_path"] = self.site.parse_category_name(tag) context["tag"] = title - context["title"] = kw["messages"][lang]["Posts about %s"] % title + context["title"] = self._get_indexes_title(title, is_category, lang, kw["messages"]) context["posts"] = post_list context["permalink"] = self.site.link(kind, tag, lang) context["kind"] = kind @@ -326,6 +332,29 @@ class RenderTags(Task): task['basename'] = str(self.name) yield task + if self.site.config['GENERATE_ATOM']: + yield self.atom_feed_list(kind, tag, lang, post_list, context, kw) + + def atom_feed_list(self, kind, tag, lang, post_list, context, kw): + """Generate atom feeds for tag lists.""" + if kind == 'tag': + context['feedlink'] = self.site.abs_link(self.site.path('tag_atom', tag, lang)) + feed_path = os.path.join(kw['output_folder'], self.site.path('tag_atom', tag, lang)) + elif kind == 'category': + context['feedlink'] = self.site.abs_link(self.site.path('category_atom', tag, lang)) + feed_path = os.path.join(kw['output_folder'], self.site.path('category_atom', tag, lang)) + + task = { + 'basename': str(self.name), + 'name': feed_path, + 'targets': [feed_path], + 'actions': [(self.site.atom_feed_renderer, (lang, post_list, feed_path, kw['filters'], context))], + 'clean': True, + 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.tags:atom')], + 'task_dep': ['render_posts'], + } + return task + def tag_rss(self, tag, lang, posts, kw, is_category): """Create a RSS feed for a single tag in a given language.""" kind = "category" if is_category else "tag" @@ -349,8 +378,8 @@ class RenderTags(Task): 'actions': [(utils.generic_rss_renderer, (lang, "{0} ({1})".format(kw["blog_title"](lang), self._get_title(tag, is_category)), kw["site_url"], None, post_list, - output_name, kw["rss_teasers"], kw["rss_plain"], kw['feed_length'], - feed_url, None, kw["rss_link_append_query"]))], + 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.tags:rss')] + deps_uptodate, 'task_dep': ['render_posts'], @@ -364,41 +393,71 @@ class RenderTags(Task): return name def tag_index_path(self, name, lang): - """Return path to the tag index.""" - return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'], - self.site.config['INDEX_FILE']] if _f] + """A link to the tag index. + + Example: + + link://tag_index => /tags/index.html + """ + if self.site.config['TAGS_INDEX_PATH'][lang]: + paths = [_f for _f in [self.site.config['TRANSLATIONS'][lang], + self.site.config['TAGS_INDEX_PATH'][lang]] if _f] + else: + paths = [_f for _f in [self.site.config['TRANSLATIONS'][lang], + self.site.config['TAG_PATH'][lang], + self.site.config['INDEX_FILE']] if _f] + return paths def category_index_path(self, name, lang): - """Return path to the category index.""" + """A link to the category index. + + Example: + + link://category_index => /categories/index.html + """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['CATEGORY_PATH'], + self.site.config['CATEGORY_PATH'][lang], self.site.config['INDEX_FILE']] if _f] def tag_path(self, name, lang): - """Return path to a tag.""" + """A link to a tag's page. + + Example: + + link://tag/cats => /tags/cats.html + """ if self.site.config['PRETTY_URLS']: return [_f for _f in [ self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'], + self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name), self.site.config['INDEX_FILE']] if _f] else: return [_f for _f in [ self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'], + self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name) + ".html"] if _f] def tag_atom_path(self, name, lang): - """Return path to a tag Atom feed.""" + """A link to a tag's Atom feed. + + Example: + + link://tag_atom/cats => /tags/cats.atom + """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'], self.slugify_tag_name(name) + ".atom"] if + self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name) + ".atom"] if _f] def tag_rss_path(self, name, lang): - """Return path to a tag RSS feed.""" + """A link to a tag's RSS feed. + + Example: + + link://tag_rss/cats => /tags/cats.xml + """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'], self.slugify_tag_name(name) + ".xml"] if + self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name) + ".xml"] if _f] def slugify_category_name(self, name): @@ -417,24 +476,39 @@ class RenderTags(Task): return path def category_path(self, name, lang): - """Return path to a category.""" + """A link to a category. + + Example: + + link://category/dogs => /categories/dogs.html + """ if self.site.config['PRETTY_URLS']: return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['CATEGORY_PATH']] if + self.site.config['CATEGORY_PATH'][lang]] if _f] + self.slugify_category_name(name) + [self.site.config['INDEX_FILE']] else: return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['CATEGORY_PATH']] if + self.site.config['CATEGORY_PATH'][lang]] if _f] + self._add_extension(self.slugify_category_name(name), ".html") def category_atom_path(self, name, lang): - """Return path to a category Atom feed.""" + """A link to a category's Atom feed. + + Example: + + link://category_atom/dogs => /categories/dogs.atom + """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['CATEGORY_PATH']] if + self.site.config['CATEGORY_PATH'][lang]] if _f] + self._add_extension(self.slugify_category_name(name), ".atom") def category_rss_path(self, name, lang): - """Return path to a category RSS feed.""" + """A link to a category's RSS feed. + + Example: + + link://category_rss/dogs => /categories/dogs.xml + """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['CATEGORY_PATH']] if + self.site.config['CATEGORY_PATH'][lang]] if _f] + self._add_extension(self.slugify_category_name(name), ".xml") |
