diff options
Diffstat (limited to 'nikola')
206 files changed, 3952 insertions, 1545 deletions
diff --git a/nikola/__init__.py b/nikola/__init__.py index 03d83da..a7f6fc6 100644 --- a/nikola/__init__.py +++ b/nikola/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -29,7 +29,7 @@ from __future__ import absolute_import import os -__version__ = "7.7.3" +__version__ = '7.8.1' DEBUG = bool(os.getenv('NIKOLA_DEBUG')) from .nikola import Nikola # NOQA diff --git a/nikola/__main__.py b/nikola/__main__.py index a4bd989..f002768 100644 --- a/nikola/__main__.py +++ b/nikola/__main__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -68,7 +68,7 @@ _RETURN_DOITNIKOLA = False def main(args=None): """Run Nikola.""" colorful = False - if sys.stderr.isatty() and os.name != 'nt': + if sys.stderr.isatty() and os.name != 'nt' and os.getenv('NIKOLA_MONO') is None: colorful = True ColorfulStderrHandler._colorful = colorful @@ -332,6 +332,8 @@ class DoitNikola(DoitMain): if args[0] == 'help': self.nikola.init_plugins(commands_only=True) + elif args[0] == 'plugin': + self.nikola.init_plugins(load_all=True) else: self.nikola.init_plugins() diff --git a/nikola/conf.py.in b/nikola/conf.py.in index 7ef6927..5010278 100644 --- a/nikola/conf.py.in +++ b/nikola/conf.py.in @@ -95,12 +95,12 @@ THEME_COLOR = '#5670d4' # POSTS and PAGES contains (wildcard, destination, template) tuples. # -# The wildcard is used to generate a list of reSt source files -# (whatever/thing.txt). +# The wildcard is used to generate a list of source files +# (whatever/thing.rst, for example). # # That fragment could have an associated metadata file (whatever/thing.meta), # and optionally translated files (example for Spanish, with code "es"): -# whatever/thing.es.txt and whatever/thing.es.meta +# whatever/thing.es.rst and whatever/thing.es.meta # # This assumes you use the default TRANSLATIONS_PATTERN. # @@ -109,13 +109,15 @@ THEME_COLOR = '#5670d4' # # These files are combined with the template to produce rendered # pages, which will be placed at -# output / TRANSLATIONS[lang] / destination / pagename.html +# output/TRANSLATIONS[lang]/destination/pagename.html # # where "pagename" is the "slug" specified in the metadata file. +# The page might also be placed in /destination/pagename/index.html +# if PRETTY_URLS are enabled. # # The difference between POSTS and PAGES is that POSTS are added -# to feeds and are considered part of a blog, while PAGES are -# just independent HTML pages. +# to feeds, indexes, tag lists and archives and are considered part +# of a blog, while PAGES are just independent HTML pages. # POSTS = ${POSTS} @@ -168,14 +170,18 @@ TIMEZONE = ${TIMEZONE} # LOCALE_DEFAULT = locale to use for languages not mentioned in LOCALES; if # not set the default Nikola mapping is used. +# LOCALES = {} +# LOCALE_FALLBACK = None +# LOCALE_DEFAULT = None + # One or more folders containing files to be copied as-is into the output. # The format is a dictionary of {source: relative destination}. # Default is: # FILES_FOLDERS = {'files': ''} # Which means copy 'files' into 'output' -# One or more folders containing listings to be processed and stored into -# the output. The format is a dictionary of {source: relative destination}. +# One or more folders containing code listings to be processed and published on +# the site. The format is a dictionary of {source: relative destination}. # Default is: # LISTINGS_FOLDERS = {'listings': 'listings'} # Which means process listings from 'listings' into 'output/listings' @@ -271,12 +277,12 @@ POSTS_SECTIONS = True # output / TRANSLATION[lang] / TAG_PATH / index.html (list of tags) # output / TRANSLATION[lang] / TAG_PATH / tag.html (list of posts for a tag) # output / TRANSLATION[lang] / TAG_PATH / tag.xml (RSS feed for a tag) - # (translatable) +# (translatable) # TAG_PATH = "categories" # See TAG_PATH's "list of tags" for the default setting value. Can be overwritten # here any path relative to the output directory. - # (translatable) +# (translatable) # TAGS_INDEX_PATH = "tags.html" # If TAG_PAGES_ARE_INDEXES is set to True, each tag's page will contain @@ -391,6 +397,12 @@ HIDDEN_AUTHORS = ['Guest'] # output / TRANSLATION[lang] / INDEX_PATH / index-*.html # INDEX_PATH = "" +# Optional HTML that displayed on “main” blog index.html files. +# May be used for a greeting. (translatable) +FRONT_INDEX_HEADER = { + DEFAULT_LANG: '' +} + # Create per-month archives instead of per-year # CREATE_MONTHLY_ARCHIVE = False # Create one large archive instead of per-year @@ -422,9 +434,12 @@ HIDDEN_AUTHORS = ['Guest'] # If USE_BASE_TAG is True, then all HTML files will include # something like <base href=http://foo.var.com/baz/bat> to help # the browser resolve relative links. -# In some rare cases, this will be a problem, and you can -# disable it by setting USE_BASE_TAG to False. -# USE_BASE_TAG = True +# Most people don’t need this tag; major websites don’t use it. Use +# only if you know what you’re doing. If this is True, your website +# will not be fully usable by manually opening .html files in your web +# browser (`nikola serve` or `nikola auto` is mandatory). Also, if you +# have mirrors of your site, they will point to SITE_URL everywhere. +USE_BASE_TAG = False # Final location for the blog main RSS feed is: # output / TRANSLATION[lang] / RSS_PATH / rss.xml @@ -463,13 +478,19 @@ REDIRECTIONS = ${REDIRECTIONS} # ] # } -# For user.github.io OR organization.github.io pages, the DEPLOY branch -# MUST be 'master', and 'gh-pages' for other repositories. -# GITHUB_SOURCE_BRANCH = 'master' -# GITHUB_DEPLOY_BRANCH = 'gh-pages' +# github_deploy configuration +# For more details, read the manual: +# https://getnikola.com/handbook.html#deploying-to-github +# You will need to configure the deployment branch on GitHub. +GITHUB_SOURCE_BRANCH = 'src' +GITHUB_DEPLOY_BRANCH = 'master' # The name of the remote where you wish to push to, using github_deploy. -# GITHUB_REMOTE_NAME = 'origin' +GITHUB_REMOTE_NAME = 'origin' + +# Whether or not github_deploy should commit to the source branch automatically +# before deploying. +GITHUB_COMMIT_SOURCE = True # Where the output site should be located # If you don't use an absolute path, it will be considered as relative @@ -555,12 +576,60 @@ REDIRECTIONS = ${REDIRECTIONS} # # If set to False, it will sort by filename instead. Defaults to True # GALLERY_SORT_BY_DATE = True + +# If set to True, EXIF data will be copied when an image is thumbnailed or +# resized. (See also EXIF_WHITELIST) +# PRESERVE_EXIF_DATA = False + +# If you have enabled PRESERVE_EXIF_DATA, this option lets you choose EXIF +# fields you want to keep in images. (See also PRESERVE_EXIF_DATA) +# +# For a full list of field names, please see here: +# http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf # -# Folders containing images to be used in normal posts or pages. Images will be -# scaled down according to IMAGE_THUMBNAIL_SIZE and MAX_IMAGE_SIZE options, but -# will have to be referenced manually to be visible on the site +# This is a dictionary of lists. Each key in the dictionary is the +# name of a IDF, and each list item is a field you want to preserve. +# If you have a IDF with only a '*' item, *EVERY* item in it will be +# preserved. If you don't want to preserve anything in a IDF, remove it +# from the setting. By default, no EXIF information is kept. +# Setting the whitelist to anything other than {} implies +# PRESERVE_EXIF_DATA is set to True +# To preserve ALL EXIF data, set EXIF_WHITELIST to {"*": "*"} + +# EXIF_WHITELIST = {} + +# Some examples of EXIF_WHITELIST settings: + +# Basic image information: +# EXIF_WHITELIST['0th'] = [ +# "Orientation", +# "XResolution", +# "YResolution", +# ] + +# If you want to keep GPS data in the images: +# EXIF_WHITELIST['GPS'] = ["*"] + +# Embedded thumbnail information: +# EXIF_WHITELIST['1st'] = ["*"] + +# Folders containing images to be used in normal posts or pages. +# IMAGE_FOLDERS is a dictionary of the form {"source": "destination"}, +# where "source" is the folder containing the images to be published, and +# "destination" is the folder under OUTPUT_PATH containing the images copied +# to the site. Thumbnail images will be created there as well. + +# To reference the images in your posts, include a leading slash in the path. +# For example, if IMAGE_FOLDERS = {'images': 'images'}, write +# +# ..image:: /images/tesla.jpg +# +# See the Nikola Handbook for details (in the “Embedding Images” and +# “Thumbnails” sections) + +# Images will be scaled down according to IMAGE_THUMBNAIL_SIZE and MAX_IMAGE_SIZE +# options, but will have to be referenced manually to be visible on the site # (the thumbnail has ``.thumbnail`` added before the file extension). -# The format is a dictionary of {source: relative destination}. IMAGE_FOLDERS = {'images': 'images'} # IMAGE_THUMBNAIL_SIZE = 400 @@ -751,13 +820,13 @@ COMMENT_SYSTEM_ID = ${COMMENT_SYSTEM_ID} # the "noannotations" metadata. # ANNOTATIONS = False -# Create index.html for page (story) folders? +# Create index.html for page folders? # WARNING: if a page would conflict with the index file (usually -# caused by setting slug to `index`), the STORY_INDEX +# caused by setting slug to `index`), the PAGE_INDEX # will not be generated for that directory. -# STORY_INDEX = False -# Enable comments on story pages? -# COMMENTS_IN_STORIES = False +# PAGE_INDEX = False +# Enable comments on pages (i.e. not posts)? +# COMMENTS_IN_PAGES = False # Enable comments on picture gallery pages? # COMMENTS_IN_GALLERIES = False @@ -835,9 +904,20 @@ PRETTY_URLS = ${PRETTY_URLS} # it's faster and the output looks better. # If you set USE_KATEX to True, you also need to add an extra CSS file # like this: -# EXTRA_HEAD_DATA = """<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/katex.min.css">""" +# EXTRA_HEAD_DATA = """<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/katex.min.css">""" # USE_KATEX = False +# If you want to use the old (buggy) inline math $.$ with KaTeX, then +# you might want to use this feature. +# KATEX_AUTO_RENDER = """ +# delimiters: [ +# {left: "$$", right: "$$", display: true}, +# {left: "\\\[", right: "\\\]", display: true}, +# {left: "$", right: "$", display: false}, +# {left: "\\\(", right: "\\\)", display: false} +# ] +# """ + # Do you want to customize the nbconversion of your IPython notebook? # IPYNB_CONFIG = {} # With the following example configuration you can use a custom jinja template @@ -855,6 +935,8 @@ MARKDOWN_EXTENSIONS = ['fenced_code', 'codehilite', 'extra'] # Extra options to pass to the pandoc comand. # by default, it's empty, is a list of strings, for example # ['-F', 'pandoc-citeproc', '--bibliography=/Users/foo/references.bib'] +# Pandoc does not demote headers by default. To enable this, you can use, for example +# ['--base-header-level=2'] # PANDOC_OPTIONS = [] # Social buttons. This is sample code for AddThis (which was the default for a @@ -871,7 +953,7 @@ MARKDOWN_EXTENSIONS = ['fenced_code', 'codehilite', 'extra'] # <li><a class="addthis_button_twitter"></a> # </ul> # </div> -# <script src="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-4f7088a56bb93798"></script> +# <script src="https://s7.addthis.com/js/300/addthis_widget.js#pubid=ra-4f7088a56bb93798"></script> # <!-- End of social buttons --> # """ @@ -932,7 +1014,7 @@ MARKDOWN_EXTENSIONS = ['fenced_code', 'codehilite', 'extra'] # # SEARCH_FORM = """ # <!-- DuckDuckGo custom search --> -# <form method="get" id="search" action="//duckduckgo.com/" +# <form method="get" id="search" action="https://duckduckgo.com/" # class="navbar-form pull-left"> # <input type="hidden" name="sites" value="%s"> # <input type="hidden" name="k8" value="#444444"> @@ -995,7 +1077,8 @@ MARKDOWN_EXTENSIONS = ['fenced_code', 'codehilite', 'extra'] # - description # # An example re is the following: -# '(?P<date>\d{4}-\d{2}-\d{2})-(?P<slug>.*)-(?P<title>.*)\.md' +# '.*\/(?P<date>\d{4}-\d{2}-\d{2})-(?P<slug>.*)-(?P<title>.*)\.rst' +# (Note the '.*\/' in the beginning -- matches source paths relative to conf.py) # FILE_METADATA_REGEXP = None # If you hate "Filenames with Capital Letters and Spaces.md", you should @@ -1042,6 +1125,11 @@ UNSLUGIFY_TITLES = True # repository. # EXTRA_PLUGINS_DIRS = [] +# Add the absolute paths to directories containing themes to use them. +# For example, the `v7` directory of your clone of the Nikola themes +# repository. +# EXTRA_THEMES_DIRS = [] + # List of regular expressions, links matching them will always be considered # valid by "nikola check -l" # LINK_CHECK_WHITELIST = [] @@ -1059,6 +1147,12 @@ UNSLUGIFY_TITLES = True # (defaults to 1.) # DEMOTE_HEADERS = 1 +# Docutils, by default, will perform a transform in your documents +# extracting unique titles at the top of your document and turning +# them into metadata. This surprises a lot of people, and setting +# this option to True will prevent it. +# NO_DOCUTILS_TITLE_TRANSFORM = False + # If you don’t like slugified file names ([a-z0-9] and a literal dash), # and would prefer to use all the characters your file system allows. # USE WITH CARE! This is also not guaranteed to be perfect, and may diff --git a/nikola/data/samplesite/stories/1.rst b/nikola/data/samplesite/pages/1.rst index 2e70345..2e70345 100644 --- a/nikola/data/samplesite/stories/1.rst +++ b/nikola/data/samplesite/pages/1.rst diff --git a/nikola/data/samplesite/stories/bootstrap-demo.rst b/nikola/data/samplesite/pages/bootstrap-demo.rst index 481140a..481140a 100644 --- a/nikola/data/samplesite/stories/bootstrap-demo.rst +++ b/nikola/data/samplesite/pages/bootstrap-demo.rst diff --git a/nikola/data/samplesite/stories/charts.txt b/nikola/data/samplesite/pages/charts.txt index 72fedb1..72fedb1 100644 --- a/nikola/data/samplesite/stories/charts.txt +++ b/nikola/data/samplesite/pages/charts.txt diff --git a/nikola/data/samplesite/stories/creating-a-theme.rst b/nikola/data/samplesite/pages/creating-a-theme.rst index 108a192..108a192 120000 --- a/nikola/data/samplesite/stories/creating-a-theme.rst +++ b/nikola/data/samplesite/pages/creating-a-theme.rst diff --git a/nikola/data/samplesite/stories/dr-nikolas-vendetta.rst b/nikola/data/samplesite/pages/dr-nikolas-vendetta.rst index 6175355..6175355 100644 --- a/nikola/data/samplesite/stories/dr-nikolas-vendetta.rst +++ b/nikola/data/samplesite/pages/dr-nikolas-vendetta.rst diff --git a/nikola/data/samplesite/stories/extending.txt b/nikola/data/samplesite/pages/extending.txt index f545532..f545532 120000 --- a/nikola/data/samplesite/stories/extending.txt +++ b/nikola/data/samplesite/pages/extending.txt diff --git a/nikola/data/samplesite/stories/internals.txt b/nikola/data/samplesite/pages/internals.txt index b955b57..b955b57 120000 --- a/nikola/data/samplesite/stories/internals.txt +++ b/nikola/data/samplesite/pages/internals.txt diff --git a/nikola/data/samplesite/stories/listings-demo.rst b/nikola/data/samplesite/pages/listings-demo.rst index 3bb8dc6..3bb8dc6 100644 --- a/nikola/data/samplesite/stories/listings-demo.rst +++ b/nikola/data/samplesite/pages/listings-demo.rst diff --git a/nikola/data/samplesite/stories/manual.rst b/nikola/data/samplesite/pages/manual.rst index 9992900..9992900 120000 --- a/nikola/data/samplesite/stories/manual.rst +++ b/nikola/data/samplesite/pages/manual.rst diff --git a/nikola/data/samplesite/stories/path_handlers.txt b/nikola/data/samplesite/pages/path_handlers.txt index cce056b..cce056b 120000 --- a/nikola/data/samplesite/stories/path_handlers.txt +++ b/nikola/data/samplesite/pages/path_handlers.txt diff --git a/nikola/data/samplesite/stories/quickref.rst b/nikola/data/samplesite/pages/quickref.rst index 7cc91bd..7cc91bd 100644 --- a/nikola/data/samplesite/stories/quickref.rst +++ b/nikola/data/samplesite/pages/quickref.rst diff --git a/nikola/data/samplesite/stories/quickstart.rst b/nikola/data/samplesite/pages/quickstart.rst index d08b295..5937e56 100644 --- a/nikola/data/samplesite/stories/quickstart.rst +++ b/nikola/data/samplesite/pages/quickstart.rst @@ -354,7 +354,7 @@ example:: ... Note that "Document Title" and "Section Title" above both use equals -signs, but are distict and unrelated styles. The text of +signs, but are distinct and unrelated styles. The text of overline-and-underlined titles (but not underlined-only) may be inset for aesthetics. diff --git a/nikola/data/samplesite/stories/slides-demo.rst b/nikola/data/samplesite/pages/slides-demo.rst index 0d07bbc..0d07bbc 100644 --- a/nikola/data/samplesite/stories/slides-demo.rst +++ b/nikola/data/samplesite/pages/slides-demo.rst diff --git a/nikola/data/samplesite/stories/social_buttons.txt b/nikola/data/samplesite/pages/social_buttons.txt index b60d598..b60d598 120000 --- a/nikola/data/samplesite/stories/social_buttons.txt +++ b/nikola/data/samplesite/pages/social_buttons.txt diff --git a/nikola/data/samplesite/stories/theming.rst b/nikola/data/samplesite/pages/theming.rst index d2dddb6..d2dddb6 120000 --- a/nikola/data/samplesite/stories/theming.rst +++ b/nikola/data/samplesite/pages/theming.rst diff --git a/nikola/data/samplesite/templates/book.tmpl b/nikola/data/samplesite/templates/book.tmpl index a44f088..d808729 100644 --- a/nikola/data/samplesite/templates/book.tmpl +++ b/nikola/data/samplesite/templates/book.tmpl @@ -90,7 +90,7 @@ </%block> <%block name="extra_js"> - <script src="//cdnjs.cloudflare.com/ajax/libs/Flowtype.js/1.1.0/flowtype.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/Flowtype.js/1.1.0/flowtype.min.js"></script> <script> $('#scrolling-cont').flowtype({ minimum: 500, diff --git a/nikola/data/shortcodes/jinja/raw.tmpl b/nikola/data/shortcodes/jinja/raw.tmpl new file mode 100644 index 0000000..0e9d6dd --- /dev/null +++ b/nikola/data/shortcodes/jinja/raw.tmpl @@ -0,0 +1 @@ +{{ data }}
\ No newline at end of file diff --git a/nikola/data/shortcodes/mako/raw.tmpl b/nikola/data/shortcodes/mako/raw.tmpl new file mode 100644 index 0000000..d594403 --- /dev/null +++ b/nikola/data/shortcodes/mako/raw.tmpl @@ -0,0 +1 @@ +${data}
\ No newline at end of file diff --git a/nikola/data/symlinked.txt b/nikola/data/symlinked.txt index a5d48b2..c0d37eb 100644 --- a/nikola/data/symlinked.txt +++ b/nikola/data/symlinked.txt @@ -6,22 +6,24 @@ docs/sphinx/manual.txt docs/sphinx/path_handlers.txt docs/sphinx/social_buttons.txt docs/sphinx/theming.txt -nikola/data/samplesite/stories/creating-a-theme.rst -nikola/data/samplesite/stories/extending.txt -nikola/data/samplesite/stories/internals.txt -nikola/data/samplesite/stories/manual.rst -nikola/data/samplesite/stories/path_handlers.txt -nikola/data/samplesite/stories/social_buttons.txt -nikola/data/samplesite/stories/theming.rst +nikola/data/samplesite/pages/creating-a-theme.rst +nikola/data/samplesite/pages/extending.txt +nikola/data/samplesite/pages/internals.txt +nikola/data/samplesite/pages/manual.rst +nikola/data/samplesite/pages/path_handlers.txt +nikola/data/samplesite/pages/social_buttons.txt +nikola/data/samplesite/pages/theming.rst nikola/data/symlink-test-link.txt nikola/data/themes/base/assets/js/moment-with-locales.min.js nikola/data/themes/base/messages/messages_cz.py nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap-theme.css nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap-theme.css.map nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap-theme.min.css +nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap-theme.min.css.map nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap.css nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap.css.map nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap.min.css +nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap.min.css.map nikola/data/themes/bootstrap3-jinja/assets/css/colorbox.css nikola/data/themes/bootstrap3-jinja/assets/css/images/controls.png nikola/data/themes/bootstrap3-jinja/assets/css/images/loading.gif @@ -79,9 +81,11 @@ nikola/data/themes/bootstrap3-jinja/bundles nikola/data/themes/bootstrap3/assets/css/bootstrap-theme.css nikola/data/themes/bootstrap3/assets/css/bootstrap-theme.css.map nikola/data/themes/bootstrap3/assets/css/bootstrap-theme.min.css +nikola/data/themes/bootstrap3/assets/css/bootstrap-theme.min.css.map nikola/data/themes/bootstrap3/assets/css/bootstrap.css nikola/data/themes/bootstrap3/assets/css/bootstrap.css.map nikola/data/themes/bootstrap3/assets/css/bootstrap.min.css +nikola/data/themes/bootstrap3/assets/css/bootstrap.min.css.map nikola/data/themes/bootstrap3/assets/css/colorbox.css nikola/data/themes/bootstrap3/assets/css/images/controls.png nikola/data/themes/bootstrap3/assets/css/images/loading.gif diff --git a/nikola/data/themes/base-jinja/bundles b/nikola/data/themes/base-jinja/bundles deleted file mode 100644 index 4760181..0000000 --- a/nikola/data/themes/base-jinja/bundles +++ /dev/null @@ -1,2 +0,0 @@ -assets/css/all.css=rst.css,code.css,theme.css -assets/css/all-nocdn.css=rst.css,code.css,theme.css diff --git a/nikola/data/themes/base-jinja/templates/author.tmpl b/nikola/data/themes/base-jinja/templates/author.tmpl index 1604939..327debe 100644 --- a/nikola/data/themes/base-jinja/templates/author.tmpl +++ b/nikola/data/themes/base-jinja/templates/author.tmpl @@ -35,7 +35,7 @@ {% if posts %} <ul class="postlist"> {% for post in posts %} - <li><a href="{{ post.permalink() }}" class="listtitle">{{ post.title()|e }}</a><time class="listdate" datetime="{{ post.formatted_date('webiso') }}" title="{{ post.formatted_date(date_format)|e }}">{{ post.formatted_date(date_format)|e }}</time></li> + <li><time class="listdate" datetime="{{ post.formatted_date('webiso') }}" title="{{ post.formatted_date(date_format)|e }}">{{ post.formatted_date(date_format)|e }}</time> <a href="{{ post.permalink() }}" class="listtitle">{{ post.title()|e }}</a></li> {% endfor %} </ul> {% endif %} diff --git a/nikola/data/themes/base-jinja/templates/base_helper.tmpl b/nikola/data/themes/base-jinja/templates/base_helper.tmpl index 66d82e9..04f49fe 100644 --- a/nikola/data/themes/base-jinja/templates/base_helper.tmpl +++ b/nikola/data/themes/base-jinja/templates/base_helper.tmpl @@ -58,7 +58,7 @@ lang="{{ lang }}"> {{ mathjax_config }} {% if use_cdn %} - <!--[if lt IE 9]><script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]--> + <!--[if lt IE 9]><script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]--> {% else %} <!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang) }}"></script><![endif]--> {% endif %} diff --git a/nikola/data/themes/base-jinja/templates/comments_helper_disqus.tmpl b/nikola/data/themes/base-jinja/templates/comments_helper_disqus.tmpl index 0d40b0b..981453d 100644 --- a/nikola/data/themes/base-jinja/templates/comments_helper_disqus.tmpl +++ b/nikola/data/themes/base-jinja/templates/comments_helper_disqus.tmpl @@ -19,12 +19,12 @@ }; (function() { var dsq = document.createElement('script'); dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; + dsq.src = 'https://' + disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); })(); </script> - <noscript>Please enable JavaScript to view the <a href="//disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript> - <a href="//disqus.com" class="dsq-brlink" rel="nofollow">Comments powered by <span class="logo-disqus">Disqus</span></a> + <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript> + <a href="https://disqus.com" class="dsq-brlink" rel="nofollow">Comments powered by <span class="logo-disqus">Disqus</span></a> {% endif %} {% endmacro %} @@ -37,6 +37,6 @@ {% macro comment_link_script() %} {% if comment_system_id %} - <script>var disqus_shortname="{{ comment_system_id }}";(function(){var a=document.createElement("script");a.async=true;a.src="//"+disqus_shortname+".disqus.com/count.js";(document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]).appendChild(a)}());</script> + <script>var disqus_shortname="{{ comment_system_id }}";(function(){var a=document.createElement("script");a.async=true;a.src="https://"+disqus_shortname+".disqus.com/count.js";(document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]).appendChild(a)}());</script> {% endif %} {% endmacro %} diff --git a/nikola/data/themes/base-jinja/templates/comments_helper_facebook.tmpl b/nikola/data/themes/base-jinja/templates/comments_helper_facebook.tmpl index 21dac2a..0b0808a 100644 --- a/nikola/data/themes/base-jinja/templates/comments_helper_facebook.tmpl +++ b/nikola/data/themes/base-jinja/templates/comments_helper_facebook.tmpl @@ -17,7 +17,7 @@ var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) {return;} js = d.createElement(s); js.id = id; - js.src = "//connect.facebook.net/en_US/all.js"; + js.src = "https://connect.facebook.net/en_US/all.js"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk')); </script> @@ -55,7 +55,7 @@ var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) {return;} js = d.createElement(s); js.id = id; - js.src = "//connect.facebook.net/en_US/all.js"; + js.src = "https://connect.facebook.net/en_US/all.js"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk')); </script> diff --git a/nikola/data/themes/base-jinja/templates/comments_helper_isso.tmpl b/nikola/data/themes/base-jinja/templates/comments_helper_isso.tmpl index 22a9595..b40b5e4 100644 --- a/nikola/data/themes/base-jinja/templates/comments_helper_isso.tmpl +++ b/nikola/data/themes/base-jinja/templates/comments_helper_isso.tmpl @@ -14,7 +14,7 @@ {% macro comment_link_script() %} - {% if comment_system_id %} + {% if comment_system_id and 'index' in pagekind %} <script src="{{ comment_system_id }}js/count.min.js" data-isso="{{ comment_system_id }}"></script> {% endif %} {% endmacro %} diff --git a/nikola/data/themes/base-jinja/templates/comments_helper_muut.tmpl b/nikola/data/themes/base-jinja/templates/comments_helper_muut.tmpl index 79ae523..ebc0541 100644 --- a/nikola/data/themes/base-jinja/templates/comments_helper_muut.tmpl +++ b/nikola/data/themes/base-jinja/templates/comments_helper_muut.tmpl @@ -9,5 +9,5 @@ {% macro comment_link_script() %} -<script src="//cdn.muut.com/1/moot.min.js"></script> +<script src="https://cdn.muut.com/1/moot.min.js"></script> {% endmacro %} diff --git a/nikola/data/themes/base-jinja/templates/gallery.tmpl b/nikola/data/themes/base-jinja/templates/gallery.tmpl index 01452e4..977dea1 100644 --- a/nikola/data/themes/base-jinja/templates/gallery.tmpl +++ b/nikola/data/themes/base-jinja/templates/gallery.tmpl @@ -17,7 +17,7 @@ {% if folders %} <ul> {% for folder, ftitle in folders %} - <li><a href="{{ folder|urlencode }}"><i + <li><a href="{{ folder }}"><i class="icon-folder-open"></i> {{ ftitle|e }}</a></li> {% endfor %} </ul> diff --git a/nikola/data/themes/base-jinja/templates/index.tmpl b/nikola/data/themes/base-jinja/templates/index.tmpl index a36bac3..f982091 100644 --- a/nikola/data/themes/base-jinja/templates/index.tmpl +++ b/nikola/data/themes/base-jinja/templates/index.tmpl @@ -12,6 +12,9 @@ {% block content %} {% block content_header %}{% endblock %} +{% if 'main_index' in pagekind %} + {{ front_index_header }} +{% endif %} <div class="postindex"> {% for post in posts %} <article class="h-entry post-{{ post.meta('type') }}"> diff --git a/nikola/data/themes/base-jinja/templates/index_helper.tmpl b/nikola/data/themes/base-jinja/templates/index_helper.tmpl index 64b4d12..704c635 100644 --- a/nikola/data/themes/base-jinja/templates/index_helper.tmpl +++ b/nikola/data/themes/base-jinja/templates/index_helper.tmpl @@ -21,16 +21,30 @@ {% macro mathjax_script(posts) %} {% if posts|selectattr("is_mathjax")|list %} {% if use_katex %} - <script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/katex.min.js"></script> - <script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/contrib/auto-render.min.js"></script> - <script> - renderMathInElement(document.body); - </script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/katex.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/contrib/auto-render.min.js"></script> + {% if katex_auto_render %} + <script> + renderMathInElement(document.body, + { + {{ katex_auto_render }} + } + ); + </script> + {% else %} + <script> + renderMathInElement(document.body); + </script> + {% endif %} {% else %} - <script type="text/javascript" src="//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> </script> + <script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> </script> + {% if mathjax_config %} + {{ mathjax_config }} + {% else %} <script type="text/x-mathjax-config"> MathJax.Hub.Config({tex2jax: {inlineMath: [['$latex ','$'], ['\\(','\\)']]}}); </script> + {% endif %} {% endif %} {% endif %} {% endmacro %} diff --git a/nikola/data/themes/base-jinja/templates/list_post.tmpl b/nikola/data/themes/base-jinja/templates/list_post.tmpl index 83f4bcf..1dd2605 100644 --- a/nikola/data/themes/base-jinja/templates/list_post.tmpl +++ b/nikola/data/themes/base-jinja/templates/list_post.tmpl @@ -9,7 +9,7 @@ {% if posts %} <ul class="postlist"> {% for post in posts %} - <li><a href="{{ post.permalink() }}" class="listtitle">{{ post.title()|e }}</a> <time class="listdate" datetime="{{ post.formatted_date('webiso') }}" title="{{ post.formatted_date(date_format)|e }}">{{ post.formatted_date(date_format)|e }}</time></li> + <li><time class="listdate" datetime="{{ post.formatted_date('webiso') }}" title="{{ post.formatted_date(date_format)|e }}">{{ post.formatted_date(date_format)|e }}</time> <a href="{{ post.permalink() }}" class="listtitle">{{ post.title()|e }}</a></li> {% endfor %} </ul> {% else %} diff --git a/nikola/data/themes/base-jinja/templates/listing.tmpl b/nikola/data/themes/base-jinja/templates/listing.tmpl index 281630d..9b6d76d 100644 --- a/nikola/data/themes/base-jinja/templates/listing.tmpl +++ b/nikola/data/themes/base-jinja/templates/listing.tmpl @@ -14,10 +14,13 @@ </ul> {% endif %} {% if code %} + <h1>{{ title }} + {% if source_link %} + <small><a href="{{ source_link }}">({{ messages("Source") }})</a></small> + {% endif %} + </h1> {{ code }} {% endif %} -{% if source_link %} - <p class="sourceline"><a href="{{ source_link }}" id="sourcelink">{{ messages("Source") }}</a></p> -{% endif %} {% endblock %} + diff --git a/nikola/data/themes/base-jinja/templates/post_helper.tmpl b/nikola/data/themes/base-jinja/templates/post_helper.tmpl index f5de54a..e2dcf59 100644 --- a/nikola/data/themes/base-jinja/templates/post_helper.tmpl +++ b/nikola/data/themes/base-jinja/templates/post_helper.tmpl @@ -3,7 +3,7 @@ {% macro meta_translations(post) %} {% if translations|length > 1 %} {% for langname in translations|sort %} - {% if langname != lang and post.is_translation_available(langname) %} + {% if langname != lang and ((not post.skip_untranslated) or post.is_translation_available(langname)) %} <link rel="alternate" hreflang="{{ langname }}" href="{{ post.permalink(langname) }}"> {% endif %} {% endfor %} @@ -87,16 +87,30 @@ {% macro mathjax_script(post) %} {% if post.is_mathjax %} {% if use_katex %} - <script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/katex.min.js"></script> - <script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/contrib/auto-render.min.js"></script> - <script> - renderMathInElement(document.body); - </script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/katex.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/contrib/auto-render.min.js"></script> + {% if katex_auto_render %} + <script> + renderMathInElement(document.body, + { + {{ katex_auto_render }} + } + ); + </script> + {% else %} + <script> + renderMathInElement(document.body); + </script> + {% endif %} {% else %} - <script type="text/javascript" src="//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> </script> + <script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> </script> + {% if mathjax_config %} + {{ mathjax_config }} + {% else %} <script type="text/x-mathjax-config"> MathJax.Hub.Config({tex2jax: {inlineMath: [['$latex ','$'], ['\\(','\\)']]}}); </script> + {% endif %} {% endif %} {% endif %} {% endmacro %} diff --git a/nikola/data/themes/base-jinja/templates/post_list_directive.tmpl b/nikola/data/themes/base-jinja/templates/post_list_directive.tmpl index f8c53cc..40cf1ce 100644 --- a/nikola/data/themes/base-jinja/templates/post_list_directive.tmpl +++ b/nikola/data/themes/base-jinja/templates/post_list_directive.tmpl @@ -6,10 +6,10 @@ <ul class="post-list"> {% for post in posts %} <li class="post-list-item"> - {{ post.formatted_date(date_format)|e }} - - <a href="{{ post.permalink(lang) }}">{{ post.title(lang)|e }}</a> - </li> + {{ post.formatted_date(date_format)|e }} + + <a href="{{ post.permalink(lang) }}">{{ post.title(lang)|e }}</a> + </li> {% endfor %} </ul> {% endif %} diff --git a/nikola/data/themes/base-jinja/templates/story.tmpl b/nikola/data/themes/base-jinja/templates/story.tmpl index 11973f1..1269724 100644 --- a/nikola/data/themes/base-jinja/templates/story.tmpl +++ b/nikola/data/themes/base-jinja/templates/story.tmpl @@ -5,7 +5,7 @@ {% extends 'post.tmpl' %} {% block content %} -<article class="storypage" itemscope="itemscope" itemtype="http://schema.org/Article"> +<article class="post-{{ post.meta('type') }} storypage" itemscope="itemscope" itemtype="http://schema.org/Article"> <header> {{ pheader.html_title() }} {{ pheader.html_translations(post) }} diff --git a/nikola/data/themes/base-jinja/templates/tag.tmpl b/nikola/data/themes/base-jinja/templates/tag.tmpl index f9adc63..363019b 100644 --- a/nikola/data/themes/base-jinja/templates/tag.tmpl +++ b/nikola/data/themes/base-jinja/templates/tag.tmpl @@ -43,7 +43,7 @@ {% if posts %} <ul class="postlist"> {% for post in posts %} - <li><time class="listdate" datetime="{{ post.formatted_date('webiso') }}" title="{{ post.formatted_date(date_format)|e }}">{{ post.formatted_date(date_format)|e }}</time><a href="{{ post.permalink() }}" class="listtitle">{{ post.title()|e }}<a></li> + <li><time class="listdate" datetime="{{ post.formatted_date('webiso') }}" title="{{ post.formatted_date(date_format)|e }}">{{ post.formatted_date(date_format)|e }}</time> <a href="{{ post.permalink() }}" class="listtitle">{{ post.title()|e }}<a></li> {% endfor %} </ul> {% endif %} diff --git a/nikola/data/themes/base-jinja/templates/tagindex.tmpl b/nikola/data/themes/base-jinja/templates/tagindex.tmpl index bc1b02d..624961d 100644 --- a/nikola/data/themes/base-jinja/templates/tagindex.tmpl +++ b/nikola/data/themes/base-jinja/templates/tagindex.tmpl @@ -2,14 +2,20 @@ {% extends 'index.tmpl' %} {% block content_header %} - {% if subcategories %} - {{ messages('Subcategories:') }} - <ul> - {% for name, link in subcategories %} - <li><a href="{{ link }}">{{ name|e }}</a></li> - {% endfor %} - </ul> - {% endif %} + <header> + <h1>{{ title|e }}</h1> + {% if description %} + <p>{{ description }}</p> + {% endif %} + {% if subcategories %} + {{ messages('Subcategories:') }} + <ul> + {% for name, link in subcategories %} + <li><a href="{{ link }}">{{ name|e }}</a></li> + {% endfor %} + </ul> + {% endif %} + </header> {% endblock %} {% block extra_head %} diff --git a/nikola/data/themes/base/assets/css/rst.css b/nikola/data/themes/base/assets/css/rst.css index 0fbab76..a1efa1a 100644 --- a/nikola/data/themes/base/assets/css/rst.css +++ b/nikola/data/themes/base/assets/css/rst.css @@ -191,7 +191,7 @@ img.align-center, .figure.align-center, object.align-center { text-align: right } /* reset inner alignment in figures */ -div.align-right { +.figure.align-right { text-align: inherit } /* div.align-center * { */ diff --git a/nikola/data/themes/base/assets/css/theme.css b/nikola/data/themes/base/assets/css/theme.css index 5c7c86d..4842a3f 100644 --- a/nikola/data/themes/base/assets/css/theme.css +++ b/nikola/data/themes/base/assets/css/theme.css @@ -1,7 +1,7 @@ @charset "UTF-8"; /* - Copyright © 2014-2015 Daniel Aleksandersen and others. + Copyright © 2014-2016 Daniel Aleksandersen and others. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated @@ -120,7 +120,7 @@ body { } .metadata p:before, .postpromonav .tags li:before, -.postlist .listdate:before { +.postlist .listdate:after { content: " — "; } .postlist li { diff --git a/nikola/data/themes/base/assets/js/jquery.min.map b/nikola/data/themes/base/assets/js/jquery.min.map deleted file mode 100644 index 1528143..0000000 --- a/nikola/data/themes/base/assets/js/jquery.min.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"jquery.min.js","sources":["jquery.js"],"names":["global","factory","module","exports","document","w","Error","window","this","noGlobal","deletedIds","slice","concat","push","indexOf","class2type","toString","hasOwn","hasOwnProperty","support","version","jQuery","selector","context","fn","init","rtrim","rmsPrefix","rdashAlpha","fcamelCase","all","letter","toUpperCase","prototype","jquery","constructor","length","toArray","call","get","num","pushStack","elems","ret","merge","prevObject","each","callback","args","map","elem","i","apply","arguments","first","eq","last","len","j","end","sort","splice","extend","src","copyIsArray","copy","name","options","clone","target","deep","isFunction","isPlainObject","isArray","undefined","expando","Math","random","replace","isReady","error","msg","noop","obj","type","Array","isWindow","isNumeric","parseFloat","isEmptyObject","key","nodeType","e","ownLast","globalEval","data","trim","execScript","camelCase","string","nodeName","toLowerCase","value","isArraylike","text","makeArray","arr","results","Object","inArray","max","second","grep","invert","callbackInverse","matches","callbackExpect","arg","guid","proxy","tmp","now","Date","split","Sizzle","Expr","getText","isXML","tokenize","compile","select","outermostContext","sortInput","hasDuplicate","setDocument","docElem","documentIsHTML","rbuggyQSA","rbuggyMatches","contains","preferredDoc","dirruns","done","classCache","createCache","tokenCache","compilerCache","sortOrder","a","b","MAX_NEGATIVE","pop","push_native","list","booleans","whitespace","characterEncoding","identifier","attributes","pseudos","rwhitespace","RegExp","rcomma","rcombinators","rattributeQuotes","rpseudo","ridentifier","matchExpr","ID","CLASS","TAG","ATTR","PSEUDO","CHILD","bool","needsContext","rinputs","rheader","rnative","rquickExpr","rsibling","rescape","runescape","funescape","_","escaped","escapedWhitespace","high","String","fromCharCode","unloadHandler","childNodes","els","seed","match","m","groups","old","nid","newContext","newSelector","ownerDocument","exec","getElementById","parentNode","id","getElementsByTagName","getElementsByClassName","qsa","test","getAttribute","setAttribute","toSelector","testContext","join","querySelectorAll","qsaError","removeAttribute","keys","cache","cacheLength","shift","markFunction","assert","div","createElement","removeChild","addHandle","attrs","handler","attrHandle","siblingCheck","cur","diff","sourceIndex","nextSibling","createInputPseudo","createButtonPseudo","createPositionalPseudo","argument","matchIndexes","documentElement","node","hasCompare","parent","doc","defaultView","top","addEventListener","attachEvent","className","appendChild","createComment","getById","getElementsByName","find","filter","attrId","getAttributeNode","tag","innerHTML","input","matchesSelector","webkitMatchesSelector","mozMatchesSelector","oMatchesSelector","msMatchesSelector","disconnectedMatch","compareDocumentPosition","adown","bup","compare","sortDetached","aup","ap","bp","unshift","expr","elements","attr","val","specified","uniqueSort","duplicates","detectDuplicates","sortStable","textContent","firstChild","nodeValue","selectors","createPseudo","relative",">","dir"," ","+","~","preFilter","excess","unquoted","nodeNameSelector","pattern","operator","check","result","what","simple","forward","ofType","xml","outerCache","nodeIndex","start","useCache","lastChild","pseudo","setFilters","idx","matched","not","matcher","unmatched","has","innerText","lang","elemLang","hash","location","root","focus","activeElement","hasFocus","href","tabIndex","enabled","disabled","checked","selected","selectedIndex","empty","header","button","even","odd","lt","gt","radio","checkbox","file","password","image","submit","reset","filters","parseOnly","tokens","soFar","preFilters","cached","addCombinator","combinator","base","checkNonElements","doneName","oldCache","newCache","elementMatcher","matchers","multipleContexts","contexts","condense","newUnmatched","mapped","setMatcher","postFilter","postFinder","postSelector","temp","preMap","postMap","preexisting","matcherIn","matcherOut","matcherFromTokens","checkContext","leadingRelative","implicitRelative","matchContext","matchAnyContext","matcherFromGroupMatchers","elementMatchers","setMatchers","bySet","byElement","superMatcher","outermost","matchedCount","setMatched","contextBackup","dirrunsUnique","token","compiled","div1","defaultValue","unique","isXMLDoc","rneedsContext","rsingleTag","risSimple","winnow","qualifier","self","is","rootjQuery","charAt","parseHTML","ready","rparentsprev","guaranteedUnique","children","contents","next","prev","until","sibling","n","r","targets","closest","l","pos","index","prevAll","add","addBack","parents","parentsUntil","nextAll","nextUntil","prevUntil","siblings","contentDocument","contentWindow","reverse","rnotwhite","optionsCache","createOptions","object","flag","Callbacks","firing","memory","fired","firingLength","firingIndex","firingStart","stack","once","fire","stopOnFalse","disable","remove","lock","locked","fireWith","Deferred","func","tuples","state","promise","always","deferred","fail","then","fns","newDefer","tuple","returned","resolve","reject","progress","notify","pipe","stateString","when","subordinate","resolveValues","remaining","updateFunc","values","progressValues","notifyWith","resolveWith","progressContexts","resolveContexts","readyList","readyWait","holdReady","hold","wait","body","setTimeout","triggerHandler","off","detach","removeEventListener","completed","detachEvent","event","readyState","frameElement","doScroll","doScrollCheck","strundefined","inlineBlockNeedsLayout","container","style","cssText","zoom","offsetWidth","deleteExpando","acceptData","noData","rbrace","rmultiDash","dataAttr","parseJSON","isEmptyDataObject","internalData","pvt","thisCache","internalKey","isNode","toJSON","internalRemoveData","cleanData","applet ","embed ","object ","hasData","removeData","_data","_removeData","queue","dequeue","startLength","hooks","_queueHooks","stop","setter","clearQueue","count","defer","pnum","source","cssExpand","isHidden","el","css","access","chainable","emptyGet","raw","bulk","rcheckableType","fragment","createDocumentFragment","leadingWhitespace","tbody","htmlSerialize","html5Clone","cloneNode","outerHTML","appendChecked","noCloneChecked","checkClone","noCloneEvent","click","eventName","change","focusin","rformElems","rkeyEvent","rmouseEvent","rfocusMorph","rtypenamespace","returnTrue","returnFalse","safeActiveElement","err","types","events","t","handleObjIn","special","eventHandle","handleObj","handlers","namespaces","origType","elemData","handle","triggered","dispatch","delegateType","bindType","namespace","delegateCount","setup","mappedTypes","origCount","teardown","removeEvent","trigger","onlyHandlers","ontype","bubbleType","eventPath","Event","isTrigger","namespace_re","noBubble","parentWindow","isPropagationStopped","preventDefault","isDefaultPrevented","_default","fix","handlerQueue","delegateTarget","preDispatch","currentTarget","isImmediatePropagationStopped","stopPropagation","postDispatch","sel","prop","originalEvent","fixHook","fixHooks","mouseHooks","keyHooks","props","srcElement","metaKey","original","which","charCode","keyCode","eventDoc","fromElement","pageX","clientX","scrollLeft","clientLeft","pageY","clientY","scrollTop","clientTop","relatedTarget","toElement","load","blur","beforeunload","returnValue","simulate","bubble","isSimulated","defaultPrevented","timeStamp","cancelBubble","stopImmediatePropagation","mouseenter","mouseleave","pointerenter","pointerleave","orig","related","submitBubbles","form","_submit_bubble","changeBubbles","propertyName","_just_changed","focusinBubbles","attaches","on","one","origFn","createSafeFragment","nodeNames","safeFrag","rinlinejQuery","rnoshimcache","rleadingWhitespace","rxhtmlTag","rtagName","rtbody","rhtml","rnoInnerhtml","rchecked","rscriptType","rscriptTypeMasked","rcleanScript","wrapMap","option","legend","area","param","thead","tr","col","td","safeFragment","fragmentDiv","optgroup","tfoot","colgroup","caption","th","getAll","found","fixDefaultChecked","defaultChecked","manipulationTarget","content","disableScript","restoreScript","setGlobalEval","refElements","cloneCopyEvent","dest","oldData","curData","fixCloneNodeIssues","defaultSelected","dataAndEvents","deepDataAndEvents","destElements","srcElements","inPage","buildFragment","scripts","selection","wrap","safe","nodes","createTextNode","append","domManip","prepend","insertBefore","before","after","keepData","html","replaceWith","replaceChild","hasScripts","set","iNoClone","_evalUrl","appendTo","prependTo","insertAfter","replaceAll","insert","iframe","elemdisplay","actualDisplay","display","getDefaultComputedStyle","defaultDisplay","write","close","shrinkWrapBlocksVal","shrinkWrapBlocks","width","rmargin","rnumnonpx","getStyles","curCSS","rposition","getComputedStyle","opener","computed","minWidth","maxWidth","getPropertyValue","currentStyle","left","rs","rsLeft","runtimeStyle","pixelLeft","addGetHookIf","conditionFn","hookFn","condition","pixelPositionVal","boxSizingReliableVal","reliableHiddenOffsetsVal","reliableMarginRightVal","opacity","cssFloat","backgroundClip","clearCloneStyle","boxSizing","MozBoxSizing","WebkitBoxSizing","reliableHiddenOffsets","computeStyleTests","boxSizingReliable","pixelPosition","reliableMarginRight","marginRight","offsetHeight","swap","ralpha","ropacity","rdisplayswap","rnumsplit","rrelNum","cssShow","position","visibility","cssNormalTransform","letterSpacing","fontWeight","cssPrefixes","vendorPropName","capName","origName","showHide","show","hidden","setPositiveNumber","subtract","augmentWidthOrHeight","extra","isBorderBox","styles","getWidthOrHeight","valueIsBorderBox","cssHooks","cssNumber","columnCount","fillOpacity","flexGrow","flexShrink","lineHeight","order","orphans","widows","zIndex","cssProps","float","$1","margin","padding","border","prefix","suffix","expand","expanded","parts","hide","toggle","Tween","easing","unit","propHooks","run","percent","eased","duration","step","tween","fx","linear","p","swing","cos","PI","fxNow","timerId","rfxtypes","rfxnum","rrun","animationPrefilters","defaultPrefilter","tweeners","*","createTween","scale","maxIterations","createFxNow","genFx","includeWidth","height","animation","collection","opts","oldfire","checkDisplay","anim","dataShow","unqueued","overflow","overflowX","overflowY","propFilter","specialEasing","Animation","properties","stopped","tick","currentTime","startTime","tweens","originalProperties","originalOptions","gotoEnd","rejectWith","timer","complete","tweener","prefilter","speed","opt","speeds","fadeTo","to","animate","optall","doAnimation","finish","stopQueue","timers","cssFn","slideDown","slideUp","slideToggle","fadeIn","fadeOut","fadeToggle","interval","setInterval","clearInterval","slow","fast","delay","time","timeout","clearTimeout","getSetAttribute","hrefNormalized","checkOn","optSelected","enctype","optDisabled","radioValue","rreturn","valHooks","optionSet","scrollHeight","nodeHook","boolHook","ruseDefault","getSetInput","removeAttr","nType","attrHooks","propName","attrNames","propFix","getter","setAttributeNode","createAttribute","coords","contenteditable","rfocusable","rclickable","removeProp","for","class","notxml","tabindex","parseInt","rclass","addClass","classes","clazz","finalValue","proceed","removeClass","toggleClass","stateVal","classNames","hasClass","hover","fnOver","fnOut","bind","unbind","delegate","undelegate","nonce","rquery","rvalidtokens","JSON","parse","requireNonComma","depth","str","comma","open","Function","parseXML","DOMParser","parseFromString","ActiveXObject","async","loadXML","ajaxLocParts","ajaxLocation","rhash","rts","rheaders","rlocalProtocol","rnoContent","rprotocol","rurl","prefilters","transports","allTypes","addToPrefiltersOrTransports","structure","dataTypeExpression","dataType","dataTypes","inspectPrefiltersOrTransports","jqXHR","inspected","seekingTransport","inspect","prefilterOrFactory","dataTypeOrTransport","ajaxExtend","flatOptions","ajaxSettings","ajaxHandleResponses","s","responses","firstDataType","ct","finalDataType","mimeType","getResponseHeader","converters","ajaxConvert","response","isSuccess","conv2","current","conv","responseFields","dataFilter","active","lastModified","etag","url","isLocal","processData","contentType","accepts","json","* text","text html","text json","text xml","ajaxSetup","settings","ajaxPrefilter","ajaxTransport","ajax","cacheURL","responseHeadersString","timeoutTimer","fireGlobals","transport","responseHeaders","callbackContext","globalEventContext","completeDeferred","statusCode","requestHeaders","requestHeadersNames","strAbort","getAllResponseHeaders","setRequestHeader","lname","overrideMimeType","code","status","abort","statusText","finalText","success","method","crossDomain","traditional","hasContent","ifModified","headers","beforeSend","send","nativeStatusText","modified","getJSON","getScript","throws","wrapAll","wrapInner","unwrap","visible","r20","rbracket","rCRLF","rsubmitterTypes","rsubmittable","buildParams","v","encodeURIComponent","serialize","serializeArray","xhr","createStandardXHR","createActiveXHR","xhrId","xhrCallbacks","xhrSupported","cors","username","xhrFields","isAbort","onreadystatechange","responseText","XMLHttpRequest","script","text script","head","scriptCharset","charset","onload","oldCallbacks","rjsonp","jsonp","jsonpCallback","originalSettings","callbackName","overwritten","responseContainer","jsonProp","keepScripts","parsed","_load","params","animated","getWindow","offset","setOffset","curPosition","curLeft","curCSSTop","curTop","curOffset","curCSSLeft","calculatePosition","curElem","using","win","box","getBoundingClientRect","pageYOffset","pageXOffset","offsetParent","parentOffset","scrollTo","Height","Width","defaultExtra","funcName","size","andSelf","define","amd","_jQuery","_$","$","noConflict"],"mappings":"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"}
\ No newline at end of file diff --git a/nikola/data/themes/base/assets/xml/atom.xsl b/nikola/data/themes/base/assets/xml/atom.xsl index cc052e0..7b18344 100644 --- a/nikola/data/themes/base/assets/xml/atom.xsl +++ b/nikola/data/themes/base/assets/xml/atom.xsl @@ -14,7 +14,7 @@ <p>This is an Atom feed. To subscribe to it, copy its address and paste it when your feed reader asks for it. It will be updated periodically in your reader. New to feeds? <a href="https://duckduckgo.com/?q=how+to+get+started+with+rss+feeds" title="Search on the web to learn more">Learn more</a>.</p> <p> <label for="address">Atom feed address:</label> -<input><xsl:attribute name="id">address</xsl:attribute><xsl:attribute name="spellcheck">false</xsl:attribute><xsl:attribute name="value"><xsl:value-of select="feed/link[@rel='self']/@href"/></xsl:attribute></input> +<input><xsl:attribute name="type">url</xsl:attribute><xsl:attribute name="id">address</xsl:attribute><xsl:attribute name="spellcheck">false</xsl:attribute><xsl:attribute name="value"><xsl:value-of select="feed/link[@rel='self']/@href"/></xsl:attribute></input> </p> <p>Preview of the feed’s current headlines:</p> <ol> diff --git a/nikola/data/themes/base/assets/xml/rss.xsl b/nikola/data/themes/base/assets/xml/rss.xsl index ee72301..f34b3b1 100644 --- a/nikola/data/themes/base/assets/xml/rss.xsl +++ b/nikola/data/themes/base/assets/xml/rss.xsl @@ -14,7 +14,7 @@ <p>This is an <abbr title="Really Simple Syndication">RSS</abbr> feed. To subscribe to it, copy its address and paste it when your feed reader asks for it. It will be updated periodically in your reader. New to feeds? <a href="https://duckduckgo.com/?q=how+to+get+started+with+rss+feeds" title="Search on the web to learn more">Learn more</a>.</p> <p> <label for="address">RSS address:</label> -<input><xsl:attribute name="id">address</xsl:attribute><xsl:attribute name="spellcheck">false</xsl:attribute><xsl:attribute name="value"><xsl:value-of select="rss/channel/atom:link[@rel='self']/@href"/></xsl:attribute></input> +<input><xsl:attribute name="type">url</xsl:attribute><xsl:attribute name="id">address</xsl:attribute><xsl:attribute name="spellcheck">false</xsl:attribute><xsl:attribute name="value"><xsl:value-of select="rss/channel/atom:link[@rel='self']/@href"/></xsl:attribute></input> </p> <p>Preview of the feed’s current headlines:</p> <ol> diff --git a/nikola/data/themes/base/bundles b/nikola/data/themes/base/bundles index d87b458..4760181 100644 --- a/nikola/data/themes/base/bundles +++ b/nikola/data/themes/base/bundles @@ -1,2 +1,2 @@ -assets/css/all.css=rst.css,code.css,theme.css,custom.css -assets/css/all-nocdn.css=rst.css,code.css,theme.css,custom.css +assets/css/all.css=rst.css,code.css,theme.css +assets/css/all-nocdn.css=rst.css,code.css,theme.css diff --git a/nikola/data/themes/base/messages/messages_ar.py b/nikola/data/themes/base/messages/messages_ar.py index 6796b6b..4990ad4 100644 --- a/nikola/data/themes/base/messages/messages_ar.py +++ b/nikola/data/themes/base/messages/messages_ar.py @@ -19,9 +19,9 @@ MESSAGES = { "Older posts": "مقالات أقدم", "Original site": "الموقع الأصلي", "Posted:": "نشر:", - "Posts about %s": "مقالات عن s%", + "Posts about %s": "مقالات عن %s", "Posts by %s": "", - "Posts for year %s": "مقالات سنة s%", + "Posts for year %s": "مقالات سنة %s", "Posts for {month} {day}, {year}": "", "Posts for {month} {year}": "", "Previous post": "المقالة السابقة", @@ -34,10 +34,11 @@ MESSAGES = { "Subcategories:": "", "Tags and Categories": "تصنيفات و فئات", "Tags": "تصنيفات", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "", "Write your post here.": "", - "old posts, page %d": "مقالات قديمة, صفحة d%", - "page %d": "صفحة d%", + "old posts, page %d": "مقالات قديمة, صفحة %d", + "page %d": "صفحة %d", } diff --git a/nikola/data/themes/base/messages/messages_az.py b/nikola/data/themes/base/messages/messages_az.py index 15b2341..11e45ba 100644 --- a/nikola/data/themes/base/messages/messages_az.py +++ b/nikola/data/themes/base/messages/messages_az.py @@ -3,10 +3,10 @@ from __future__ import unicode_literals MESSAGES = { "%d min remaining to read": "%d dəqiqəlik oxuma", - "(active)": "", + "(active)": "(aktiv)", "Also available in:": "Həmçinin mövcuddur:", "Archive": "Arxiv", - "Authors": "", + "Authors": "Müəlliflər", "Categories": "Kateqoriyalar", "Comments": "Şərhlər", "LANGUAGE": "Azərbaycan dili", @@ -20,7 +20,7 @@ MESSAGES = { "Original site": "Original sayt", "Posted:": "yazılma tarixi:", "Posts about %s": "%s ilə bağlı yazılar", - "Posts by %s": "", + "Posts by %s": "%s tərəfindən yazılmış yazılar", "Posts for year %s": "%s ilindəki yazılar", "Posts for {month} {day}, {year}": "{month} {day}, {year} üçün yazılar", "Posts for {month} {year}": "{month} {year} üçün yazılar", @@ -31,13 +31,14 @@ MESSAGES = { "Read more": "Davamı", "Skip to main content": "Əsas mövzuya keç", "Source": "Mənbə", - "Subcategories:": "", + "Subcategories:": "Subkateqoriyalar", "Tags and Categories": "Teqlər və Kateqoriyalar", "Tags": "Teqlər", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", + "Toggle navigation": "", + "Uncategorized": "Kateqoriyasız", + "Updates": "Yenilənmələr", + "Write your page here.": "Öz səhifəni bura yaz", + "Write your post here.": "Öz məqaləni bura yaz", "old posts, page %d": "köhnə yazılar, səhifə %s", "page %d": "səhifə %d", } diff --git a/nikola/data/themes/base/messages/messages_bg.py b/nikola/data/themes/base/messages/messages_bg.py index bd6d301..2e4d400 100644 --- a/nikola/data/themes/base/messages/messages_bg.py +++ b/nikola/data/themes/base/messages/messages_bg.py @@ -2,42 +2,43 @@ from __future__ import unicode_literals MESSAGES = { - "%d min remaining to read": "", - "(active)": "", - "Also available in:": "Също достъпно в:", + "%d min remaining to read": "%d минути до прочитане", + "(active)": "(активно)", + "Also available in:": "Достъпно също на:", "Archive": "Архив", - "Authors": "", + "Authors": "Автори", "Categories": "Категории", - "Comments": "", + "Comments": "Коментари", "LANGUAGE": "Български", - "Languages:": "", + "Languages:": "Езици:", "More posts about %s": "Още публикации относно %s", "Newer posts": "Нови публикации", "Next post": "Следваща публикация", - "No posts found.": "", - "Nothing found.": "", + "No posts found.": "Не са намерени публикации.", + "Nothing found.": "Нищо не е намерено.", "Older posts": "Стари публикации", "Original site": "Оригиналния сайт", - "Posted:": "Публиковано:", + "Posted:": "Публикyвано:", "Posts about %s": "Публикации относно %s", - "Posts by %s": "", + "Posts by %s": "Публикации от %s", "Posts for year %s": "Публикации за %s година", - "Posts for {month} {day}, {year}": "", + "Posts for {month} {day}, {year}": "Публикации от {day} {month} {year}", "Posts for {month} {year}": "Публикации за {month} {year}", "Previous post": "Предишна публикация", - "Publication date": "", - "RSS feed": "", + "Publication date": "Дата на публикуване", + "RSS feed": "RSS поток", "Read in English": "Прочетете на български", - "Read more": "Прочети още", - "Skip to main content": "", - "Source": "Source", - "Subcategories:": "", + "Read more": "Чети нататък", + "Skip to main content": "Прескочи до основното съдържание", + "Source": "Изходен код", + "Subcategories:": "Подкатегории:", "Tags and Categories": "Тагове и Категории", "Tags": "Тагове", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", + "Toggle navigation": "", + "Uncategorized": "Без категория", + "Updates": "Обновления", + "Write your page here.": "Напиши тук текста на твоята страница.", + "Write your post here.": "Напиши тук текста на твоята публикация.", "old posts, page %d": "стари публикации, страница %d", "page %d": "страница %d", } diff --git a/nikola/data/themes/base/messages/messages_bs.py b/nikola/data/themes/base/messages/messages_bs.py index d2bef18..9d7ac6f 100644 --- a/nikola/data/themes/base/messages/messages_bs.py +++ b/nikola/data/themes/base/messages/messages_bs.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Podkategorije:", "Tags and Categories": "Oznake i kategorije", "Tags": "Oznake", + "Toggle navigation": "", "Uncategorized": "Bez kategorije", "Updates": "Ažuriranja", "Write your page here.": "Vašu stranicu napišite ovdje.", diff --git a/nikola/data/themes/base/messages/messages_ca.py b/nikola/data/themes/base/messages/messages_ca.py index bd4cacc..f3aebdf 100644 --- a/nikola/data/themes/base/messages/messages_ca.py +++ b/nikola/data/themes/base/messages/messages_ca.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "", "Tags and Categories": "Etiquetes i Categories", "Tags": "Etiquetes", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "", diff --git a/nikola/data/themes/base/messages/messages_cs.py b/nikola/data/themes/base/messages/messages_cs.py index b509043..42fb1c1 100644 --- a/nikola/data/themes/base/messages/messages_cs.py +++ b/nikola/data/themes/base/messages/messages_cs.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "", "Tags and Categories": "Štítky a kategorie", "Tags": "Štítky", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "", diff --git a/nikola/data/themes/base/messages/messages_da.py b/nikola/data/themes/base/messages/messages_da.py index 34a92f5..08b3d15 100644 --- a/nikola/data/themes/base/messages/messages_da.py +++ b/nikola/data/themes/base/messages/messages_da.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "", "Tags and Categories": "Nøgleord og kategorier", "Tags": "Nøgleord", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "", diff --git a/nikola/data/themes/base/messages/messages_de.py b/nikola/data/themes/base/messages/messages_de.py index fd2c293..7981c69 100644 --- a/nikola/data/themes/base/messages/messages_de.py +++ b/nikola/data/themes/base/messages/messages_de.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Unterkategorien:", "Tags and Categories": "Tags und Kategorien", "Tags": "Tags", + "Toggle navigation": "", "Uncategorized": "Nicht kategorisiert", "Updates": "Updates", "Write your page here.": "Schreibe hier deinen Seiteninhalt hin.", diff --git a/nikola/data/themes/base/messages/messages_el.py b/nikola/data/themes/base/messages/messages_el.py index 9775b30..7e32afa 100644 --- a/nikola/data/themes/base/messages/messages_el.py +++ b/nikola/data/themes/base/messages/messages_el.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "", "Tags and Categories": "Ετικέτες και κατηγορίες", "Tags": "Ετικέτες", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "", diff --git a/nikola/data/themes/base/messages/messages_en.py b/nikola/data/themes/base/messages/messages_en.py index f17ce18..3c39f55 100644 --- a/nikola/data/themes/base/messages/messages_en.py +++ b/nikola/data/themes/base/messages/messages_en.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Subcategories:", "Tags and Categories": "Tags and Categories", "Tags": "Tags", + "Toggle navigation": "Toggle navigation", "Uncategorized": "Uncategorized", "Updates": "Updates", "Write your page here.": "Write your page here.", diff --git a/nikola/data/themes/base/messages/messages_eo.py b/nikola/data/themes/base/messages/messages_eo.py index 126c98f..9d1ae72 100644 --- a/nikola/data/themes/base/messages/messages_eo.py +++ b/nikola/data/themes/base/messages/messages_eo.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Subkategorioj:", "Tags and Categories": "Etikedoj kaj kategorioj", "Tags": "Etikedoj", + "Toggle navigation": "Ŝalti menuon", "Uncategorized": "Sen kategorioj", "Updates": "Ĝisdatigoj", "Write your page here.": "Skribu tie vian paĝon.", diff --git a/nikola/data/themes/base/messages/messages_es.py b/nikola/data/themes/base/messages/messages_es.py index 7457c89..c590a64 100644 --- a/nikola/data/themes/base/messages/messages_es.py +++ b/nikola/data/themes/base/messages/messages_es.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Subcategorías:", "Tags and Categories": "Etiquetas y Categorías", "Tags": "Etiquetas", + "Toggle navigation": "Mostrar navegación", "Uncategorized": "Sin categoría", "Updates": "Actualizaciones", "Write your page here.": "Escriba su página aquí.", diff --git a/nikola/data/themes/base/messages/messages_et.py b/nikola/data/themes/base/messages/messages_et.py index bfe58e4..9b3ba9c 100644 --- a/nikola/data/themes/base/messages/messages_et.py +++ b/nikola/data/themes/base/messages/messages_et.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "", "Tags and Categories": "Sildid ja kategooriad", "Tags": "Märksõnad", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "", diff --git a/nikola/data/themes/base/messages/messages_eu.py b/nikola/data/themes/base/messages/messages_eu.py index 8635f21..68d7162 100644 --- a/nikola/data/themes/base/messages/messages_eu.py +++ b/nikola/data/themes/base/messages/messages_eu.py @@ -2,42 +2,43 @@ from __future__ import unicode_literals MESSAGES = { - "%d min remaining to read": "", - "(active)": "", - "Also available in:": "Eskuragarria hemen ere:", + "%d min remaining to read": "%d minutu gelditzen dira irakurtzeko", + "(active)": "(aktibo)", + "Also available in:": "Eskuragarria hizkuntza hauetan ere:", "Archive": "Artxiboa", - "Authors": "", + "Authors": "Egileak", "Categories": "Kategoriak", - "Comments": "", + "Comments": "Iruzkinak", "LANGUAGE": "Euskara", - "Languages:": "", - "More posts about %s": "%s-ri buruzko post gehiago", - "Newer posts": "Post berrienak", - "Next post": "Hurrengo posta", - "No posts found.": "", - "Nothing found.": "", - "Older posts": "Post zaharrenak", + "Languages:": "Hizkuntzak:", + "More posts about %s": "%s-ri buruzko argitalpen gehiago", + "Newer posts": "Argitalpen berriagoak", + "Next post": "Hurrengo argitalpena", + "No posts found.": "Ez da argitalpenik aurkitu", + "Nothing found.": "Ez da ezer aurkitu", + "Older posts": "Post zaharragoak", "Original site": "Jatorrizko orria", "Posted:": "Argitaratuta:", - "Posts about %s": "%s-ri buruzko postak", - "Posts by %s": "", - "Posts for year %s": "%s. urteko postak", - "Posts for {month} {day}, {year}": "", - "Posts for {month} {year}": "{year}ko {month}ren postak", - "Previous post": "Aurreko posta", - "Publication date": "", - "RSS feed": "", + "Posts about %s": "%s-ri buruzko argitalpenak", + "Posts by %s": "%s-ek idatzitako argitalpenak", + "Posts for year %s": "%s. urteko argitalpenak", + "Posts for {month} {day}, {year}": "{year}ko {month}aren {day}ko argitalpenak", + "Posts for {month} {year}": "{year}ko {month}ren argitalpenak", + "Previous post": "Aurreko argitalpena", + "Publication date": "Argitaratze-data", + "RSS feed": "RSS jarioa", "Read in English": "Euskaraz irakurri", "Read more": "Irakurri gehiago", - "Skip to main content": "", + "Skip to main content": "Joan eduki nagusira", "Source": "Iturria", - "Subcategories:": "", + "Subcategories:": "Azpikategoriak:", "Tags and Categories": "Etiketak eta Kategoriak", "Tags": "Etiketak", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", - "old posts, page %d": "Post zaharren, orria %d", - "page %d": "orria %d", + "Toggle navigation": "", + "Uncategorized": "Kategorizatu-gabeak", + "Updates": "Eguneraketak", + "Write your page here.": "Idatzi zure orria hemen", + "Write your post here.": "Idatzi zure argitalpena hemen", + "old posts, page %d": "Argitalpen zaharragoak,%d. orria", + "page %d": "%d. orria", } diff --git a/nikola/data/themes/base/messages/messages_fa.py b/nikola/data/themes/base/messages/messages_fa.py index 1d96da8..b2abad6 100644 --- a/nikola/data/themes/base/messages/messages_fa.py +++ b/nikola/data/themes/base/messages/messages_fa.py @@ -3,10 +3,10 @@ from __future__ import unicode_literals MESSAGES = { "%d min remaining to read": "%d دقیقه برای خواندن باقی مانده", - "(active)": "", + "(active)": "(فعال)", "Also available in:": "همچنین قابل دسترس از:", "Archive": "آرشیو", - "Authors": "", + "Authors": "نویسندهها", "Categories": "دستهها", "Comments": "دیدگاهها", "LANGUAGE": "فارسی", @@ -20,7 +20,7 @@ MESSAGES = { "Original site": "سایت اصلی", "Posted:": "ارسال شده:", "Posts about %s": "ارسالها دربارهٔ %s", - "Posts by %s": "", + "Posts by %s": "ارسالهای %s", "Posts for year %s": "ارسالها برای سال %s", "Posts for {month} {day}, {year}": "ارسال برای {month} {day}. {year}", "Posts for {month} {year}": "ارسال برای {month} {year}", @@ -31,13 +31,14 @@ MESSAGES = { "Read more": "بیشتر بخوانید", "Skip to main content": "متن اصلی را نادیده بگیر", "Source": "منبع", - "Subcategories:": "", + "Subcategories:": "زیر بخشها:", "Tags and Categories": "برچسبها و دستهها", "Tags": "برچسبها", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", + "Toggle navigation": "", + "Uncategorized": "دستهبندی نشده", + "Updates": "بروزرسانیها", + "Write your page here.": "من صفحه را اینجا بنویسید. ", + "Write your post here.": "متن پستتان را اینجا بنویسید.", "old posts, page %d": "صفحهٔ ارسالهای قدیمی %d", "page %d": "برگه %d", } diff --git a/nikola/data/themes/base/messages/messages_fi.py b/nikola/data/themes/base/messages/messages_fi.py index b7ba141..9a70ede 100644 --- a/nikola/data/themes/base/messages/messages_fi.py +++ b/nikola/data/themes/base/messages/messages_fi.py @@ -6,7 +6,7 @@ MESSAGES = { "(active)": "(aktiivinen)", "Also available in:": "Saatavilla myös:", "Archive": "Arkisto", - "Authors": "", + "Authors": "Kirjoittajat", "Categories": "Kategoriat", "Comments": "Kommentit", "LANGUAGE": "Suomi", @@ -20,9 +20,9 @@ MESSAGES = { "Original site": "Alkuperäinen sivusto", "Posted:": "Postattu:", "Posts about %s": "Postauksia aiheesta %s", - "Posts by %s": "", + "Posts by %s": "Postaukset kirjoittajalta %s", "Posts for year %s": "Postauksia vuodelta %s", - "Posts for {month} {day}, {year}": "", + "Posts for {month} {day}, {year}": "Kirjoituksia ajalta {day}. {month}ta {year}", "Posts for {month} {year}": "Postauksia ajalle {month} {year}", "Previous post": "Edellinen postaus", "Publication date": "Julkaisupäivämäärä", @@ -34,10 +34,11 @@ MESSAGES = { "Subcategories:": "Alakategoriat:", "Tags and Categories": "Tagit ja kategoriat", "Tags": "Tagit", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", + "Toggle navigation": "", + "Uncategorized": "Luokittelematon", + "Updates": "Päivitykset", + "Write your page here.": "Kirjoita sisältö tähän.", + "Write your post here.": "Kirjoita sisältö tähän.", "old posts, page %d": "vanhoja postauksia, sivu %d", "page %d": "sivu %d", } diff --git a/nikola/data/themes/base/messages/messages_fil.py b/nikola/data/themes/base/messages/messages_fil.py index f661c0e..6107c54 100644 --- a/nikola/data/themes/base/messages/messages_fil.py +++ b/nikola/data/themes/base/messages/messages_fil.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "", "Tags and Categories": "", "Tags": "", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "", diff --git a/nikola/data/themes/base/messages/messages_fr.py b/nikola/data/themes/base/messages/messages_fr.py index 1c2025c..6be1422 100644 --- a/nikola/data/themes/base/messages/messages_fr.py +++ b/nikola/data/themes/base/messages/messages_fr.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Sous-catégories", "Tags and Categories": "Étiquettes et catégories", "Tags": "Étiquettes", + "Toggle navigation": "", "Uncategorized": "Sans catégorie", "Updates": "Mises à jour", "Write your page here.": "Écrivez votre page ici.", diff --git a/nikola/data/themes/base/messages/messages_gl.py b/nikola/data/themes/base/messages/messages_gl.py index f661c0e..11ac1cf 100644 --- a/nikola/data/themes/base/messages/messages_gl.py +++ b/nikola/data/themes/base/messages/messages_gl.py @@ -2,42 +2,43 @@ from __future__ import unicode_literals MESSAGES = { - "%d min remaining to read": "", - "(active)": "", - "Also available in:": "", - "Archive": "", - "Authors": "", - "Categories": "", - "Comments": "", - "LANGUAGE": "", - "Languages:": "", - "More posts about %s": "", - "Newer posts": "", - "Next post": "", - "No posts found.": "", - "Nothing found.": "", - "Older posts": "", - "Original site": "", - "Posted:": "", - "Posts about %s": "", - "Posts by %s": "", - "Posts for year %s": "", - "Posts for {month} {day}, {year}": "", - "Posts for {month} {year}": "", - "Previous post": "", - "Publication date": "", - "RSS feed": "", - "Read in English": "", - "Read more": "", - "Skip to main content": "", - "Source": "", - "Subcategories:": "", - "Tags and Categories": "", - "Tags": "", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", - "old posts, page %d": "", - "page %d": "", + "%d min remaining to read": "%d min restantes para ler", + "(active)": "(activo)", + "Also available in:": "Tamén dispoñible en:", + "Archive": "Arquivo", + "Authors": "Autores", + "Categories": "Categorías", + "Comments": "Comentarios", + "LANGUAGE": "Inglés", + "Languages:": "Linguas:", + "More posts about %s": "Máis artigos sobre %s", + "Newer posts": "Últimos artigos", + "Next post": "Seguinte artigo", + "No posts found.": "Non se atoparon artigos.", + "Nothing found.": "Non se atopou nada.", + "Older posts": "Artigos vellos", + "Original site": "Sitio orixinal", + "Posted:": "Publicado:", + "Posts about %s": "Artigos sobre %s", + "Posts by %s": "Publicacións de %s", + "Posts for year %s": "Artigos do ano %s", + "Posts for {month} {day}, {year}": "Artigos de {month} {day}, {year}", + "Posts for {month} {year}": "Artigos de {month} {year}", + "Previous post": "Artigo anterior", + "Publication date": "Data de publicación", + "RSS feed": "Sindicación RSS", + "Read in English": "Ler en Inglés", + "Read more": "Ler máis", + "Skip to main content": "Saltar ó contido principal", + "Source": "Fonte", + "Subcategories:": "Subcategorías:", + "Tags and Categories": "Etiquetas e categorías", + "Tags": "Etiquetas", + "Toggle navigation": "", + "Uncategorized": "Sen categoría", + "Updates": "Actualizacións", + "Write your page here.": "Escribe a túa páxina aquí.", + "Write your post here.": "Escribe o teu artigo aquí.", + "old posts, page %d": "Artigos vellos, páxina %d", + "page %d": "páxina %d", } diff --git a/nikola/data/themes/base/messages/messages_he.py b/nikola/data/themes/base/messages/messages_he.py new file mode 100644 index 0000000..106e35e --- /dev/null +++ b/nikola/data/themes/base/messages/messages_he.py @@ -0,0 +1,44 @@ +# -*- encoding:utf-8 -*- +from __future__ import unicode_literals + +MESSAGES = { + "%d min remaining to read": "d% דקות נותרים לסיום קריאה", + "(active)": "(פעיל)", + "Also available in:": "זמין גם ב:", + "Archive": "ארכיב", + "Authors": "מחברים", + "Categories": "קטגוריות", + "Comments": "הערות", + "LANGUAGE": "אנגלית", + "Languages:": "שפות:", + "More posts about %s": "עוד פוסטים אודות s%", + "Newer posts": "פוסטים חדשים", + "Next post": "לפוסט הבא", + "No posts found.": "לא נמצאו פוסטים", + "Nothing found.": "לא נמצא", + "Older posts": "פוסטים ישנים", + "Original site": "אתר המקורי", + "Posted:": "פורסם:", + "Posts about %s": "פוסטים אודות s%", + "Posts by %s": "פוסטים ע״י s%", + "Posts for year %s": "פוסטים לשנת s%", + "Posts for {month} {day}, {year}": "פוסטים עבוד {year},{day}{month}", + "Posts for {month} {year}": "פוסטים עבוד {year}{month}", + "Previous post": "פוסט הקודם", + "Publication date": "תאריך פרסום", + "RSS feed": "פיד RSS", + "Read in English": "קרא באנגלית", + "Read more": "קרא עוד", + "Skip to main content": "דלג לתוכן ראשי", + "Source": "מקור", + "Subcategories:": "תתי קטגוריות:", + "Tags and Categories": "תגים וקטגוריות", + "Tags": "תגים", + "Toggle navigation": "החלף מצב ניווט", + "Uncategorized": "לא משויך לקטגוריה", + "Updates": "עדכונים", + "Write your page here.": "תכתוב את העמוד שלך פה.", + "Write your post here.": "תכתוב את הפוסט שלך פה.", + "old posts, page %d": "פוסטים קודמים, דף d%", + "page %d": "עמוד d%", +} diff --git a/nikola/data/themes/base/messages/messages_hi.py b/nikola/data/themes/base/messages/messages_hi.py index afbd8d5..551a9df 100644 --- a/nikola/data/themes/base/messages/messages_hi.py +++ b/nikola/data/themes/base/messages/messages_hi.py @@ -3,10 +3,10 @@ from __future__ import unicode_literals MESSAGES = { "%d min remaining to read": "पढ़ने में %d मिनट बाकी", - "(active)": "", + "(active)": "(सक्रिय)", "Also available in:": "उपलब्ध भाषाएँ:", "Archive": "आर्काइव", - "Authors": "", + "Authors": "लेखक", "Categories": "श्रेणियाँ", "Comments": "टिप्पणियाँ", "LANGUAGE": "हिन्दी", @@ -20,7 +20,7 @@ MESSAGES = { "Original site": "असली साइट", "Posted:": "पोस्टेड:", "Posts about %s": "%s के बारे में पोस्टें", - "Posts by %s": "", + "Posts by %s": "%s की पोस्टें", "Posts for year %s": "साल %s की पोस्टें", "Posts for {month} {day}, {year}": "{day} {month} {year} की पोस्टें", "Posts for {month} {year}": "{month} {year} की पोस्टें", @@ -31,13 +31,14 @@ MESSAGES = { "Read more": "और पढ़िए", "Skip to main content": "मुख्य सामग्री पर जाएँ", "Source": "सोर्स", - "Subcategories:": "", + "Subcategories:": "उपश्रेणी", "Tags and Categories": "टैग्स और श्रेणियाँ", "Tags": "टैग्स", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", + "Toggle navigation": "", + "Uncategorized": "बिना श्रेणी", + "Updates": "अपडेट्स", + "Write your page here.": "अपना पेज यहाँ लिखिए", + "Write your post here.": "अपनी पोस्ट यहाँ लिखिए", "old posts, page %d": "पुरानी पोस्टें, पृष्ठ %d", "page %d": "पृष्ठ %d", } diff --git a/nikola/data/themes/base/messages/messages_hr.py b/nikola/data/themes/base/messages/messages_hr.py index 79bd050..933bafc 100644 --- a/nikola/data/themes/base/messages/messages_hr.py +++ b/nikola/data/themes/base/messages/messages_hr.py @@ -6,7 +6,7 @@ MESSAGES = { "(active)": "(aktivno)", "Also available in:": "Također dostupno i u:", "Archive": "Arhiva", - "Authors": "", + "Authors": "Autori", "Categories": "Kategorije", "Comments": "Komentari", "LANGUAGE": "hrvatski", @@ -20,9 +20,9 @@ MESSAGES = { "Original site": "Izvorna stranica", "Posted:": "Objavljeno:", "Posts about %s": "Postovi o %s", - "Posts by %s": "", + "Posts by %s": "Objave od %s", "Posts for year %s": "Postovi za godinu %s", - "Posts for {month} {day}, {year}": "", + "Posts for {month} {day}, {year}": "Objave za {month} {day}, {year}", "Posts for {month} {year}": "Postovi za {month} {year}", "Previous post": "Prethodni post", "Publication date": "Nadnevak objave", @@ -34,10 +34,11 @@ MESSAGES = { "Subcategories:": "Podkategorije:", "Tags and Categories": "Tagovi i kategorije", "Tags": "Tagovi", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", + "Toggle navigation": "", + "Uncategorized": "Nekategorizirano", + "Updates": "Nadopune", + "Write your page here.": "Napiši svoju stranicu ovdje", + "Write your post here.": "Napiši svoju objavu ovdje", "old posts, page %d": "stari postovi, stranice %d", "page %d": "stranice %d", } diff --git a/nikola/data/themes/base/messages/messages_hu.py b/nikola/data/themes/base/messages/messages_hu.py index e1dc354..0b137fc 100644 --- a/nikola/data/themes/base/messages/messages_hu.py +++ b/nikola/data/themes/base/messages/messages_hu.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Alkategóriák:", "Tags and Categories": "Címkék és kategóriák", "Tags": "Címkék", + "Toggle navigation": "", "Uncategorized": "Nincs kategorizálva", "Updates": "Frissítések", "Write your page here.": "Ide írd az oldalad.", diff --git a/nikola/data/themes/base/messages/messages_id.py b/nikola/data/themes/base/messages/messages_id.py index 3682b5b..ec60f9a 100644 --- a/nikola/data/themes/base/messages/messages_id.py +++ b/nikola/data/themes/base/messages/messages_id.py @@ -6,10 +6,10 @@ MESSAGES = { "(active)": "(aktif)", "Also available in:": "Juga tersedia dalam:", "Archive": "Arsip", - "Authors": "", + "Authors": "Penulis", "Categories": "Kategori", "Comments": "Komentar", - "LANGUAGE": "Inggris", + "LANGUAGE": "Bahasa Indonesia", "Languages:": "Bahasa:", "More posts about %s": "Lebih banyak tulisan tentang %s", "Newer posts": "Tulisan lebih baru", @@ -20,24 +20,25 @@ MESSAGES = { "Original site": "Situs orisinal", "Posted:": "Ditulis oleh:", "Posts about %s": "Tulisan tentang %s", - "Posts by %s": "", + "Posts by %s": "Tulisan oleh %s", "Posts for year %s": "Tulisan untuk tahun %s", "Posts for {month} {day}, {year}": "Tulisan untuk {month} {day}, {year}", "Posts for {month} {year}": "Tulisan untuk {month} {year}", "Previous post": "Tulisan sebelumnya", "Publication date": "Tanggal publikasi", "RSS feed": "Sindikasi RSS", - "Read in English": "Baca dalam bahasa Inggris", + "Read in English": "Baca dalam Bahasa Indonesia", "Read more": "Baca selengkapnya", "Skip to main content": "Lanjutkan ke konten utama", "Source": "Sumber", "Subcategories:": "Sub kategori:", "Tags and Categories": "Tag dan Kategori", "Tags": "Tag", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", + "Toggle navigation": "", + "Uncategorized": "Tanpa kategori", + "Updates": "Update", + "Write your page here.": "Tulis halaman Anda disini.", + "Write your post here.": "Tulis tulisan Anda disini.", "old posts, page %d": "tulisan lama, halaman %d", "page %d": "halaman %d", } diff --git a/nikola/data/themes/base/messages/messages_it.py b/nikola/data/themes/base/messages/messages_it.py index 65481e0..08a65d5 100644 --- a/nikola/data/themes/base/messages/messages_it.py +++ b/nikola/data/themes/base/messages/messages_it.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Sottocategorie:", "Tags and Categories": "Tag e categorie", "Tags": "Tag", + "Toggle navigation": "", "Uncategorized": "Senza categorie", "Updates": "Aggiornamenti", "Write your page here.": "Scrivi qui la tua pagina.", diff --git a/nikola/data/themes/base/messages/messages_ja.py b/nikola/data/themes/base/messages/messages_ja.py index c94c79c..f0d752e 100644 --- a/nikola/data/themes/base/messages/messages_ja.py +++ b/nikola/data/themes/base/messages/messages_ja.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "サブカテゴリ", "Tags and Categories": "カテゴリおよびタグ一覧", "Tags": "タグ", + "Toggle navigation": "", "Uncategorized": "uncategorized", "Updates": "フィード", "Write your page here.": "ここに文書を記述してください。", diff --git a/nikola/data/themes/base/messages/messages_ko.py b/nikola/data/themes/base/messages/messages_ko.py index 7f06c28..9a87aef 100644 --- a/nikola/data/themes/base/messages/messages_ko.py +++ b/nikola/data/themes/base/messages/messages_ko.py @@ -3,10 +3,10 @@ from __future__ import unicode_literals MESSAGES = { "%d min remaining to read": "읽기 %d분 남음.", - "(active)": "", - "Also available in:": "", + "(active)": "(활성됨)", + "Also available in:": "이곳에서도 가능함:", "Archive": "저장소", - "Authors": "", + "Authors": "작성자", "Categories": "분류", "Comments": "댓글", "LANGUAGE": "영어", @@ -18,11 +18,11 @@ MESSAGES = { "Nothing found.": "검색 결과 없음.", "Older posts": "옛날 포스트", "Original site": "출처", - "Posted:": "", + "Posted:": "작성됨:", "Posts about %s": "%s에 대한 포스트", - "Posts by %s": "", + "Posts by %s": "%s에 의해 작성된 글", "Posts for year %s": "%s년도 포스트", - "Posts for {month} {day}, {year}": "", + "Posts for {month} {day}, {year}": " {year}년 {month}월 {day}일에 작성된 포스트", "Posts for {month} {year}": "{year}년 {month}월에 쓴 포스트", "Previous post": "이전 포스트", "Publication date": "발간일", @@ -31,13 +31,14 @@ MESSAGES = { "Read more": "더 읽기", "Skip to main content": "주 콘텐츠로 바로가기", "Source": "원문", - "Subcategories:": "", + "Subcategories:": "서브 카테고리", "Tags and Categories": "태그와 분류", "Tags": "태그", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", + "Toggle navigation": "", + "Uncategorized": "카테고리 없음", + "Updates": "업데이트", + "Write your page here.": "여기에 페이지를 작성하세요.", + "Write your post here.": "이곳에 글을 작성하세요.", "old posts, page %d": "이전 포스트, 페이지 %d", "page %d": "페이지 %d", } diff --git a/nikola/data/themes/base/messages/messages_lt.py b/nikola/data/themes/base/messages/messages_lt.py new file mode 100644 index 0000000..54b61d1 --- /dev/null +++ b/nikola/data/themes/base/messages/messages_lt.py @@ -0,0 +1,44 @@ +# -*- encoding:utf-8 -*- +from __future__ import unicode_literals + +MESSAGES = { + "%d min remaining to read": "liko %d min skaitymo", + "(active)": "(aktyvi)", + "Also available in:": "Taip pat turimas šiomis kalbomis:", + "Archive": "Archyvas", + "Authors": "Autoriai", + "Categories": "Kategorijos", + "Comments": "Komentarai", + "LANGUAGE": "Lietuvių", + "Languages:": "Kalbos:", + "More posts about %s": "Daugiau įrašų apie %s", + "Newer posts": "Naujesni įrašai", + "Next post": "Sekantis įrašas", + "No posts found.": "Įrašų nerasta.", + "Nothing found.": "Nieko nerasta.", + "Older posts": "Senesni įrašai", + "Original site": "Originalo svetainė", + "Posted:": "Įrašyta:", + "Posts about %s": "Įrašai apie %s", + "Posts by %s": "Autoriaus %s įrašai", + "Posts for year %s": "%s metų įrašai", + "Posts for {month} {day}, {year}": "{month} {day}, {year} įrašai", + "Posts for {month} {year}": "{month} {year} įrašai", + "Previous post": "Ankstesnis įrašas", + "Publication date": "Publikavimo data", + "RSS feed": "RSS srautas", + "Read in English": "Skaityti lietuviškai", + "Read more": "Skaityti daugiau", + "Skip to main content": "Pereiti į pagrindinį turinį", + "Source": "Pirminis tekstas", + "Subcategories:": "Subkategorijos:", + "Tags and Categories": "Žymės ir Kategorijos", + "Tags": "Žymės", + "Toggle navigation": "", + "Uncategorized": "Nekategorizuota", + "Updates": "Atnaujinimai", + "Write your page here.": "Čia rašykite puslapio tekstą.", + "Write your post here.": "Čia rašykite įrašo tekstą.", + "old posts, page %d": "seni įrašai, %d puslapis", + "page %d": "%d puslapis", +} diff --git a/nikola/data/themes/base/messages/messages_nb.py b/nikola/data/themes/base/messages/messages_nb.py index fd98f88..8ab0911 100644 --- a/nikola/data/themes/base/messages/messages_nb.py +++ b/nikola/data/themes/base/messages/messages_nb.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Underkategorier:", "Tags and Categories": "Merker og kategorier", "Tags": "Merker", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "Skriv siden din her.", diff --git a/nikola/data/themes/base/messages/messages_nl.py b/nikola/data/themes/base/messages/messages_nl.py index 42becd9..7c5698d 100644 --- a/nikola/data/themes/base/messages/messages_nl.py +++ b/nikola/data/themes/base/messages/messages_nl.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Subcategorieën", "Tags and Categories": "Tags en Categorieën", "Tags": "Tags", + "Toggle navigation": "Toggle navigatie", "Uncategorized": "Ongeordend", "Updates": "Bijgewerkte versies", "Write your page here.": "Schrijf hier je pagina.", diff --git a/nikola/data/themes/base/messages/messages_pa.py b/nikola/data/themes/base/messages/messages_pa.py index 13996ac..5eb76f1 100644 --- a/nikola/data/themes/base/messages/messages_pa.py +++ b/nikola/data/themes/base/messages/messages_pa.py @@ -6,10 +6,10 @@ MESSAGES = { "(active)": "(ਚਲੰਤ)", "Also available in:": "ਹੋਰ ਉਪਲਬਧ ਬੋਲੀਆਂ:", "Archive": "ਆਰਕਾਈਵ", - "Authors": "", + "Authors": "ਲੇਖਕ", "Categories": "ਸ਼੍ਰੇਣੀ", "Comments": "ਟਿੱਪਣੀਆਂ", - "LANGUAGE": "ਅੰਗਰੇਜ਼ੀ", + "LANGUAGE": "ਪੰਜਾਬੀ ", "Languages:": "ਬੋਲੀਆਂ:", "More posts about %s": "%s ਬਾਰੇ ਹੋਰ ਲਿਖਤਾਂ", "Newer posts": "ਨਵੀਆਂ ਲਿਖਤਾਂ", @@ -20,22 +20,23 @@ MESSAGES = { "Original site": "ਅਸਲ ਸਾਈਟ", "Posted:": "ਲਿਖਤ ਛਪੀ:", "Posts about %s": "%s ਬਾਰੇ ਲਿਖਤਾਂ", - "Posts by %s": "", + "Posts by %s": "%s ਦੀ ਲਿਖਤਾਂ", "Posts for year %s": "ਸਾਲ %s ਦੀਆਂ ਲਿਖਤਾਂ", "Posts for {month} {day}, {year}": "{day} {month} {year} ਦੀਆਂ ਲਿਖਤਾਂ", "Posts for {month} {year}": "{month} {year} ਦੀਆਂ ਲਿਖਤਾਂ", "Previous post": "ਪਿਛਲੀ ਲਿਖਤ", "Publication date": "ਛਪਾਈ ਦੀ ਤਰੀਕ", "RSS feed": "ਆਰ ਐੱਸ ਐੱਸ ਫੀਡ", - "Read in English": "ਅੰਗਰੇਜ਼ੀ ਵਿੱਚ ਪੜ੍ਹੋ", + "Read in English": "ਪੰਜਾਬੀ ਵਿੱਚ ਪੜ੍ਹੋ", "Read more": "ਹੋਰ ਪੜ੍ਹੋ", "Skip to main content": "ਮੁੱਖ ਸਮੱਗਰੀ ਵੱਲ ਜਾਓ", "Source": "ਮੂਲ", "Subcategories:": "ਉਪਸ਼੍ਰੇਣੀਆਂ:", "Tags and Categories": "ਟੈਗ ਅਤੇ ਸ਼੍ਰੇਣੀਆਂ", "Tags": "ਟੈਗ", - "Uncategorized": "", - "Updates": "", + "Toggle navigation": "", + "Uncategorized": "ਇਤਾਹਾਸ", + "Updates": "ਅੱਪਡੇਟਸ", "Write your page here.": "ਆਪਣਾ ਸਫ਼ਾ ਏਥੇ ਲਿਖੋ |", "Write your post here.": "ਆਪਣੀ ਲਿਖਤ ਏਥੇ ਲਿਖੋ |", "old posts, page %d": "ਪੁਰਾਣੀਆਂ ਲਿਖਤਾਂ , ਸਫ਼ਾ %d", diff --git a/nikola/data/themes/base/messages/messages_pl.py b/nikola/data/themes/base/messages/messages_pl.py index 3e24bfc..257a31a 100644 --- a/nikola/data/themes/base/messages/messages_pl.py +++ b/nikola/data/themes/base/messages/messages_pl.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Podkategorie:", "Tags and Categories": "Tagi i Kategorie", "Tags": "Tagi", + "Toggle navigation": "Pokaż/ukryj menu", "Uncategorized": "Nieskategoryzowane", "Updates": "Aktualności", "Write your page here.": "Tu wpisz treść strony.", diff --git a/nikola/data/themes/base/messages/messages_pt.py b/nikola/data/themes/base/messages/messages_pt.py index 3c4bd55..4a184bd 100644 --- a/nikola/data/themes/base/messages/messages_pt.py +++ b/nikola/data/themes/base/messages/messages_pt.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Sub-Categorias:", "Tags and Categories": "Etiquetas e Categorias", "Tags": "Etiqueta", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "Escreva a sua página aqui.", diff --git a/nikola/data/themes/base/messages/messages_pt_br.py b/nikola/data/themes/base/messages/messages_pt_br.py index 7dcc8a8..b2877e4 100644 --- a/nikola/data/themes/base/messages/messages_pt_br.py +++ b/nikola/data/themes/base/messages/messages_pt_br.py @@ -3,10 +3,10 @@ from __future__ import unicode_literals MESSAGES = { "%d min remaining to read": "%d mín restante para leitura", - "(active)": "", + "(active)": "(ativo)", "Also available in:": "Também disponível em:", "Archive": "Arquivo", - "Authors": "", + "Authors": "Autores", "Categories": "Categorias", "Comments": "Comentários", "LANGUAGE": "Português", @@ -20,7 +20,7 @@ MESSAGES = { "Original site": "Site original", "Posted:": "Publicado:", "Posts about %s": "Posts sobre %s", - "Posts by %s": "", + "Posts by %s": "Postado por %s", "Posts for year %s": "Posts do ano %s", "Posts for {month} {day}, {year}": "Posts do {day} {month}, {year}", "Posts for {month} {year}": "Posts de {month} {year}", @@ -31,13 +31,14 @@ MESSAGES = { "Read more": "Leia mais", "Skip to main content": "Pular para o conteúdo principal", "Source": "Código", - "Subcategories:": "", + "Subcategories:": "Subcategorias:", "Tags and Categories": "Tags e Categorias", "Tags": "Tags", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", + "Toggle navigation": "Alternar navegação", + "Uncategorized": "Sem categoria", + "Updates": "Atualizações", + "Write your page here.": "Insira a sua página aqui", + "Write your post here.": "Escreva o seu comentário aqui.", "old posts, page %d": "Posts antigos, página %d", "page %d": "página %d", } diff --git a/nikola/data/themes/base/messages/messages_ru.py b/nikola/data/themes/base/messages/messages_ru.py index bdf3873..0070b94 100644 --- a/nikola/data/themes/base/messages/messages_ru.py +++ b/nikola/data/themes/base/messages/messages_ru.py @@ -6,7 +6,7 @@ MESSAGES = { "(active)": "(активная)", "Also available in:": "Также доступно на:", "Archive": "Архив", - "Authors": "", + "Authors": "Разработчики", "Categories": "Категории", "Comments": "Комментарии", "LANGUAGE": "Русский", @@ -20,7 +20,7 @@ MESSAGES = { "Original site": "Оригинальный сайт", "Posted:": "Опубликовано:", "Posts about %s": "Записи о %s", - "Posts by %s": "", + "Posts by %s": "Запись %s", "Posts for year %s": "Записи за %s год", "Posts for {month} {day}, {year}": "Записи за {day} {month} {year}", "Posts for {month} {year}": "Записи за {month} {year}", @@ -34,8 +34,9 @@ MESSAGES = { "Subcategories:": "Подкатегории:", "Tags and Categories": "Тэги и категории", "Tags": "Тэги", - "Uncategorized": "", - "Updates": "", + "Toggle navigation": "", + "Uncategorized": "Несортированное", + "Updates": "Обновления", "Write your page here.": "Создайте Вашу страницу здесь.", "Write your post here.": "Создайте Вашу запись здесь.", "old posts, page %d": "%d страница со старыми записями", diff --git a/nikola/data/themes/base/messages/messages_si_lk.py b/nikola/data/themes/base/messages/messages_si_lk.py index f661c0e..6107c54 100644 --- a/nikola/data/themes/base/messages/messages_si_lk.py +++ b/nikola/data/themes/base/messages/messages_si_lk.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "", "Tags and Categories": "", "Tags": "", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "", diff --git a/nikola/data/themes/base/messages/messages_sk.py b/nikola/data/themes/base/messages/messages_sk.py index 03cce43..7b7df6f 100644 --- a/nikola/data/themes/base/messages/messages_sk.py +++ b/nikola/data/themes/base/messages/messages_sk.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Podkategórie:", "Tags and Categories": "Štítky a kategórie", "Tags": "Štítky", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "Tu napíšte svoju stránku.", diff --git a/nikola/data/themes/base/messages/messages_sl.py b/nikola/data/themes/base/messages/messages_sl.py index a531ca5..31f3a58 100644 --- a/nikola/data/themes/base/messages/messages_sl.py +++ b/nikola/data/themes/base/messages/messages_sl.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "", "Tags and Categories": "Značke in kategorije", "Tags": "Značke", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "", diff --git a/nikola/data/themes/base/messages/messages_sq.py b/nikola/data/themes/base/messages/messages_sq.py new file mode 100644 index 0000000..6d5e39a --- /dev/null +++ b/nikola/data/themes/base/messages/messages_sq.py @@ -0,0 +1,44 @@ +# -*- encoding:utf-8 -*- +from __future__ import unicode_literals + +MESSAGES = { + "%d min remaining to read": "%d min ngelen për tu lexuar", + "(active)": "(aktiv)", + "Also available in:": "Gjithashtu e disponueshme në:", + "Archive": "Arkiva", + "Authors": "Autorë", + "Categories": "Kategori", + "Comments": "Komente", + "LANGUAGE": "Shqip", + "Languages:": "Gjuhë:", + "More posts about %s": "Më shumë postime rreth %s", + "Newer posts": "Postime më të reja", + "Next post": "Postimi i rradhës", + "No posts found.": "Nuk është gjetur asnjë post.", + "Nothing found.": "Nuk është gjetur asgjë.", + "Older posts": "Postime më të vjetra", + "Original site": "Faqja origjinale", + "Posted:": "Postuar:", + "Posts about %s": "Postime rreth %s", + "Posts by %s": "Postime nga %s", + "Posts for year %s": "Postime për vitin %s", + "Posts for {month} {day}, {year}": "Postime për {month} {day}, {year}", + "Posts for {month} {year}": "Postime për {month} {year}", + "Previous post": "Postim i kaluar", + "Publication date": "Data e publikimit", + "RSS feed": "Furnizim RSS", + "Read in English": "Lexo në Shqip", + "Read more": "Lexo më shumë", + "Skip to main content": "Shko tek përmbajtja kryesore", + "Source": "Burim", + "Subcategories:": "Nënkategori:", + "Tags and Categories": "Etiketa dhe Kategori", + "Tags": "Etiketa", + "Toggle navigation": "", + "Uncategorized": "E pa kategorizuar", + "Updates": "Përditësime", + "Write your page here.": "Shkruaj faqen tënde këtu.", + "Write your post here.": "Shkruaj postin tënd këtu.", + "old posts, page %d": "Postime të kaluara, faqe %d", + "page %d": "faqe %d", +} diff --git a/nikola/data/themes/base/messages/messages_sr.py b/nikola/data/themes/base/messages/messages_sr.py index 13137c3..03f1024 100644 --- a/nikola/data/themes/base/messages/messages_sr.py +++ b/nikola/data/themes/base/messages/messages_sr.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Подкатегорије:", "Tags and Categories": "Тагови и категорије", "Tags": "Тагови", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "Вашу страницу напишите овдје.", diff --git a/nikola/data/themes/base/messages/messages_sr_latin.py b/nikola/data/themes/base/messages/messages_sr_latin.py index db60c76..f7f3727 100644 --- a/nikola/data/themes/base/messages/messages_sr_latin.py +++ b/nikola/data/themes/base/messages/messages_sr_latin.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Podkategorije:", "Tags and Categories": "Oznake i kategorije", "Tags": "Oznake", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "Vašu stranicu napišite ovdje.", diff --git a/nikola/data/themes/base/messages/messages_sv.py b/nikola/data/themes/base/messages/messages_sv.py index d1357e7..106138f 100644 --- a/nikola/data/themes/base/messages/messages_sv.py +++ b/nikola/data/themes/base/messages/messages_sv.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "Underkategorier:", "Tags and Categories": "Taggar och Kategorier", "Tags": "Taggar", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "Skriv din sida här.", diff --git a/nikola/data/themes/base/messages/messages_te.py b/nikola/data/themes/base/messages/messages_te.py new file mode 100644 index 0000000..70d8150 --- /dev/null +++ b/nikola/data/themes/base/messages/messages_te.py @@ -0,0 +1,44 @@ +# -*- encoding:utf-8 -*- +from __future__ import unicode_literals + +MESSAGES = { + "%d min remaining to read": "%d నిమిషాలు చదవడానికి కావలెను ", + "(active)": "(క్రియాశీల)", + "Also available in:": "ఇందులో కూడా లభించును:", + "Archive": "అభిలేఖలు ", + "Authors": "రచయితలు", + "Categories": "వర్గాలు", + "Comments": "వ్యాఖ్యలు", + "LANGUAGE": "ఆంగ్లం ", + "Languages:": "భాషలు:", + "More posts about %s": "%s గూర్చి మరిన్ని టపాలు", + "Newer posts": "కొత్త టపాలు", + "Next post": "తరువాత టపా", + "No posts found.": "", + "Nothing found.": "", + "Older posts": "పాత టపాలు", + "Original site": "వాస్తవ సైట్", + "Posted:": "ప్రచురుంచిన తేదీ:", + "Posts about %s": "%s గూర్చి టపాలు", + "Posts by %s": "%s యొక్క టపాలు", + "Posts for year %s": "%s సంవత్సర టపాలు", + "Posts for {month} {day}, {year}": "", + "Posts for {month} {year}": "", + "Previous post": "మునుపటి టపా", + "Publication date": "ప్రచురణ తేదీ", + "RSS feed": "RSS ఫీడ్", + "Read in English": "ఆంగ్లంలో చదవండి", + "Read more": "ఇంకా చదవండి", + "Skip to main content": "ప్రధాన విషయానికి వెళ్ళు", + "Source": "మూలం", + "Subcategories:": "ఉపవర్గాలు:", + "Tags and Categories": "ట్యాగ్లు మరియు వర్గాలు", + "Tags": "ట్యాగ్లు", + "Toggle navigation": "", + "Uncategorized": "వర్గీకరించని", + "Updates": "నవీకరణలు", + "Write your page here.": "మీ పేజీ ఇక్కడ రాయండి.", + "Write your post here.": "ఇక్కడ మీ టపా ను వ్రాయండి.", + "old posts, page %d": "పాత టపాలు, పేజీ %d", + "page %d": "పేజీ %d", +} diff --git a/nikola/data/themes/base/messages/messages_tl.py b/nikola/data/themes/base/messages/messages_tl.py index 303ea36..1b85eb4 100644 --- a/nikola/data/themes/base/messages/messages_tl.py +++ b/nikola/data/themes/base/messages/messages_tl.py @@ -34,6 +34,7 @@ MESSAGES = { "Subcategories:": "", "Tags and Categories": "", "Tags": "Mga Tag", + "Toggle navigation": "", "Uncategorized": "", "Updates": "", "Write your page here.": "", diff --git a/nikola/data/themes/base/messages/messages_tr.py b/nikola/data/themes/base/messages/messages_tr.py index e173a40..e09dcb8 100644 --- a/nikola/data/themes/base/messages/messages_tr.py +++ b/nikola/data/themes/base/messages/messages_tr.py @@ -3,15 +3,15 @@ from __future__ import unicode_literals MESSAGES = { "%d min remaining to read": "%d dakikalık okuma", - "(active)": "", + "(active)": "(etkin)", "Also available in:": "Şu dilde de mevcut:", "Archive": "Arşiv", - "Authors": "", + "Authors": "Yazarlar", "Categories": "Kategoriler", "Comments": "Yorumlar", "LANGUAGE": "Türkçe", "Languages:": "Diller:", - "More posts about %s": "%s ilgili diğer yazılar", + "More posts about %s": "%s hakkında diğer yazılar", "Newer posts": "Daha yeni yazılar", "Next post": "Sonraki yazı", "No posts found.": "Yazı bulunamadı.", @@ -20,9 +20,9 @@ MESSAGES = { "Original site": "Orjinal web sayfası", "Posted:": "Yayın tarihi:", "Posts about %s": "%s ile ilgili yazılar", - "Posts by %s": "", + "Posts by %s": "%s tarafından yazılanlar", "Posts for year %s": "%s yılındaki yazılar", - "Posts for {month} {day}, {year}": "{month} {day}, {year} 'den beri olan yazılar", + "Posts for {month} {day}, {year}": "{month} {day}, {year} tarihinden itibaren yazılar", "Posts for {month} {year}": "{month} {year} göre yazılar", "Previous post": "Önceki yazı", "Publication date": "Yayınlanma tarihi", @@ -31,13 +31,14 @@ MESSAGES = { "Read more": "Devamını oku", "Skip to main content": "Ana içeriğe geç", "Source": "Kaynak", - "Subcategories:": "", + "Subcategories:": "Alt kategoriler:", "Tags and Categories": "Etiketler ve Kategoriler", "Tags": "Etiketler", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", + "Toggle navigation": "", + "Uncategorized": "Kategorisiz", + "Updates": "Güncellemeler", + "Write your page here.": "Sayfanızı buraya yazın.", + "Write your post here.": "Yazınızı buraya yazın.", "old posts, page %d": "eski yazılar, sayfa %d", "page %d": "sayfa %d", } diff --git a/nikola/data/themes/base/messages/messages_uk.py b/nikola/data/themes/base/messages/messages_uk.py index 4f19568..327a5d9 100644 --- a/nikola/data/themes/base/messages/messages_uk.py +++ b/nikola/data/themes/base/messages/messages_uk.py @@ -6,7 +6,7 @@ MESSAGES = { "(active)": "(активне)", "Also available in:": "Іншою мовою:", "Archive": "Архів", - "Authors": "", + "Authors": "Автори", "Categories": "Категорії", "Comments": "Коментарі", "LANGUAGE": "Українська", @@ -20,7 +20,7 @@ MESSAGES = { "Original site": "Оригінал сайту", "Posted:": "Опублікована:", "Posts about %s": "Статті про %s", - "Posts by %s": "", + "Posts by %s": "Статті %s", "Posts for year %s": "Статті за %s рік", "Posts for {month} {day}, {year}": "Статті за {month} {day}, {year}", "Posts for {month} {year}": "Статті за {month} {year}", @@ -34,8 +34,9 @@ MESSAGES = { "Subcategories:": "Підкатегорії:", "Tags and Categories": "Теги і категорії", "Tags": "Теги", - "Uncategorized": "", - "Updates": "", + "Toggle navigation": "", + "Uncategorized": "Без категорії", + "Updates": "Підписки", "Write your page here.": "Напишіть Вашу сторінку тут.", "Write your post here.": "Напишить Вашу статтю тут.", "old posts, page %d": "старі статті, сторінка %d", diff --git a/nikola/data/themes/base/messages/messages_ur.py b/nikola/data/themes/base/messages/messages_ur.py index 92cdccd..055f72e 100644 --- a/nikola/data/themes/base/messages/messages_ur.py +++ b/nikola/data/themes/base/messages/messages_ur.py @@ -34,7 +34,8 @@ MESSAGES = { "Subcategories:": "ذیلی زمرے", "Tags and Categories": "ٹیگز اور زمرے", "Tags": "ٹیگز", - "Uncategorized": "", + "Toggle navigation": "", + "Uncategorized": "بے زمرہ", "Updates": "تازہ ترین", "Write your page here.": "اپنے صفحے کا متن یہاں لکھیں۔", "Write your post here.": "اپنی تحریر یہاں لکھیں۔", diff --git a/nikola/data/themes/base/messages/messages_zh_cn.py b/nikola/data/themes/base/messages/messages_zh_cn.py index 888b50e..84e4317 100644 --- a/nikola/data/themes/base/messages/messages_zh_cn.py +++ b/nikola/data/themes/base/messages/messages_zh_cn.py @@ -2,42 +2,43 @@ from __future__ import unicode_literals MESSAGES = { - "%d min remaining to read": "", - "(active)": "", + "%d min remaining to read": "剩余 %d 分钟去阅读", + "(active)": "(活跃)", "Also available in:": "其他语言版本:", "Archive": "文章存档", - "Authors": "", + "Authors": "作者", "Categories": "分类", - "Comments": "", + "Comments": "注释", "LANGUAGE": "简体中文", - "Languages:": "", - "More posts about %s": "更多相关文章: %s", + "Languages:": "语言:", + "More posts about %s": "更多 %s 相关文章", "Newer posts": "新一篇", "Next post": "后一篇", - "No posts found.": "", - "Nothing found.": "", + "No posts found.": "没有找到文章", + "Nothing found.": "没有找到。", "Older posts": "旧一篇", "Original site": "原文地址", "Posted:": "发表于:", "Posts about %s": "文章分类:%s", - "Posts by %s": "", + "Posts by %s": "%s 发布", "Posts for year %s": "%s年文章", - "Posts for {month} {day}, {year}": "", + "Posts for {month} {day}, {year}": "{year}年{month}月{day}日文章", "Posts for {month} {year}": "{year}年{month}月文章", "Previous post": "前一篇", - "Publication date": "", - "RSS feed": "", + "Publication date": "发布日期", + "RSS feed": "RSS 源", "Read in English": "中文版", "Read more": "更多", - "Skip to main content": "", + "Skip to main content": "跳到主内容", "Source": "源代码", - "Subcategories:": "", + "Subcategories:": "子类别:", "Tags and Categories": "标签和分类", "Tags": "标签", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", + "Toggle navigation": "", + "Uncategorized": "未分类", + "Updates": "更新", + "Write your page here.": "在这里书写你的页面。", + "Write your post here.": "在这里书写你的文章。", "old posts, page %d": "旧文章页 %d", - "page %d": "", + "page %d": "第 %d 页", } diff --git a/nikola/data/themes/base/messages/messages_zh_tw.py b/nikola/data/themes/base/messages/messages_zh_tw.py index f661c0e..e6de0ff 100644 --- a/nikola/data/themes/base/messages/messages_zh_tw.py +++ b/nikola/data/themes/base/messages/messages_zh_tw.py @@ -2,42 +2,43 @@ from __future__ import unicode_literals MESSAGES = { - "%d min remaining to read": "", - "(active)": "", - "Also available in:": "", - "Archive": "", - "Authors": "", - "Categories": "", - "Comments": "", - "LANGUAGE": "", - "Languages:": "", - "More posts about %s": "", - "Newer posts": "", - "Next post": "", - "No posts found.": "", - "Nothing found.": "", - "Older posts": "", - "Original site": "", - "Posted:": "", - "Posts about %s": "", - "Posts by %s": "", - "Posts for year %s": "", - "Posts for {month} {day}, {year}": "", - "Posts for {month} {year}": "", - "Previous post": "", - "Publication date": "", - "RSS feed": "", - "Read in English": "", - "Read more": "", - "Skip to main content": "", - "Source": "", - "Subcategories:": "", - "Tags and Categories": "", - "Tags": "", - "Uncategorized": "", - "Updates": "", - "Write your page here.": "", - "Write your post here.": "", - "old posts, page %d": "", - "page %d": "", + "%d min remaining to read": "尚餘 %d 分鐘", + "(active)": "(啟用)", + "Also available in:": "其他語言版本:", + "Archive": "彙整", + "Authors": "作者", + "Categories": "分類", + "Comments": "迴響", + "LANGUAGE": "繁體中文", + "Languages:": "語言:", + "More posts about %s": "更多 %s 的文章", + "Newer posts": "較新的文章", + "Next post": "下一篇", + "No posts found.": "沒有找到文章。", + "Nothing found.": "沒有找到。", + "Older posts": "較舊的文章", + "Original site": "原始網站", + "Posted:": "發佈於:", + "Posts about %s": "文章分類:%s", + "Posts by %s": "%s 發佈", + "Posts for year %s": "%s 年的文章", + "Posts for {month} {day}, {year}": "{year}年{month}月{day}日的文章", + "Posts for {month} {year}": "{year}年{month}月的文章", + "Previous post": "上一篇", + "Publication date": "發佈日期", + "RSS feed": "RSS 訂閱", + "Read in English": "繁體中文版", + "Read more": "閱讀更多", + "Skip to main content": "略過主內容", + "Source": "原始碼", + "Subcategories:": "子分類", + "Tags and Categories": "標籤和分類", + "Tags": "標籤", + "Toggle navigation": "切換導航", + "Uncategorized": "未分類", + "Updates": "更新", + "Write your page here.": "從這裡開始編輯頁面", + "Write your post here.": "從這裡開始編輯文章", + "old posts, page %d": "舊文章,第 %d 頁", + "page %d": "第 %d 頁", } diff --git a/nikola/data/themes/base/templates/author.tmpl b/nikola/data/themes/base/templates/author.tmpl index 3ad5140..21d8d64 100644 --- a/nikola/data/themes/base/templates/author.tmpl +++ b/nikola/data/themes/base/templates/author.tmpl @@ -35,7 +35,7 @@ %if posts: <ul class="postlist"> % for post in posts: - <li><a href="${post.permalink()}" class="listtitle">${post.title()|h}</a><time class="listdate" datetime="${post.formatted_date('webiso')}" title="${post.formatted_date(date_format)|h}">${post.formatted_date(date_format)|h}</time></li> + <li><time class="listdate" datetime="${post.formatted_date('webiso')}" title="${post.formatted_date(date_format)|h}">${post.formatted_date(date_format)|h}</time> <a href="${post.permalink()}" class="listtitle">${post.title()|h}</a></li> % endfor </ul> %endif diff --git a/nikola/data/themes/base/templates/base_helper.tmpl b/nikola/data/themes/base/templates/base_helper.tmpl index 35ed983..e2ffab2 100644 --- a/nikola/data/themes/base/templates/base_helper.tmpl +++ b/nikola/data/themes/base/templates/base_helper.tmpl @@ -58,7 +58,7 @@ lang="${lang}"> ${mathjax_config} %if use_cdn: - <!--[if lt IE 9]><script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]--> + <!--[if lt IE 9]><script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]--> %else: <!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang)}"></script><![endif]--> %endif diff --git a/nikola/data/themes/base/templates/comments_helper_disqus.tmpl b/nikola/data/themes/base/templates/comments_helper_disqus.tmpl index 6dd423c..b842871 100644 --- a/nikola/data/themes/base/templates/comments_helper_disqus.tmpl +++ b/nikola/data/themes/base/templates/comments_helper_disqus.tmpl @@ -21,12 +21,12 @@ }; (function() { var dsq = document.createElement('script'); dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; + dsq.src = 'https://' + disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); })(); </script> - <noscript>Please enable JavaScript to view the <a href="//disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript> - <a href="//disqus.com" class="dsq-brlink" rel="nofollow">Comments powered by <span class="logo-disqus">Disqus</span></a> + <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript> + <a href="https://disqus.com" class="dsq-brlink" rel="nofollow">Comments powered by <span class="logo-disqus">Disqus</span></a> %endif </%def> @@ -39,6 +39,6 @@ <%def name="comment_link_script()"> %if comment_system_id: - <script>var disqus_shortname="${comment_system_id}";(function(){var a=document.createElement("script");a.async=true;a.src="//"+disqus_shortname+".disqus.com/count.js";(document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]).appendChild(a)}());</script> + <script>var disqus_shortname="${comment_system_id}";(function(){var a=document.createElement("script");a.async=true;a.src="https://"+disqus_shortname+".disqus.com/count.js";(document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]).appendChild(a)}());</script> %endif </%def> diff --git a/nikola/data/themes/base/templates/comments_helper_facebook.tmpl b/nikola/data/themes/base/templates/comments_helper_facebook.tmpl index d6059a1..815f489 100644 --- a/nikola/data/themes/base/templates/comments_helper_facebook.tmpl +++ b/nikola/data/themes/base/templates/comments_helper_facebook.tmpl @@ -17,7 +17,7 @@ var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) {return;} js = d.createElement(s); js.id = id; - js.src = "//connect.facebook.net/en_US/all.js"; + js.src = "https://connect.facebook.net/en_US/all.js"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk')); </script> @@ -55,7 +55,7 @@ var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) {return;} js = d.createElement(s); js.id = id; - js.src = "//connect.facebook.net/en_US/all.js"; + js.src = "https://connect.facebook.net/en_US/all.js"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk')); </script> diff --git a/nikola/data/themes/base/templates/comments_helper_isso.tmpl b/nikola/data/themes/base/templates/comments_helper_isso.tmpl index 8dc95f5..95f93ae 100644 --- a/nikola/data/themes/base/templates/comments_helper_isso.tmpl +++ b/nikola/data/themes/base/templates/comments_helper_isso.tmpl @@ -14,7 +14,7 @@ <%def name="comment_link_script()"> - %if comment_system_id: + %if comment_system_id and 'index' in pagekind: <script src="${comment_system_id}js/count.min.js" data-isso="${comment_system_id}"></script> %endif </%def> diff --git a/nikola/data/themes/base/templates/comments_helper_muut.tmpl b/nikola/data/themes/base/templates/comments_helper_muut.tmpl index 94532d9..e5e7c05 100644 --- a/nikola/data/themes/base/templates/comments_helper_muut.tmpl +++ b/nikola/data/themes/base/templates/comments_helper_muut.tmpl @@ -9,5 +9,5 @@ <%def name="comment_link_script()"> -<script src="//cdn.muut.com/1/moot.min.js"></script> +<script src="https://cdn.muut.com/1/moot.min.js"></script> </%def> diff --git a/nikola/data/themes/base/templates/gallery.tmpl b/nikola/data/themes/base/templates/gallery.tmpl index 126d51c..f9bbd1b 100644 --- a/nikola/data/themes/base/templates/gallery.tmpl +++ b/nikola/data/themes/base/templates/gallery.tmpl @@ -17,7 +17,7 @@ %if folders: <ul> % for folder, ftitle in folders: - <li><a href="${folder|u}"><i + <li><a href="${folder}"><i class="icon-folder-open"></i> ${ftitle|h}</a></li> % endfor </ul> diff --git a/nikola/data/themes/base/templates/index.tmpl b/nikola/data/themes/base/templates/index.tmpl index 294bdaf..f74d2e4 100644 --- a/nikola/data/themes/base/templates/index.tmpl +++ b/nikola/data/themes/base/templates/index.tmpl @@ -12,6 +12,9 @@ <%block name="content"> <%block name="content_header"></%block> +% if 'main_index' in pagekind: + ${front_index_header} +% endif <div class="postindex"> % for post in posts: <article class="h-entry post-${post.meta('type')}"> diff --git a/nikola/data/themes/base/templates/index_helper.tmpl b/nikola/data/themes/base/templates/index_helper.tmpl index fae046d..0e98016 100644 --- a/nikola/data/themes/base/templates/index_helper.tmpl +++ b/nikola/data/themes/base/templates/index_helper.tmpl @@ -21,16 +21,30 @@ <%def name="mathjax_script(posts)"> %if any(post.is_mathjax for post in posts): %if use_katex: - <script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/katex.min.js"></script> - <script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/contrib/auto-render.min.js"></script> - <script> - renderMathInElement(document.body); - </script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/katex.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/contrib/auto-render.min.js"></script> + % if katex_auto_render: + <script> + renderMathInElement(document.body, + { + ${katex_auto_render} + } + ); + </script> + % else: + <script> + renderMathInElement(document.body); + </script> + % endif %else: - <script type="text/javascript" src="//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> </script> + <script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> </script> + % if mathjax_config: + ${mathjax_config} + % else: <script type="text/x-mathjax-config"> MathJax.Hub.Config({tex2jax: {inlineMath: [['$latex ','$'], ['\\(','\\)']]}}); </script> + % endif %endif %endif </%def> diff --git a/nikola/data/themes/base/templates/list_post.tmpl b/nikola/data/themes/base/templates/list_post.tmpl index 5fd4df1..bc52385 100644 --- a/nikola/data/themes/base/templates/list_post.tmpl +++ b/nikola/data/themes/base/templates/list_post.tmpl @@ -9,7 +9,7 @@ %if posts: <ul class="postlist"> % for post in posts: - <li><a href="${post.permalink()}" class="listtitle">${post.title()|h}</a> <time class="listdate" datetime="${post.formatted_date('webiso')}" title="${post.formatted_date(date_format)|h}">${post.formatted_date(date_format)|h}</time></li> + <li><time class="listdate" datetime="${post.formatted_date('webiso')}" title="${post.formatted_date(date_format)|h}">${post.formatted_date(date_format)|h}</time> <a href="${post.permalink()}" class="listtitle">${post.title()|h}</a></li> % endfor </ul> %else: diff --git a/nikola/data/themes/base/templates/listing.tmpl b/nikola/data/themes/base/templates/listing.tmpl index 1800bd9..fae7607 100644 --- a/nikola/data/themes/base/templates/listing.tmpl +++ b/nikola/data/themes/base/templates/listing.tmpl @@ -14,10 +14,13 @@ ${ui.bar(crumbs)} </ul> %endif % if code: + <h1>${title} + % if source_link: + <small><a href="${source_link}">(${messages("Source")})</a></small> + % endif + </h1> ${code} % endif -% if source_link: - <p class="sourceline"><a href="${source_link}" id="sourcelink">${messages("Source")}</a></p> -% endif </%block> + diff --git a/nikola/data/themes/base/templates/post_helper.tmpl b/nikola/data/themes/base/templates/post_helper.tmpl index f8572ec..47bf9b3 100644 --- a/nikola/data/themes/base/templates/post_helper.tmpl +++ b/nikola/data/themes/base/templates/post_helper.tmpl @@ -3,7 +3,7 @@ <%def name="meta_translations(post)"> %if len(translations) > 1: %for langname in sorted(translations): - %if langname != lang and post.is_translation_available(langname): + %if langname != lang and ((not post.skip_untranslated) or post.is_translation_available(langname)): <link rel="alternate" hreflang="${langname}" href="${post.permalink(langname)}"> %endif %endfor @@ -87,16 +87,30 @@ <%def name="mathjax_script(post)"> %if post.is_mathjax: %if use_katex: - <script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/katex.min.js"></script> - <script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.3.0/contrib/auto-render.min.js"></script> - <script> - renderMathInElement(document.body); - </script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/katex.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/contrib/auto-render.min.js"></script> + % if katex_auto_render: + <script> + renderMathInElement(document.body, + { + ${katex_auto_render} + } + ); + </script> + % else: + <script> + renderMathInElement(document.body); + </script> + % endif %else: - <script type="text/javascript" src="//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> </script> + <script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> </script> + % if mathjax_config: + ${mathjax_config} + % else: <script type="text/x-mathjax-config"> MathJax.Hub.Config({tex2jax: {inlineMath: [['$latex ','$'], ['\\(','\\)']]}}); </script> + % endif %endif %endif </%def> diff --git a/nikola/data/themes/base/templates/post_list_directive.tmpl b/nikola/data/themes/base/templates/post_list_directive.tmpl index 4a136a1..4a1cf69 100644 --- a/nikola/data/themes/base/templates/post_list_directive.tmpl +++ b/nikola/data/themes/base/templates/post_list_directive.tmpl @@ -6,10 +6,10 @@ <ul class="post-list"> % for post in posts: <li class="post-list-item"> - ${post.formatted_date(date_format)|h} - - <a href="${post.permalink(lang)}">${post.title(lang)|h}</a> - </li> + ${post.formatted_date(date_format)|h} + + <a href="${post.permalink(lang)}">${post.title(lang)|h}</a> + </li> % endfor </ul> %endif diff --git a/nikola/data/themes/base/templates/story.tmpl b/nikola/data/themes/base/templates/story.tmpl index 2737c4d..b8fb7ed 100644 --- a/nikola/data/themes/base/templates/story.tmpl +++ b/nikola/data/themes/base/templates/story.tmpl @@ -5,7 +5,7 @@ <%inherit file="post.tmpl"/> <%block name="content"> -<article class="storypage" itemscope="itemscope" itemtype="http://schema.org/Article"> +<article class="post-${post.meta('type')} storypage" itemscope="itemscope" itemtype="http://schema.org/Article"> <header> ${pheader.html_title()} ${pheader.html_translations(post)} diff --git a/nikola/data/themes/base/templates/tag.tmpl b/nikola/data/themes/base/templates/tag.tmpl index d07c434..50c5bf2 100644 --- a/nikola/data/themes/base/templates/tag.tmpl +++ b/nikola/data/themes/base/templates/tag.tmpl @@ -43,7 +43,7 @@ %if posts: <ul class="postlist"> % for post in posts: - <li><time class="listdate" datetime="${post.formatted_date('webiso')}" title="${post.formatted_date(date_format)|h}">${post.formatted_date(date_format)|h}</time><a href="${post.permalink()}" class="listtitle">${post.title()|h}<a></li> + <li><time class="listdate" datetime="${post.formatted_date('webiso')}" title="${post.formatted_date(date_format)|h}">${post.formatted_date(date_format)|h}</time> <a href="${post.permalink()}" class="listtitle">${post.title()|h}<a></li> % endfor </ul> %endif diff --git a/nikola/data/themes/base/templates/tagindex.tmpl b/nikola/data/themes/base/templates/tagindex.tmpl index 047d638..c3c51b0 100644 --- a/nikola/data/themes/base/templates/tagindex.tmpl +++ b/nikola/data/themes/base/templates/tagindex.tmpl @@ -2,14 +2,20 @@ <%inherit file="index.tmpl"/> <%block name="content_header"> - %if subcategories: - ${messages('Subcategories:')} - <ul> - %for name, link in subcategories: - <li><a href="${link}">${name|h}</a></li> - %endfor - </ul> - %endif + <header> + <h1>${title|h}</h1> + %if description: + <p>${description}</p> + %endif + %if subcategories: + ${messages('Subcategories:')} + <ul> + %for name, link in subcategories: + <li><a href="${link}">${name|h}</a></li> + %endfor + </ul> + %endif + </header> </%block> <%block name="extra_head"> diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap-theme.min.css.map b/nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap-theme.min.css.map new file mode 120000 index 0000000..fcd3722 --- /dev/null +++ b/nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap-theme.min.css.map @@ -0,0 +1 @@ +../../../../../../bower_components/bootstrap/dist/css/bootstrap-theme.min.css.map
\ No newline at end of file diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap.min.css.map b/nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap.min.css.map new file mode 120000 index 0000000..5914aca --- /dev/null +++ b/nikola/data/themes/bootstrap3-jinja/assets/css/bootstrap.min.css.map @@ -0,0 +1 @@ +../../../../../../bower_components/bootstrap/dist/css/bootstrap.min.css.map
\ No newline at end of file diff --git a/nikola/data/themes/bootstrap3-jinja/assets/css/theme.css b/nikola/data/themes/bootstrap3-jinja/assets/css/theme.css index 6964ec6..52466de 100644 --- a/nikola/data/themes/bootstrap3-jinja/assets/css/theme.css +++ b/nikola/data/themes/bootstrap3-jinja/assets/css/theme.css @@ -118,7 +118,7 @@ article.post-micro { } .metadata p:before, -.postlist .listdate:before { +.postlist .listdate:after { content: " — "; } @@ -177,6 +177,14 @@ article.post-micro { border-top: 1px solid #e5e5e5; } +.codetable { + table-layout: fixed; +} + +.codetable pre { + overflow-x: scroll; +} + /* hat tip bootstrap/html5 boilerplate */ @media print { *, *:before, *:after { diff --git a/nikola/data/themes/bootstrap3-jinja/assets/js/flowr.plugin.js b/nikola/data/themes/bootstrap3-jinja/assets/js/flowr.plugin.js index a6ab900..732fa3d 100644 --- a/nikola/data/themes/bootstrap3-jinja/assets/js/flowr.plugin.js +++ b/nikola/data/themes/bootstrap3-jinja/assets/js/flowr.plugin.js @@ -180,7 +180,7 @@ } } //utils - // If the resposive var is set to true then listen for resize method + // If the responsive var is set to true then listen for resize method // and prevent resizing from happening twice if responsive is set again during append phase! if (settings.responsive && !$this.data('__responsive')) { $(window).resize(function() { @@ -211,7 +211,7 @@ var currentItem = 0; // Store all the data - var allData = $this.data('data') || []; + var allData = []; for (i = 0; i < data.length; i++) { allData.push(data[i]); } diff --git a/nikola/data/themes/bootstrap3-jinja/templates/base.tmpl b/nikola/data/themes/bootstrap3-jinja/templates/base.tmpl index 21b93cd..4ce46d0 100644 --- a/nikola/data/themes/bootstrap3-jinja/templates/base.tmpl +++ b/nikola/data/themes/bootstrap3-jinja/templates/base.tmpl @@ -17,7 +17,7 @@ <div class="container"><!-- This keeps the margins nice --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-navbar" aria-controls="bs-navbar" aria-expanded="false"> - <span class="sr-only">Toggle navigation</span> + <span class="sr-only">{{ messages("Toggle navigation") }}</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> diff --git a/nikola/data/themes/bootstrap3-jinja/templates/base_helper.tmpl b/nikola/data/themes/bootstrap3-jinja/templates/base_helper.tmpl index d9ea683..1d1802f 100644 --- a/nikola/data/themes/bootstrap3-jinja/templates/base_helper.tmpl +++ b/nikola/data/themes/bootstrap3-jinja/templates/base_helper.tmpl @@ -63,7 +63,7 @@ lang="{{ lang }}"> {{ mathjax_config }} {% if use_cdn %} - <!--[if lt IE 9]><script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]--> + <!--[if lt IE 9]><script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]--> {% else %} <!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang) }}"></script><![endif]--> {% endif %} @@ -74,16 +74,17 @@ lang="{{ lang }}"> {% macro late_load_js() %} {% if use_bundles %} {% if use_cdn %} - <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.3/jquery.min.js"></script> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> + <script src="/assets/js/all.js"></script> {% else %} <script src="/assets/js/all-nocdn.js"></script> {% endif %} {% else %} {% if use_cdn %} - <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.3/jquery.min.js"></script> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> {% else %} <script src="/assets/js/jquery.min.js"></script> <script src="/assets/js/bootstrap.min.js"></script> @@ -102,14 +103,14 @@ lang="{{ lang }}"> {% macro html_stylesheets() %} {% if use_bundles %} {% if use_cdn %} - <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <link href="/assets/css/all.css" rel="stylesheet" type="text/css"> {% else %} <link href="/assets/css/all-nocdn.css" rel="stylesheet" type="text/css"> {% endif %} {% else %} {% if use_cdn %} - <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> {% else %} <link href="/assets/css/bootstrap.min.css" rel="stylesheet" type="text/css"> {% endif %} diff --git a/nikola/data/themes/bootstrap3-jinja/templates/gallery.tmpl b/nikola/data/themes/bootstrap3-jinja/templates/gallery.tmpl index 2ae0457..cd9a5ed 100644 --- a/nikola/data/themes/bootstrap3-jinja/templates/gallery.tmpl +++ b/nikola/data/themes/bootstrap3-jinja/templates/gallery.tmpl @@ -90,5 +90,6 @@ $("#gallery_container").flowr({ } }); $("a.image-reference").colorbox({rel:"gal", maxWidth:"100%",maxHeight:"100%",scalePhotos:true}); +$('a.image-reference[href="'+window.location.hash.substring(1,1000)+'"]').click(); </script> {% endblock %} diff --git a/nikola/data/themes/bootstrap3-jinja/templates/listing.tmpl b/nikola/data/themes/bootstrap3-jinja/templates/listing.tmpl index f0d8050..ed99410 100644 --- a/nikola/data/themes/bootstrap3-jinja/templates/listing.tmpl +++ b/nikola/data/themes/bootstrap3-jinja/templates/listing.tmpl @@ -1,11 +1,10 @@ {# -*- coding: utf-8 -*- #} {% extends 'base.tmpl' %} {% import 'crumbs.tmpl' as ui with context %} - {% block content %} {{ ui.bar(crumbs) }} {% if folders or files %} -<ul class="list-unstyled"> +<ul> {% for name in folders %} <li><a href="{{ name|urlencode }}"><i class="glyphicon glyphicon-folder-open"></i> {{ name|e }}</a> {% endfor %} @@ -15,6 +14,11 @@ </ul> {% endif %} {% if code %} +<h1>{{ title }} + {% if source_link %} + <small><a href="{{ source_link }}">({{ messages("Source") }})</a></small> + {% endif %} + </h1> {{ code }} {% endif %} {% endblock %} diff --git a/nikola/data/themes/bootstrap3/assets/css/bootstrap-theme.min.css.map b/nikola/data/themes/bootstrap3/assets/css/bootstrap-theme.min.css.map new file mode 120000 index 0000000..fcd3722 --- /dev/null +++ b/nikola/data/themes/bootstrap3/assets/css/bootstrap-theme.min.css.map @@ -0,0 +1 @@ +../../../../../../bower_components/bootstrap/dist/css/bootstrap-theme.min.css.map
\ No newline at end of file diff --git a/nikola/data/themes/bootstrap3/assets/css/bootstrap.min.css.map b/nikola/data/themes/bootstrap3/assets/css/bootstrap.min.css.map new file mode 120000 index 0000000..5914aca --- /dev/null +++ b/nikola/data/themes/bootstrap3/assets/css/bootstrap.min.css.map @@ -0,0 +1 @@ +../../../../../../bower_components/bootstrap/dist/css/bootstrap.min.css.map
\ No newline at end of file diff --git a/nikola/data/themes/bootstrap3/assets/css/theme.css b/nikola/data/themes/bootstrap3/assets/css/theme.css index 6964ec6..52466de 100644 --- a/nikola/data/themes/bootstrap3/assets/css/theme.css +++ b/nikola/data/themes/bootstrap3/assets/css/theme.css @@ -118,7 +118,7 @@ article.post-micro { } .metadata p:before, -.postlist .listdate:before { +.postlist .listdate:after { content: " — "; } @@ -177,6 +177,14 @@ article.post-micro { border-top: 1px solid #e5e5e5; } +.codetable { + table-layout: fixed; +} + +.codetable pre { + overflow-x: scroll; +} + /* hat tip bootstrap/html5 boilerplate */ @media print { *, *:before, *:after { diff --git a/nikola/data/themes/bootstrap3/assets/js/flowr.plugin.js b/nikola/data/themes/bootstrap3/assets/js/flowr.plugin.js index a6ab900..732fa3d 100644 --- a/nikola/data/themes/bootstrap3/assets/js/flowr.plugin.js +++ b/nikola/data/themes/bootstrap3/assets/js/flowr.plugin.js @@ -180,7 +180,7 @@ } } //utils - // If the resposive var is set to true then listen for resize method + // If the responsive var is set to true then listen for resize method // and prevent resizing from happening twice if responsive is set again during append phase! if (settings.responsive && !$this.data('__responsive')) { $(window).resize(function() { @@ -211,7 +211,7 @@ var currentItem = 0; // Store all the data - var allData = $this.data('data') || []; + var allData = []; for (i = 0; i < data.length; i++) { allData.push(data[i]); } diff --git a/nikola/data/themes/bootstrap3/templates/base.tmpl b/nikola/data/themes/bootstrap3/templates/base.tmpl index ab7805b..2f7a290 100644 --- a/nikola/data/themes/bootstrap3/templates/base.tmpl +++ b/nikola/data/themes/bootstrap3/templates/base.tmpl @@ -17,7 +17,7 @@ ${template_hooks['extra_head']()} <div class="container"><!-- This keeps the margins nice --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-navbar" aria-controls="bs-navbar" aria-expanded="false"> - <span class="sr-only">Toggle navigation</span> + <span class="sr-only">${messages("Toggle navigation")}</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> diff --git a/nikola/data/themes/bootstrap3/templates/base_helper.tmpl b/nikola/data/themes/bootstrap3/templates/base_helper.tmpl index 587a933..20b135b 100644 --- a/nikola/data/themes/bootstrap3/templates/base_helper.tmpl +++ b/nikola/data/themes/bootstrap3/templates/base_helper.tmpl @@ -63,7 +63,7 @@ lang="${lang}"> ${mathjax_config} %if use_cdn: - <!--[if lt IE 9]><script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]--> + <!--[if lt IE 9]><script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]--> %else: <!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang)}"></script><![endif]--> %endif @@ -74,16 +74,17 @@ lang="${lang}"> <%def name="late_load_js()"> %if use_bundles: %if use_cdn: - <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.3/jquery.min.js"></script> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> + <script src="/assets/js/all.js"></script> %else: <script src="/assets/js/all-nocdn.js"></script> %endif %else: %if use_cdn: - <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.3/jquery.min.js"></script> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> %else: <script src="/assets/js/jquery.min.js"></script> <script src="/assets/js/bootstrap.min.js"></script> @@ -102,14 +103,14 @@ lang="${lang}"> <%def name="html_stylesheets()"> %if use_bundles: %if use_cdn: - <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <link href="/assets/css/all.css" rel="stylesheet" type="text/css"> %else: <link href="/assets/css/all-nocdn.css" rel="stylesheet" type="text/css"> %endif %else: %if use_cdn: - <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> %else: <link href="/assets/css/bootstrap.min.css" rel="stylesheet" type="text/css"> %endif diff --git a/nikola/data/themes/bootstrap3/templates/gallery.tmpl b/nikola/data/themes/bootstrap3/templates/gallery.tmpl index af0c194..3dbfa82 100644 --- a/nikola/data/themes/bootstrap3/templates/gallery.tmpl +++ b/nikola/data/themes/bootstrap3/templates/gallery.tmpl @@ -90,5 +90,6 @@ $("#gallery_container").flowr({ } }); $("a.image-reference").colorbox({rel:"gal", maxWidth:"100%",maxHeight:"100%",scalePhotos:true}); +$('a.image-reference[href="'+window.location.hash.substring(1,1000)+'"]').click(); </script> </%block> diff --git a/nikola/data/themes/bootstrap3/templates/listing.tmpl b/nikola/data/themes/bootstrap3/templates/listing.tmpl index 8c43806..44809d0 100644 --- a/nikola/data/themes/bootstrap3/templates/listing.tmpl +++ b/nikola/data/themes/bootstrap3/templates/listing.tmpl @@ -1,11 +1,10 @@ ## -*- coding: utf-8 -*- <%inherit file="base.tmpl"/> <%namespace name="ui" file="crumbs.tmpl" import="bar"/> - <%block name="content"> ${ui.bar(crumbs)} %if folders or files: -<ul class="list-unstyled"> +<ul> % for name in folders: <li><a href="${name|u}"><i class="glyphicon glyphicon-folder-open"></i> ${name|h}</a> % endfor @@ -15,6 +14,11 @@ ${ui.bar(crumbs)} </ul> %endif % if code: +<h1>${title} + % if source_link: + <small><a href="${source_link}">(${messages("Source")})</a></small> + % endif + </h1> ${code} % endif </%block> diff --git a/nikola/filters.py b/nikola/filters.py index 0c477c2..b53e605 100644 --- a/nikola/filters.py +++ b/nikola/filters.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -142,54 +142,54 @@ def yui_compressor(infile): raise Exception("yui-compressor is not installed.") return False - return runinplace(r'{} --nomunge %1 -o %2'.format(yuicompressor), infile) + return runinplace('{} --nomunge %1 -o %2'.format(yuicompressor), infile) def closure_compiler(infile): """Run closure-compiler on a file.""" - return runinplace(r'closure-compiler --warning_level QUIET --js %1 --js_output_file %2', infile) + return runinplace('closure-compiler --warning_level QUIET --js %1 --js_output_file %2', infile) def optipng(infile): """Run optipng on a file.""" - return runinplace(r"optipng -preserve -o2 -quiet %1", infile) + return runinplace("optipng -preserve -o2 -quiet %1", infile) def jpegoptim(infile): """Run jpegoptim on a file.""" - return runinplace(r"jpegoptim -p --strip-all -q %1", infile) + return runinplace("jpegoptim -p --strip-all -q %1", infile) -def html_tidy_withconfig(infile): +def html_tidy_withconfig(infile, executable='tidy5'): """Run HTML Tidy with tidy5.conf as config file.""" - return _html_tidy_runner(infile, r"-quiet --show-info no --show-warnings no -utf8 -indent -config tidy5.conf -modify %1") + return _html_tidy_runner(infile, "-quiet --show-info no --show-warnings no -utf8 -indent -config tidy5.conf -modify %1", executable=executable) -def html_tidy_nowrap(infile): +def html_tidy_nowrap(infile, executable='tidy5'): """Run HTML Tidy without line wrapping.""" - return _html_tidy_runner(infile, r"-quiet --show-info no --show-warnings no -utf8 -indent --indent-attributes no --sort-attributes alpha --wrap 0 --wrap-sections no --drop-empty-elements no --tidy-mark no -modify %1") + return _html_tidy_runner(infile, "-quiet --show-info no --show-warnings no -utf8 -indent --indent-attributes no --sort-attributes alpha --wrap 0 --wrap-sections no --drop-empty-elements no --tidy-mark no -modify %1", executable=executable) -def html_tidy_wrap(infile): +def html_tidy_wrap(infile, executable='tidy5'): """Run HTML Tidy with line wrapping.""" - return _html_tidy_runner(infile, r"-quiet --show-info no --show-warnings no -utf8 -indent --indent-attributes no --sort-attributes alpha --wrap 80 --wrap-sections no --drop-empty-elements no --tidy-mark no -modify %1") + return _html_tidy_runner(infile, "-quiet --show-info no --show-warnings no -utf8 -indent --indent-attributes no --sort-attributes alpha --wrap 80 --wrap-sections no --drop-empty-elements no --tidy-mark no -modify %1", executable=executable) -def html_tidy_wrap_attr(infile): +def html_tidy_wrap_attr(infile, executable='tidy5'): """Run HTML tidy with line wrapping and attribute indentation.""" - return _html_tidy_runner(infile, r"-quiet --show-info no --show-warnings no -utf8 -indent --indent-attributes yes --sort-attributes alpha --wrap 80 --wrap-sections no --drop-empty-elements no --tidy-mark no -modify %1") + return _html_tidy_runner(infile, "-quiet --show-info no --show-warnings no -utf8 -indent --indent-attributes yes --sort-attributes alpha --wrap 80 --wrap-sections no --drop-empty-elements no --tidy-mark no -modify %1", executable=executable) -def html_tidy_mini(infile): +def html_tidy_mini(infile, executable='tidy5'): """Run HTML tidy with minimal settings.""" - return _html_tidy_runner(infile, r"-quiet --show-info no --show-warnings no -utf8 --indent-attributes no --sort-attributes alpha --wrap 0 --wrap-sections no --tidy-mark no --drop-empty-elements no -modify %1") + return _html_tidy_runner(infile, "-quiet --show-info no --show-warnings no -utf8 --indent-attributes no --sort-attributes alpha --wrap 0 --wrap-sections no --tidy-mark no --drop-empty-elements no -modify %1", executable=executable) -def _html_tidy_runner(infile, options): +def _html_tidy_runner(infile, options, executable='tidy5'): """Run HTML Tidy.""" # Warnings (returncode 1) are not critical, and *everything* is a warning. try: - status = runinplace(r"tidy5 " + options, infile) + status = runinplace(executable + " " + options, infile) except subprocess.CalledProcessError as err: status = 0 if err.returncode == 1 else err.returncode return status @@ -290,6 +290,9 @@ def cssminify(data): url = 'http://cssminifier.com/raw' _data = {'input': data} response = requests.post(url, data=_data) + if response.status_code != 200: + LOGGER.error("can't use cssminifier.com: HTTP status {}", response.status_code) + return data return response.text except Exception as exc: LOGGER.error("can't use cssminifier.com: {}", exc) @@ -303,6 +306,9 @@ def jsminify(data): url = 'http://javascript-minifier.com/raw' _data = {'input': data} response = requests.post(url, data=_data) + if response.status_code != 200: + LOGGER.error("can't use javascript-minifier.com: HTTP status {}", response.status_code) + return data return response.text except Exception as exc: LOGGER.error("can't use javascript-minifier.com: {}", exc) @@ -330,7 +336,7 @@ def _normalize_html(data): data = lxml.html.tostring(lxml.html.fromstring(data), encoding='unicode') except: pass - return data + return '<!DOCTYPE html>\n' + data normalize_html = apply_to_text_file(_normalize_html) diff --git a/nikola/image_processing.py b/nikola/image_processing.py index b6f8215..e0096b2 100644 --- a/nikola/image_processing.py +++ b/nikola/image_processing.py @@ -33,32 +33,72 @@ import lxml import re import gzip +import piexif + from nikola import utils Image = None try: - from PIL import Image, ExifTags # NOQA + from PIL import ExifTags, Image # NOQA except ImportError: try: - import Image as _Image import ExifTags + import Image as _Image Image = _Image except ImportError: pass +EXIF_TAG_NAMES = {} + class ImageProcessor(object): """Apply image operations.""" - image_ext_list_builtin = ['.jpg', '.png', '.jpeg', '.gif', '.svg', '.bmp', '.tiff'] + image_ext_list_builtin = ['.jpg', '.png', '.jpeg', '.gif', '.svg', '.svgz', '.bmp', '.tiff'] + + def _fill_exif_tag_names(self): + """Connect EXIF tag names to numeric values.""" + if not EXIF_TAG_NAMES: + for ifd in piexif.TAGS: + for tag, data in piexif.TAGS[ifd].items(): + EXIF_TAG_NAMES[tag] = data['name'] + + def filter_exif(self, exif, whitelist): + """Filter EXIF data as described in the documentation.""" + # Scenario 1: keep everything + if whitelist == {'*': '*'}: + return exif - def resize_image(self, src, dst, max_size, bigger_panoramas=True): + # Scenario 2: keep nothing + if whitelist == {}: + return None + + # Scenario 3: keep some + self._fill_exif_tag_names() + exif = exif.copy() # Don't modify in-place, it's rude + for k in list(exif.keys()): + if type(exif[k]) != dict: + pass # At least thumbnails have no fields + elif k not in whitelist: + exif.pop(k) # Not whitelisted, remove + elif k in whitelist and whitelist[k] == '*': + # Fully whitelisted, keep all + pass + else: + # Partially whitelisted + for tag in list(exif[k].keys()): + if EXIF_TAG_NAMES[tag] not in whitelist[k]: + exif[k].pop(tag) + + return exif or None + + def resize_image(self, src, dst, max_size, bigger_panoramas=True, preserve_exif_data=False, exif_whitelist={}): """Make a copy of the image in the requested size.""" if not Image or os.path.splitext(src)[1] in ['.svg', '.svgz']: self.resize_svg(src, dst, max_size, bigger_panoramas) return im = Image.open(src) - w, h = im.size + size = w, h = im.size if w > max_size or h > max_size: size = max_size, max_size @@ -66,30 +106,44 @@ class ImageProcessor(object): if bigger_panoramas and w > 2 * h: size = min(w, max_size * 4), min(w, max_size * 4) - try: - exif = im._getexif() - except Exception: - exif = None - if exif is not None: - for tag, value in list(exif.items()): - decoded = ExifTags.TAGS.get(tag, tag) + try: + exif = piexif.load(im.info["exif"]) + except KeyError: + exif = None + # Inside this if, we can manipulate exif as much as + # we want/need and it will be preserved if required + if exif is not None: + # Rotate according to EXIF + value = exif['0th'].get(piexif.ImageIFD.Orientation, 1) + if value in (3, 4): + im = im.transpose(Image.ROTATE_180) + elif value in (5, 6): + im = im.transpose(Image.ROTATE_270) + elif value in (7, 8): + im = im.transpose(Image.ROTATE_90) + if value in (2, 4, 5, 7): + im = im.transpose(Image.FLIP_LEFT_RIGHT) + exif['0th'][piexif.ImageIFD.Orientation] = 1 - if decoded == 'Orientation': - if value == 3: - im = im.rotate(180) - elif value == 6: - im = im.rotate(270) - elif value == 8: - im = im.rotate(90) - break - try: - im.thumbnail(size, Image.ANTIALIAS) + try: + im.thumbnail(size, Image.ANTIALIAS) + if exif is not None and preserve_exif_data: + # Put right size in EXIF data + w, h = im.size + if '0th' in exif: + exif["0th"][piexif.ImageIFD.ImageWidth] = w + exif["0th"][piexif.ImageIFD.ImageLength] = h + if 'Exif' in exif: + exif["Exif"][piexif.ExifIFD.PixelXDimension] = w + exif["Exif"][piexif.ExifIFD.PixelYDimension] = h + # Filter EXIF data as required + exif = self.filter_exif(exif, exif_whitelist) + im.save(dst, exif=piexif.dump(exif)) + else: im.save(dst) - except Exception as e: - self.logger.warn("Can't thumbnail {0}, using original " - "image as thumbnail ({1})".format(src, e)) - utils.copy_file(src, dst) - else: # Image is small + except Exception as e: + self.logger.warn("Can't process {0}, using original " + "image! ({1})".format(src, e)) utils.copy_file(src, dst) def resize_svg(self, src, dst, max_size, bigger_panoramas): @@ -98,10 +152,10 @@ class ImageProcessor(object): # Resize svg based on viewport hacking. # note that this can also lead to enlarged svgs if src.endswith('.svgz'): - with gzip.GzipFile(src) as op: + with gzip.GzipFile(src, 'rb') as op: xml = op.read() else: - with open(src) as op: + with open(src, 'rb') as op: xml = op.read() tree = lxml.etree.XML(xml) width = tree.attrib['width'] @@ -125,9 +179,9 @@ class ImageProcessor(object): tree.attrib.pop("height") tree.attrib['viewport'] = "0 0 %ipx %ipx" % (w, h) if dst.endswith('.svgz'): - op = gzip.GzipFile(dst, 'w') + op = gzip.GzipFile(dst, 'wb') else: - op = open(dst, 'w') + op = open(dst, 'wb') op.write(lxml.etree.tostring(tree)) op.close() except (KeyError, AttributeError) as e: diff --git a/nikola/nikola.py b/nikola/nikola.py index 9e9b849..0a62360 100644 --- a/nikola/nikola.py +++ b/nikola/nikola.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -57,7 +57,8 @@ from yapsy.PluginManager import PluginManager from blinker import signal from .post import Post # NOQA -from . import DEBUG, utils +from .state import Persistor +from . import DEBUG, utils, shortcodes from .plugin_categories import ( Command, LateTask, @@ -65,6 +66,7 @@ from .plugin_categories import ( CompilerExtension, MarkdownExtension, RestExtension, + ShortcodePlugin, Task, TaskMultiplier, TemplateSystem, @@ -119,6 +121,8 @@ LEGAL_VALUES = { 'fa': 'Persian', 'fi': 'Finnish', 'fr': 'French', + 'gl': 'Galician', + 'he': 'Hebrew', 'hi': 'Hindi', 'hr': 'Croatian', 'hu': 'Hungarian', @@ -126,7 +130,8 @@ LEGAL_VALUES = { 'it': 'Italian', ('ja', '!jp'): 'Japanese', 'ko': 'Korean', - 'nb': 'Norwegian Bokmål', + 'lt': 'Lithuanian', + 'nb': 'Norwegian (Bokmål)', 'nl': 'Dutch', 'pa': 'Punjabi', 'pl': 'Polish', @@ -135,46 +140,75 @@ LEGAL_VALUES = { 'ru': 'Russian', 'sk': 'Slovak', 'sl': 'Slovene', + 'sq': 'Albanian', 'sr': 'Serbian (Cyrillic)', 'sr_latin': 'Serbian (Latin)', 'sv': 'Swedish', + 'te': 'Telugu', ('tr', '!tr_TR'): 'Turkish', 'ur': 'Urdu', 'uk': 'Ukrainian', 'zh_cn': 'Chinese (Simplified)', + 'zh_tw': 'Chinese (Traditional)' }, '_WINDOWS_LOCALE_GUESSES': { # TODO incomplete - # some languages may need that the appropiate Microsoft Language Pack be instaled. + # some languages may need that the appropriate Microsoft Language Pack be installed. + "ar": "Arabic", + "az": "Azeri (Latin)", "bg": "Bulgarian", + "bs": "Bosnian", "ca": "Catalan", + "cs": "Czech", + "da": "Danish", "de": "German", "el": "Greek", "en": "English", - "eo": "Esperanto", + # "eo": "Esperanto", # Not available "es": "Spanish", - "fa": "Farsi", # Persian + "et": "Estonian", + "eu": "Basque", + "fa": "Persian", # Persian + "fi": "Finnish", "fr": "French", + "gl": "Galician", + "he": "Hebrew", + "hi": "Hindi", "hr": "Croatian", "hu": "Hungarian", + "id": "Indonesian", "it": "Italian", - "jp": "Japanese", + "ja": "Japanese", + "ko": "Korean", + "nb": "Norwegian", # Not Bokmål, as Windows doesn't find it for unknown reasons. "nl": "Dutch", + "pa": "Punjabi", "pl": "Polish", + "pt": "Portuguese_Portugal", "pt_br": "Portuguese_Brazil", "ru": "Russian", - "sl_si": "Slovenian", - "tr_tr": "Turkish", + "sk": "Slovak", + "sl": "Slovenian", + "sq": "Albanian", + "sr": "Serbian", + "sr_latin": "Serbian (Latin)", + "sv": "Swedish", + "te": "Telugu", + "tr": "Turkish", + "uk": "Ukrainian", + "ur": "Urdu", "zh_cn": "Chinese_China", # Chinese (Simplified) + "zh_tw": "Chinese_Taiwan", # Chinese (Traditional) }, '_TRANSLATIONS_WITH_COUNTRY_SPECIFIERS': { # This dict is used in `init` in case of locales that exist with a # country specifier. If there is no other locale that has the same # language with a different country, ``nikola init`` (but nobody else!) # will accept it, warning the user about it. - 'zh': 'zh_cn', + + # This dict is currently empty. }, - 'RTL_LANGUAGES': ('ar', 'fa', 'ur'), + 'RTL_LANGUAGES': ('ar', 'fa', 'he', 'ur'), 'COLORBOX_LOCALES': defaultdict( str, ar='ar', @@ -190,12 +224,14 @@ LEGAL_VALUES = { fa='fa', fi='fi', fr='fr', + he='he', hr='hr', hu='hu', id='id', it='it', ja='ja', ko='kr', # kr is South Korea, ko is the Korean language + lt='lt', nb='no', nl='nl', pl='pl', @@ -209,7 +245,8 @@ LEGAL_VALUES = { sv='sv', tr='tr', uk='uk', - zh_cn='zh-CN' + zh_cn='zh-CN', + zh_tw='zh-TW' ), 'MOMENTJS_LOCALES': defaultdict( str, @@ -232,13 +269,16 @@ LEGAL_VALUES = { fa='fa', fi='fi', fr='fr', + gl='gl', hi='hi', + he='he', hr='hr', hu='hu', id='id', it='it', ja='ja', ko='ko', + lt='lt', nb='nb', nl='nl', pl='pl', @@ -247,12 +287,14 @@ LEGAL_VALUES = { ru='ru', sk='sk', sl='sl', + sq='sq', sr='sr-cyrl', sr_latin='sr', sv='sv', tr='tr', uk='uk', - zh_cn='zh-cn' + zh_cn='zh-cn', + zh_tw='zh-tw' ), 'PYPHEN_LOCALES': { 'bg': 'bg', @@ -262,13 +304,14 @@ LEGAL_VALUES = { 'da': 'da', 'de': 'de', 'el': 'el', - 'en': 'en', + 'en': 'en_US', 'es': 'es', 'et': 'et', 'fr': 'fr', 'hr': 'hr', 'hu': 'hu', 'it': 'it', + 'lt': 'lt', 'nb': 'nb', 'nl': 'nl', 'pl': 'pl', @@ -279,8 +322,32 @@ LEGAL_VALUES = { 'sl': 'sl', 'sr': 'sr', 'sv': 'sv', + 'te': 'te', 'uk': 'uk', }, + 'DOCUTILS_LOCALES': { + 'ca': 'ca', + 'da': 'da', + 'de': 'de', + 'en': 'en', + 'eo': 'eo', + 'es': 'es', + 'fi': 'fi', + 'fr': 'fr', + 'gl': 'gl', + 'he': 'he', + 'it': 'it', + 'ja': 'ja', + 'lt': 'lt', + 'pl': 'pl', + 'pt': 'pt_br', # hope nobody will mind + 'pt_br': 'pt_br', + 'ru': 'ru', + 'sk': 'sk', + 'sv': 'sv', + 'zh_cn': 'zh_cn', + 'zh_tw': 'zh_tw' + } } @@ -288,7 +355,13 @@ def _enclosure(post, lang): """Add an enclosure to RSS.""" enclosure = post.meta('enclosure', lang) if enclosure: - length = 0 + try: + length = int(post.meta('enclosure_length', lang) or 0) + except KeyError: + length = 0 + except ValueError: + utils.LOGGER.warn("Invalid enclosure length for post {0}".format(post.source_path)) + length = 0 url = enclosure mime = mimetypes.guess_type(url)[0] return url, length, mime @@ -324,6 +397,7 @@ class Nikola(object): self._scanned = False self._template_system = None self._THEMES = None + self._MESSAGES = None self.debug = DEBUG self.loghandlers = utils.STDERR_HANDLER # TODO remove on v8 self.colorful = config.pop('__colorful__', False) @@ -334,6 +408,7 @@ class Nikola(object): self.configuration_filename = config.pop('__configuration_filename__', False) self.configured = bool(config) self.injected_deps = defaultdict(list) + self.shortcode_registry = {} self.rst_transforms = [] self.template_hooks = { @@ -373,7 +448,7 @@ class Nikola(object): 'CODE_COLOR_SCHEME': 'default', 'COMMENT_SYSTEM': 'disqus', 'COMMENTS_IN_GALLERIES': False, - 'COMMENTS_IN_STORIES': False, + 'COMMENTS_IN_PAGES': False, 'COMPILERS': { "rest": ('.txt', '.rst'), "markdown": ('.md', '.mdown', '.markdown'), @@ -398,8 +473,10 @@ class Nikola(object): 'DEPLOY_COMMANDS': {'default': []}, 'DISABLED_PLUGINS': [], 'EXTRA_PLUGINS_DIRS': [], + 'EXTRA_THEMES_DIRS': [], 'COMMENT_SYSTEM_ID': 'nikolademo', 'ENABLE_AUTHOR_PAGES': True, + 'EXIF_WHITELIST': {}, 'EXTRA_HEAD_DATA': '', 'FAVICONS': (), 'FEED_LENGTH': 10, @@ -408,6 +485,7 @@ class Nikola(object): 'FILES_FOLDERS': {'files': ''}, 'FILTERS': {}, 'FORCE_ISO8601': False, + 'FRONT_INDEX_HEADER': '', 'GALLERY_FOLDERS': {'galleries': 'galleries'}, 'GALLERY_SORT_BY_DATE': True, 'GLOBAL_CONTEXT_FILLER': [], @@ -430,6 +508,7 @@ class Nikola(object): 'INDEXES_STATIC': True, 'INDEX_PATH': '', 'IPYNB_CONFIG': {}, + 'KATEX_AUTO_RENDER': '', 'LESS_COMPILER': 'lessc', 'LESS_OPTIONS': [], 'LICENSE': '', @@ -444,11 +523,14 @@ class Nikola(object): 'OUTPUT_FOLDER': 'output', 'POSTS': (("posts/*.txt", "posts", "post.tmpl"),), 'POSTS_SECTIONS': True, + 'POSTS_SECTION_COLORS': {}, 'POSTS_SECTION_ARE_INDEXES': True, 'POSTS_SECTION_DESCRIPTIONS': "", 'POSTS_SECTION_FROM_META': False, 'POSTS_SECTION_NAME': "", 'POSTS_SECTION_TITLE': "{name}", + 'PRESERVE_EXIF_DATA': False, + # TODO: change in v8 'PAGES': (("stories/*.txt", "stories", "story.tmpl"),), 'PANDOC_OPTIONS': [], 'PRETTY_URLS': False, @@ -475,7 +557,7 @@ class Nikola(object): 'SLUG_TAG_PATH': True, 'SOCIAL_BUTTONS_CODE': '', 'SITE_URL': 'https://example.com/', - 'STORY_INDEX': False, + 'PAGE_INDEX': False, 'STRIP_INDEXES': False, 'SITEMAP_INCLUDE_FILELESS_DIRS': True, 'TAG_PATH': 'categories', @@ -492,7 +574,7 @@ class Nikola(object): 'THUMBNAIL_SIZE': 180, 'UNSLUGIFY_TITLES': False, # WARNING: conf.py.in overrides this with True for backwards compatibility 'URL_TYPE': 'rel_path', - 'USE_BASE_TAG': True, + 'USE_BASE_TAG': False, 'USE_BUNDLES': True, 'USE_CDN': False, 'USE_CDN_WARNING': True, @@ -511,6 +593,7 @@ class Nikola(object): 'GITHUB_SOURCE_BRANCH': 'master', 'GITHUB_DEPLOY_BRANCH': 'gh-pages', 'GITHUB_REMOTE_NAME': 'origin', + 'GITHUB_COMMIT_SOURCE': False, # WARNING: conf.py.in overrides this with True for backwards compatibility } # set global_context for template rendering @@ -548,6 +631,7 @@ class Nikola(object): 'BODY_END', 'EXTRA_HEAD_DATA', 'NAVIGATION_LINKS', + 'FRONT_INDEX_HEADER', 'INDEX_READ_MORE_LINK', 'FEED_READ_MORE_LINK', 'INDEXES_TITLE', @@ -576,7 +660,13 @@ class Nikola(object): 'body_end', 'extra_head_data', 'date_format', - 'js_date_format',) + 'js_date_format', + 'posts_section_colors', + 'posts_section_descriptions', + 'posts_section_name', + 'posts_section_title', + 'front_index_header', + ) # WARNING: navigation_links SHOULD NOT be added to the list above. # Themes ask for [lang] there and we should provide it. @@ -594,6 +684,15 @@ class Nikola(object): except KeyError: pass + # A EXIF_WHITELIST implies you want to keep EXIF data + if self.config['EXIF_WHITELIST'] and not self.config['PRESERVE_EXIF_DATA']: + utils.LOGGER.warn('Setting EXIF_WHITELIST implies PRESERVE_EXIF_DATA is set to True') + self.config['PRESERVE_EXIF_DATA'] = True + + # Setting PRESERVE_EXIF_DATA with an empty EXIF_WHITELIST implies 'keep everything' + if self.config['PRESERVE_EXIF_DATA'] and not self.config['EXIF_WHITELIST']: + utils.LOGGER.warn('You are setting PRESERVE_EXIF_DATA and not EXIF_WHITELIST so EXIF data is not really kept.') + # Handle CONTENT_FOOTER properly. # We provide the arguments to format in CONTENT_FOOTER_FORMATS. self.config['CONTENT_FOOTER'].langformat(self.config['CONTENT_FOOTER_FORMATS']) @@ -635,7 +734,7 @@ class Nikola(object): # TODO: remove on v8 if 'RSS_LINKS_APPEND_QUERY' in config: utils.LOGGER.warn('The RSS_LINKS_APPEND_QUERY option is deprecated, use FEED_LINKS_APPEND_QUERY instead.') - if 'FEED_TEASERS' in config: + if 'FEED_LINKS_APPEND_QUERY' in config: utils.LOGGER.warn('FEED_LINKS_APPEND_QUERY conflicts with RSS_LINKS_APPEND_QUERY, ignoring RSS_LINKS_APPEND_QUERY.') self.config['FEED_LINKS_APPEND_QUERY'] = config['RSS_LINKS_APPEND_QUERY'] @@ -772,6 +871,15 @@ class Nikola(object): utils.LOGGER.warn("WRITE_TAG_CLOUD is not set in your config. Defaulting to True (== writing tag_cloud_data.json).") utils.LOGGER.warn("Please explicitly add the setting to your conf.py with the desired value, as the setting will default to False in the future.") + # Rename stories to pages (#1891, #2518) + # TODO: remove in v8 + if 'COMMENTS_IN_STORIES' in config: + utils.LOGGER.warn('The COMMENTS_IN_STORIES option is deprecated, use COMMENTS_IN_PAGES instead.') + self.config['COMMENTS_IN_PAGES'] = config['COMMENTS_IN_STORIES'] + if 'STORY_INDEX' in config: + utils.LOGGER.warn('The STORY_INDEX option is deprecated, use PAGE_INDEX instead.') + self.config['PAGE_INDEX'] = config['STORY_INDEX'] + # We use one global tzinfo object all over Nikola. try: self.tzinfo = dateutil.tz.gettz(self.config['TIMEZONE']) @@ -794,6 +902,9 @@ class Nikola(object): candidate = utils.get_translation_candidate(self.config, "f" + ext, lang) compilers[compiler].add(candidate) + # Get search path for themes + self.themes_dirs = ['themes'] + self.config['EXTRA_THEMES_DIRS'] + # Avoid redundant compilers # Remove compilers that match nothing in POSTS/PAGES # And put them in "bad compilers" @@ -807,9 +918,21 @@ class Nikola(object): else: self.bad_compilers.add(k) - self._set_global_context() + self._set_global_context_from_config() + self._set_global_context_from_data() - def init_plugins(self, commands_only=False): + # Set persistent state facility + self.state = Persistor('state_data.json') + + # Set cache facility + self.cache = Persistor(os.path.join(self.config['CACHE_FOLDER'], 'cache_data.json')) + + # Create directories for persistors only if a site exists (Issue #2334) + if self.configured: + self.state._set_site(self) + self.cache._set_site(self) + + def init_plugins(self, commands_only=False, load_all=False): """Load plugins as needed.""" self.plugin_manager = PluginManager(categories_filter={ "Command": Command, @@ -821,6 +944,7 @@ class Nikola(object): "CompilerExtension": CompilerExtension, "MarkdownExtension": MarkdownExtension, "RestExtension": RestExtension, + "ShortcodePlugin": ShortcodePlugin, "SignalHandler": SignalHandler, "ConfigPlugin": ConfigPlugin, "PostScanner": PostScanner, @@ -828,47 +952,71 @@ class Nikola(object): self.plugin_manager.getPluginLocator().setPluginInfoExtension('plugin') extra_plugins_dirs = self.config['EXTRA_PLUGINS_DIRS'] if sys.version_info[0] == 3: - places = [ + self._plugin_places = [ resource_filename('nikola', 'plugins'), - os.path.join(os.getcwd(), 'plugins'), os.path.expanduser('~/.nikola/plugins'), + os.path.join(os.getcwd(), 'plugins'), ] + [path for path in extra_plugins_dirs if path] else: - places = [ + self._plugin_places = [ resource_filename('nikola', utils.sys_encode('plugins')), os.path.join(os.getcwd(), utils.sys_encode('plugins')), os.path.expanduser('~/.nikola/plugins'), ] + [utils.sys_encode(path) for path in extra_plugins_dirs if path] - self.plugin_manager.getPluginLocator().setPluginPlaces(places) + self.plugin_manager.getPluginLocator().setPluginPlaces(self._plugin_places) self.plugin_manager.locatePlugins() bad_candidates = set([]) - for p in self.plugin_manager._candidates: - if commands_only: - if p[-1].details.has_option('Nikola', 'plugincategory'): - # FIXME TemplateSystem should not be needed - if p[-1].details.get('Nikola', 'PluginCategory') not in {'Command', 'Template'}: + if not load_all: + for p in self.plugin_manager._candidates: + if commands_only: + if p[-1].details.has_option('Nikola', 'plugincategory'): + # FIXME TemplateSystem should not be needed + if p[-1].details.get('Nikola', 'PluginCategory') not in {'Command', 'Template'}: + bad_candidates.add(p) + else: + bad_candidates.add(p) + elif self.configured: # Not commands-only, and configured + # Remove compilers we don't use + if p[-1].name in self.bad_compilers: + bad_candidates.add(p) + self.disabled_compilers[p[-1].name] = p + utils.LOGGER.debug('Not loading unneeded compiler {}', p[-1].name) + if p[-1].name not in self.config['COMPILERS'] and \ + p[-1].details.has_option('Nikola', 'plugincategory') and p[-1].details.get('Nikola', 'PluginCategory') == 'Compiler': bad_candidates.add(p) - else: # Not commands-only - # Remove compilers we don't use - if p[-1].name in self.bad_compilers: - bad_candidates.add(p) - self.disabled_compilers[p[-1].name] = p - utils.LOGGER.debug('Not loading unneeded compiler {}', p[-1].name) - if p[-1].name not in self.config['COMPILERS'] and \ - p[-1].details.has_option('Nikola', 'plugincategory') and p[-1].details.get('Nikola', 'PluginCategory') == 'Compiler': - bad_candidates.add(p) - self.disabled_compilers[p[-1].name] = p - utils.LOGGER.debug('Not loading unneeded compiler {}', p[-1].name) - # Remove blacklisted plugins - if p[-1].name in self.config['DISABLED_PLUGINS']: - bad_candidates.add(p) - utils.LOGGER.debug('Not loading disabled plugin {}', p[-1].name) - # Remove compiler extensions we don't need - if p[-1].details.has_option('Nikola', 'compiler') and p[-1].details.get('Nikola', 'compiler') in self.disabled_compilers: - bad_candidates.add(p) - utils.LOGGER.debug('Not loading compiler extension {}', p[-1].name) - self.plugin_manager._candidates = list(set(self.plugin_manager._candidates) - bad_candidates) + self.disabled_compilers[p[-1].name] = p + utils.LOGGER.debug('Not loading unneeded compiler {}', p[-1].name) + # Remove blacklisted plugins + if p[-1].name in self.config['DISABLED_PLUGINS']: + bad_candidates.add(p) + utils.LOGGER.debug('Not loading disabled plugin {}', p[-1].name) + # Remove compiler extensions we don't need + if p[-1].details.has_option('Nikola', 'compiler') and p[-1].details.get('Nikola', 'compiler') in self.disabled_compilers: + bad_candidates.add(p) + utils.LOGGER.debug('Not loading compiler extension {}', p[-1].name) + self.plugin_manager._candidates = list(set(self.plugin_manager._candidates) - bad_candidates) + + # Find repeated plugins and discard the less local copy + def plugin_position_in_places(plugin): + # plugin here is a tuple: + # (path to the .plugin file, path to plugin module w/o .py, plugin metadata) + for i, place in enumerate(self._plugin_places): + if plugin[0].startswith(place): + return i + + plugin_dict = defaultdict(list) + for data in self.plugin_manager._candidates: + plugin_dict[data[2].name].append(data) + self.plugin_manager._candidates = [] + for name, plugins in plugin_dict.items(): + if len(plugins) > 1: + # Sort by locality + plugins.sort(key=plugin_position_in_places) + utils.LOGGER.debug("Plugin {} exists in multiple places, using {}".format( + plugins[-1][2].name, plugins[-1][0])) + self.plugin_manager._candidates.append(plugins[-1]) + self.plugin_manager.loadPlugins() self._activate_plugins_of_category("SignalHandler") @@ -895,6 +1043,9 @@ class Nikola(object): self.plugin_manager.activatePluginByName(plugin_info.name) plugin_info.plugin_object.set_site(self) + # Activate shortcode plugins + self._activate_plugins_of_category("ShortcodePlugin") + # Load compiler plugins self.compilers = {} self.inverse_compilers = {} @@ -905,11 +1056,15 @@ class Nikola(object): plugin_info.plugin_object self._activate_plugins_of_category("ConfigPlugin") - + self._register_templated_shortcodes() signal('configured').send(self) - def _set_global_context(self): - """Create global context from configuration.""" + def _set_global_context_from_config(self): + """Create global context from configuration. + + These are options that are used by templates, so they always need to be + available. + """ self._GLOBAL_CONTEXT['url_type'] = self.config['URL_TYPE'] self._GLOBAL_CONTEXT['timezone'] = self.tzinfo self._GLOBAL_CONTEXT['_link'] = self.link @@ -937,6 +1092,7 @@ class Nikola(object): self._GLOBAL_CONTEXT['show_blog_title'] = self.config.get('SHOW_BLOG_TITLE') self._GLOBAL_CONTEXT['logo_url'] = self.config.get('LOGO_URL') self._GLOBAL_CONTEXT['blog_description'] = self.config.get('BLOG_DESCRIPTION') + self._GLOBAL_CONTEXT['front_index_header'] = self.config.get('FRONT_INDEX_HEADER') self._GLOBAL_CONTEXT['color_hsl_adjust_hex'] = utils.color_hsl_adjust_hex self._GLOBAL_CONTEXT['colorize_str_from_base_color'] = utils.colorize_str_from_base_color @@ -956,6 +1112,7 @@ class Nikola(object): self._GLOBAL_CONTEXT['mathjax_config'] = self.config.get( 'MATHJAX_CONFIG') self._GLOBAL_CONTEXT['use_katex'] = self.config.get('USE_KATEX') + self._GLOBAL_CONTEXT['katex_auto_render'] = self.config.get('KATEX_AUTO_RENDER') self._GLOBAL_CONTEXT['subtheme'] = self.config.get('THEME_REVEAL_CONFIG_SUBTHEME') self._GLOBAL_CONTEXT['transition'] = self.config.get('THEME_REVEAL_CONFIG_TRANSITION') self._GLOBAL_CONTEXT['content_footer'] = self.config.get( @@ -999,6 +1156,16 @@ class Nikola(object): self._GLOBAL_CONTEXT.update(self.config.get('GLOBAL_CONTEXT', {})) + def _set_global_context_from_data(self): + """Load files from data/ and put them in the global context.""" + self._GLOBAL_CONTEXT['data'] = {} + for root, dirs, files in os.walk('data', followlinks=True): + for fname in files: + fname = os.path.join(root, fname) + data = utils.load_data(fname) + key = os.path.splitext(fname.split(os.sep, 1)[1])[0] + self._GLOBAL_CONTEXT['data'][key] = data + def _activate_plugins_of_category(self, category): """Activate all the plugins of a given category and return them.""" # this code duplicated in tests/base.py @@ -1012,11 +1179,13 @@ class Nikola(object): def _get_themes(self): if self._THEMES is None: try: - self._THEMES = utils.get_theme_chain(self.config['THEME']) + self._THEMES = utils.get_theme_chain(self.config['THEME'], self.themes_dirs) except Exception: - utils.LOGGER.warn('''Cannot load theme "{0}", using 'bootstrap3' instead.'''.format(self.config['THEME'])) - self.config['THEME'] = 'bootstrap3' - return self._get_themes() + if self.config['THEME'] != 'bootstrap3': + utils.LOGGER.warn('''Cannot load theme "{0}", using 'bootstrap3' instead.'''.format(self.config['THEME'])) + self.config['THEME'] = 'bootstrap3' + return self._get_themes() + raise # Check consistency of USE_CDN and the current THEME (Issue #386) if self.config['USE_CDN'] and self.config['USE_CDN_WARNING']: bootstrap_path = utils.get_asset_path(os.path.join( @@ -1030,9 +1199,12 @@ class Nikola(object): def _get_messages(self): try: - return utils.load_messages(self.THEMES, - self.translations, - self.default_lang) + if self._MESSAGES is None: + self._MESSAGES = utils.load_messages(self.THEMES, + self.translations, + self.default_lang, + themes_dirs=self.themes_dirs) + return self._MESSAGES except utils.LanguageNotFoundError as e: utils.LOGGER.error('''Cannot load language "{0}". Please make sure it is supported by Nikola itself, or that you have the appropriate messages files in your themes.'''.format(e.lang)) sys.exit(1) @@ -1115,7 +1287,7 @@ class Nikola(object): return compile_html - def render_template(self, template_name, output_name, context): + def render_template(self, template_name, output_name, context, url_type=None): """Render a template with the global context. If ``output_name`` is None, will return a string and all URL @@ -1123,6 +1295,9 @@ class Nikola(object): If ``output_name`` is a string, URLs will be normalized and the resultant HTML will be saved to the named file (path must start with OUTPUT_FOLDER). + + The argument ``url_type`` allows to override the ``URL_TYPE`` + configuration. """ local_context = {} local_context["template_name"] = template_name @@ -1159,22 +1334,22 @@ class Nikola(object): utils.makedirs(os.path.dirname(output_name)) parser = lxml.html.HTMLParser(remove_blank_text=True) doc = lxml.html.document_fromstring(data, parser) - self.rewrite_links(doc, src, context['lang']) + self.rewrite_links(doc, src, context['lang'], url_type) data = b'<!DOCTYPE html>\n' + lxml.html.tostring(doc, encoding='utf8', method='html', pretty_print=True) with open(output_name, "wb+") as post_file: post_file.write(data) - def rewrite_links(self, doc, src, lang): + def rewrite_links(self, doc, src, lang, url_type=None): """Replace links in document to point to the right places.""" # First let lxml replace most of them - doc.rewrite_links(lambda dst: self.url_replacer(src, dst, lang), resolve_base_href=False) + doc.rewrite_links(lambda dst: self.url_replacer(src, dst, lang, url_type), resolve_base_href=False) # lxml ignores srcset in img and source elements, so do that by hand objs = list(doc.xpath('(*//img|*//source)')) for obj in objs: if 'srcset' in obj.attrib: urls = [u.strip() for u in obj.attrib['srcset'].split(',')] - urls = [self.url_replacer(src, dst, lang) for dst in urls] + urls = [self.url_replacer(src, dst, lang, url_type) for dst in urls] obj.set('srcset', ', '.join(urls)) def url_replacer(self, src, dst, lang=None, url_type=None): @@ -1277,7 +1452,7 @@ class Nikola(object): # Now i is the longest common prefix result = '/'.join(['..'] * (len(src_elems) - i - 1) + dst_elems[i:]) - if not result: + if not result and not parsed_dst.fragment: result = "." # Don't forget the query part of the link @@ -1292,6 +1467,83 @@ class Nikola(object): return result + def _make_renderfunc(self, t_data, fname=None): + """Return a function that can be registered as a template shortcode. + + The returned function has access to the passed template data and + accepts any number of positional and keyword arguments. Positional + arguments values are added as a tuple under the key ``_args`` to the + keyword argument dict and then the latter provides the template + context. + + """ + def render_shortcode(*args, **kw): + context = self.GLOBAL_CONTEXT.copy() + context.update(kw) + context['_args'] = args + context['lang'] = utils.LocaleBorg().current_lang + for k in self._GLOBAL_CONTEXT_TRANSLATABLE: + context[k] = context[k](context['lang']) + output = self.template_system.render_template_to_string(t_data, context) + if fname is not None: + dependencies = [fname] + self.template_system.get_deps(fname) + else: + dependencies = [] + return output, dependencies + return render_shortcode + + def _register_templated_shortcodes(self): + """Register shortcodes based on templates. + + This will register a shortcode for any template found in shortcodes/ + folders and a generic "template" shortcode which will consider the + content in the shortcode as a template in itself. + """ + self.register_shortcode('template', self._template_shortcode_handler) + + builtin_sc_dir = resource_filename( + 'nikola', + os.path.join('data', 'shortcodes', utils.get_template_engine(self.THEMES))) + + for sc_dir in [builtin_sc_dir, 'shortcodes']: + if not os.path.isdir(sc_dir): + continue + + for fname in os.listdir(sc_dir): + name, ext = os.path.splitext(fname) + + if ext != '.tmpl': + continue + with open(os.path.join(sc_dir, fname)) as fd: + self.register_shortcode(name, self._make_renderfunc( + fd.read(), os.path.join(sc_dir, fname))) + + def _template_shortcode_handler(self, *args, **kw): + t_data = kw.pop('data', '') + context = self.GLOBAL_CONTEXT.copy() + context.update(kw) + context['_args'] = args + context['lang'] = utils.LocaleBorg().current_lang + for k in self._GLOBAL_CONTEXT_TRANSLATABLE: + context[k] = context[k](context['lang']) + output = self.template_system.render_template_to_string(t_data, context) + dependencies = self.template_system.get_string_deps(t_data) + return output, dependencies + + def register_shortcode(self, name, f): + """Register function f to handle shortcode "name".""" + if name in self.shortcode_registry: + utils.LOGGER.warn('Shortcode name conflict: {}', name) + return + self.shortcode_registry[name] = f + + # XXX in v8, get rid of with_dependencies + def apply_shortcodes(self, data, filename=None, lang=None, with_dependencies=False, extra_context={}): + """Apply shortcodes from the registry on data.""" + if lang is None: + lang = utils.LocaleBorg().current_lang + return shortcodes.apply_shortcodes(data, self.shortcode_registry, self, filename, lang=lang, with_dependencies=with_dependencies, extra_context=extra_context) + def generic_rss_renderer(self, lang, title, link, description, timeline, output_path, rss_teasers, rss_plain, feed_length=10, feed_url=None, enclosure=_enclosure, rss_links_append_query=None): @@ -1398,8 +1650,8 @@ class Nikola(object): * gallery (name is the gallery name) * listing (name is the source code file name) * post_path (name is 1st element in a POSTS/PAGES tuple) - * slug (name is the slug of a post or story) - * filename (name is the source filename of a post/story, in DEFAULT_LANG, relative to conf.py) + * slug (name is the slug of a post or page) + * filename (name is the source filename of a post/page, in DEFAULT_LANG, relative to conf.py) The returned value is always a path relative to output, like "categories/whatever.html" @@ -1461,7 +1713,7 @@ class Nikola(object): Example: - links://slug/yellow-camaro => /posts/cars/awful/yellow-camaro/index.html + link://slug/yellow-camaro => /posts/cars/awful/yellow-camaro/index.html """ results = [p for p in self.timeline if p.meta('slug') == name] if not results: @@ -1472,7 +1724,7 @@ class Nikola(object): return [_f for _f in results[0].permalink(lang).split('/') if _f] def filename_path(self, name, lang): - """Link to post or story by source filename. + """Link to post or page by source filename. Example: @@ -1660,6 +1912,7 @@ class Nikola(object): self.tags_per_language = defaultdict(list) self.category_hierarchy = {} self.post_per_file = {} + self.post_per_input_file = {} self.timeline = [] self.pages = [] @@ -1670,27 +1923,28 @@ class Nikola(object): quit = False # Classify posts per year/tag/month/whatever - slugged_tags = set([]) + slugged_tags = defaultdict(set) for post in self.timeline: if post.use_in_feeds: self.posts.append(post) self.posts_per_year[str(post.date.year)].append(post) self.posts_per_month[ '{0}/{1:02d}'.format(post.date.year, post.date.month)].append(post) - for tag in post.alltags: - _tag_slugified = utils.slugify(tag) - if _tag_slugified in slugged_tags: - if tag not in self.posts_per_tag: - # Tags that differ only in case - other_tag = [existing for existing in self.posts_per_tag.keys() if utils.slugify(existing) == _tag_slugified][0] - utils.LOGGER.error('You have tags that are too similar: {0} and {1}'.format(tag, other_tag)) - utils.LOGGER.error('Tag {0} is used in: {1}'.format(tag, post.source_path)) - utils.LOGGER.error('Tag {0} is used in: {1}'.format(other_tag, ', '.join([p.source_path for p in self.posts_per_tag[other_tag]]))) - quit = True - else: - slugged_tags.add(utils.slugify(tag)) - self.posts_per_tag[tag].append(post) for lang in self.config['TRANSLATIONS'].keys(): + for tag in post.tags_for_language(lang): + _tag_slugified = utils.slugify(tag, lang) + if _tag_slugified in slugged_tags[lang]: + if tag not in self.posts_per_tag: + # Tags that differ only in case + other_tag = [existing for existing in self.posts_per_tag.keys() if utils.slugify(existing, lang) == _tag_slugified][0] + utils.LOGGER.error('You have tags that are too similar: {0} and {1}'.format(tag, other_tag)) + utils.LOGGER.error('Tag {0} is used in: {1}'.format(tag, post.source_path)) + utils.LOGGER.error('Tag {0} is used in: {1}'.format(other_tag, ', '.join([p.source_path for p in self.posts_per_tag[other_tag]]))) + quit = True + else: + slugged_tags[lang].add(_tag_slugified) + if post not in self.posts_per_tag[tag]: + self.posts_per_tag[tag].append(post) self.tags_per_language[lang].extend(post.tags_for_language(lang)) self._add_post_to_category(post, post.meta('category')) @@ -1703,6 +1957,7 @@ class Nikola(object): for lang in self.config['TRANSLATIONS'].keys(): dest = post.destination_path(lang=lang) src_dest = post.destination_path(lang=lang, extension=post.source_ext()) + src_file = post.translated_source_path(lang=lang) if dest in self.post_per_file: utils.LOGGER.error('Two posts are trying to generate {0}: {1} and {2}'.format( dest, @@ -1717,13 +1972,16 @@ class Nikola(object): quit = True self.post_per_file[dest] = post self.post_per_file[src_dest] = post + self.post_per_input_file[src_file] = post # deduplicate tags_per_language self.tags_per_language[lang] = list(set(self.tags_per_language[lang])) # Sort everything. for thing in self.timeline, self.posts, self.all_posts, self.pages: - thing.sort(key=lambda p: (p.date, p.source_path)) + thing.sort(key=lambda p: + (int(p.meta('priority')) if p.meta('priority') else 0, + p.date, p.source_path)) thing.reverse() self._sort_category_hierarchy() @@ -1738,38 +1996,38 @@ class Nikola(object): sys.exit(1) signal('scanned').send(self) - def generic_page_renderer(self, lang, post, filters, context=None): - """Render post fragments to final HTML pages.""" + def generic_renderer(self, lang, output_name, template_name, filters, file_deps=None, uptodate_deps=None, context=None, context_deps_remove=None, post_deps_dict=None, url_type=None): + """Helper function for rendering pages and post lists and other related pages. + + lang is the current language. + output_name is the destination file name. + template_name is the template to be used. + filters is the list of filters (usually site.config['FILTERS']) which will be used to post-process the result. + file_deps (optional) is a list of additional file dependencies (next to template and its dependencies). + uptodate_deps (optional) is a list of additional entries added to the task's uptodate list. + context (optional) a dict used as a basis for the template context. The lang parameter will always be added. + context_deps_remove (optional) is a list of keys to remove from the context after using it as an uptodate dependency. This should name all keys containing non-trivial Python objects; they can be replaced by adding JSON-style dicts in post_deps_dict. + post_deps_dict (optional) is a dict merged into the copy of context which is used as an uptodate dependency. + url_type (optional) allows to override the ``URL_TYPE`` configuration + """ utils.LocaleBorg().set_locale(lang) - context = context.copy() if context else {} - deps = post.deps(lang) + \ - self.template_system.template_deps(post.template_name) - deps.extend(utils.get_asset_path(x, self.THEMES) for x in ('bundles', 'parent', 'engine')) - deps = list(filter(None, deps)) - context['post'] = post - context['lang'] = lang - context['title'] = post.title(lang) - context['description'] = post.description(lang) - context['permalink'] = post.permalink(lang) - if 'pagekind' not in context: - context['pagekind'] = ['generic_page'] - if post.use_in_feeds: - context['enable_comments'] = True - else: - context['enable_comments'] = self.config['COMMENTS_IN_STORIES'] - extension = self.get_compiler(post.source_path).extension() - output_name = os.path.join(self.config['OUTPUT_FOLDER'], - post.destination_path(lang, extension)) + + file_deps = copy(file_deps) if file_deps else [] + file_deps += self.template_system.template_deps(template_name) + file_deps = sorted(list(filter(None, file_deps))) + + context = copy(context) if context else {} + context["lang"] = lang + deps_dict = copy(context) - deps_dict.pop('post') - if post.prev_post: - deps_dict['PREV_LINK'] = [post.prev_post.permalink(lang)] - if post.next_post: - deps_dict['NEXT_LINK'] = [post.next_post.permalink(lang)] + if context_deps_remove: + for key in context_deps_remove: + deps_dict.pop(key) deps_dict['OUTPUT_FOLDER'] = self.config['OUTPUT_FOLDER'] deps_dict['TRANSLATIONS'] = self.config['TRANSLATIONS'] deps_dict['global'] = self.GLOBAL_CONTEXT - deps_dict['comments'] = context['enable_comments'] + if post_deps_dict: + deps_dict.update(post_deps_dict) for k, v in self.GLOBAL_CONTEXT['template_hooks'].items(): deps_dict['||template_hooks|{0}||'.format(k)] = v._items @@ -1779,62 +2037,81 @@ class Nikola(object): deps_dict['navigation_links'] = deps_dict['global']['navigation_links'](lang) - if post: - deps_dict['post_translations'] = post.translated_to - task = { 'name': os.path.normpath(output_name), - 'file_dep': sorted(deps), 'targets': [output_name], - 'actions': [(self.render_template, [post.template_name, - output_name, context])], + 'file_dep': file_deps, + 'actions': [(self.render_template, [template_name, output_name, + context, url_type])], 'clean': True, - 'uptodate': [config_changed(deps_dict, 'nikola.nikola.Nikola.generic_page_renderer')] + post.deps_uptodate(lang), + 'uptodate': [config_changed(deps_dict, 'nikola.nikola.Nikola.generic_renderer')] + ([] if uptodate_deps is None else uptodate_deps) } - yield utils.apply_filters(task, filters) + return utils.apply_filters(task, filters) - def generic_post_list_renderer(self, lang, posts, output_name, - template_name, filters, extra_context): + def generic_page_renderer(self, lang, post, filters, context=None): + """Render post fragments to final HTML pages.""" + extension = self.get_compiler(post.source_path).extension() + output_name = os.path.join(self.config['OUTPUT_FOLDER'], + post.destination_path(lang, extension)) + + deps = post.deps(lang) + uptodate_deps = post.deps_uptodate(lang) + deps.extend(utils.get_asset_path(x, self.THEMES) for x in ('bundles', 'parent', 'engine')) + + context = copy(context) if context else {} + context['post'] = post + context['title'] = post.title(lang) + context['description'] = post.description(lang) + context['permalink'] = post.permalink(lang) + if 'pagekind' not in context: + context['pagekind'] = ['generic_page'] + if post.use_in_feeds: + context['enable_comments'] = True + else: + context['enable_comments'] = self.config['COMMENTS_IN_PAGES'] + + deps_dict = {} + if post.prev_post: + deps_dict['PREV_LINK'] = [post.prev_post.permalink(lang)] + if post.next_post: + deps_dict['NEXT_LINK'] = [post.next_post.permalink(lang)] + deps_dict['comments'] = context['enable_comments'] + if post: + deps_dict['post_translations'] = post.translated_to + + yield self.generic_renderer(lang, output_name, post.template_name, filters, + file_deps=deps, + uptodate_deps=uptodate_deps, + context=context, + context_deps_remove=['post'], + post_deps_dict=deps_dict) + + def generic_post_list_renderer(self, lang, posts, output_name, template_name, filters, extra_context): """Render pages with lists of posts.""" deps = [] - deps += self.template_system.template_deps(template_name) uptodate_deps = [] for post in posts: deps += post.deps(lang) uptodate_deps += post.deps_uptodate(lang) + context = {} context["posts"] = posts context["title"] = self.config['BLOG_TITLE'](lang) context["description"] = self.config['BLOG_DESCRIPTION'](lang) - context["lang"] = lang context["prevlink"] = None context["nextlink"] = None - context.update(extra_context) - deps_context = copy(context) - deps_context["posts"] = [(p.meta[lang]['title'], p.permalink(lang)) for p in - posts] - deps_context["global"] = self.GLOBAL_CONTEXT - - for k, v in self.GLOBAL_CONTEXT['template_hooks'].items(): - deps_context['||template_hooks|{0}||'.format(k)] = v._items - - for k in self._GLOBAL_CONTEXT_TRANSLATABLE: - deps_context[k] = deps_context['global'][k](lang) - - deps_context['navigation_links'] = deps_context['global']['navigation_links'](lang) + if extra_context: + context.update(extra_context) - task = { - 'name': os.path.normpath(output_name), - 'targets': [output_name], - 'file_dep': sorted(deps), - 'actions': [(self.render_template, [template_name, output_name, - context])], - 'clean': True, - 'uptodate': [config_changed(deps_context, 'nikola.nikola.Nikola.generic_post_list_renderer')] + uptodate_deps - } + post_deps_dict = {} + post_deps_dict["posts"] = [(p.meta[lang]['title'], p.permalink(lang)) for p in posts] - return utils.apply_filters(task, filters) + return self.generic_renderer(lang, output_name, template_name, filters, + file_deps=deps, + uptodate_deps=uptodate_deps, + context=context, + post_deps_dict=post_deps_dict) def atom_feed_renderer(self, lang, posts, output_path, filters, extra_context): @@ -1991,7 +2268,7 @@ class Nikola(object): entry_content.text = content for category in post.tags_for_language(lang): entry_category = lxml.etree.SubElement(entry_root, "category") - entry_category.set("term", utils.slugify(category)) + entry_category.set("term", utils.slugify(category, lang)) entry_category.set("label", category) dst_dir = os.path.dirname(output_path) @@ -2137,6 +2414,7 @@ class Nikola(object): "basename": basename, "name": atom_output_name, "file_dep": sorted([_.base_path for _ in post_list]), + "task_dep": ['render_posts'], "targets": [atom_output_name], "actions": [(self.atom_feed_renderer, (lang, @@ -2164,7 +2442,7 @@ class Nikola(object): def __repr__(self): """Representation of a Nikola site.""" - return '<Nikola Site: {0!r}>'.format(self.config['BLOG_TITLE']()) + return '<Nikola Site: {0!r}>'.format(self.config['BLOG_TITLE'](self.config['DEFAULT_LANG'])) def sanitized_locales(locale_fallback, locale_default, locales, translations): @@ -2239,6 +2517,9 @@ def sanitized_locales(locale_fallback, locale_default, locales, translations): locale_n = locale_fallback msg = "Could not guess locale for language {0}, using locale {1}" utils.LOGGER.warn(msg.format(lang, locale_n)) + utils.LOGGER.warn("Please fix your OS locale configuration or use the LOCALES option in conf.py to specify your preferred locale.") + if sys.platform != 'win32': + utils.LOGGER.warn("Make sure to use an UTF-8 locale to ensure Unicode support.") locales[lang] = locale_n return locale_fallback, locale_default, locales diff --git a/nikola/packages/README.md b/nikola/packages/README.md index 156d43f..ad94b00 100644 --- a/nikola/packages/README.md +++ b/nikola/packages/README.md @@ -3,3 +3,5 @@ We ship some third-party things with Nikola. They live here, along with their l Packages: * tzlocal by Lennart Regebro, CC0 license (modified) + * datecond by Chris Warrick (Nikola contributor), 3-clause BSD license + (modified) diff --git a/nikola/packages/datecond/LICENSE b/nikola/packages/datecond/LICENSE new file mode 100644 index 0000000..5e8b6d6 --- /dev/null +++ b/nikola/packages/datecond/LICENSE @@ -0,0 +1,30 @@ +Copyright © 2016, Chris Warrick. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the author of this software nor the names of + contributors to this software may be used to endorse or promote + products derived from this software without specific prior written + consent. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/nikola/packages/datecond/__init__.py b/nikola/packages/datecond/__init__.py new file mode 100644 index 0000000..b409057 --- /dev/null +++ b/nikola/packages/datecond/__init__.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# Date Conditionals (datecond) +# Version 0.1.2 +# Copyright © 2015-2016, Chris Warrick. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions, and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the author of this software nor the names of +# contributors to this software may be used to endorse or promote +# products derived from this software without specific prior written +# consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Date range parser.""" + +from __future__ import print_function, unicode_literals +import dateutil.parser +import re +import operator + + +__all__ = ('date_in_range',) +CLAUSE = re.compile('(year|month|day|hour|minute|second|weekday|isoweekday)?' + ' ?(==|!=|<=|>=|<|>) ?(.*)') +OPERATORS = { + '==': operator.eq, + '!=': operator.ne, + '<=': operator.le, + '>=': operator.ge, + '<': operator.lt, + '>': operator.gt, +} + + +def date_in_range(date_range, date, debug=True): + """Check if date is in the range specified. + + Format: + * comma-separated clauses (AND) + * clause: attribute comparison_operator value (spaces optional) + * attribute: year, month, day, hour, month, second, weekday, isoweekday + or empty for full datetime + * comparison_operator: == != <= >= < > + * value: integer or dateutil-compatible date input + """ + out = True + + for item in date_range.split(','): + attribute, comparison_operator, value = CLAUSE.match( + item.strip()).groups() + if attribute in ('weekday', 'isoweekday'): + left = getattr(date, attribute)() + right = int(value) + elif attribute: + left = getattr(date, attribute) + right = int(value) + else: + left = date + right = dateutil.parser.parse(value) + if debug: # pragma: no cover + print(" <{0} {1} {2}>".format(left, comparison_operator, right)) + out = out and OPERATORS[comparison_operator](left, right) + return out diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py index b96de4f..4b4f956 100644 --- a/nikola/plugin_categories.py +++ b/nikola/plugin_categories.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -30,6 +30,7 @@ from __future__ import absolute_import import sys import os import re +import io from yapsy.IPlugin import IPlugin from doit.cmd_base import Command as DoitCommand @@ -81,6 +82,10 @@ class BasePlugin(IPlugin): """Add 'dependency' to the target task's task_deps.""" self.site.injected_deps[target].append(dependency) + def get_deps(self, filename): + """Find the dependencies for a file.""" + return [] + class PostScanner(BasePlugin): """The scan method of these plugins is called by Nikola.scan_posts.""" @@ -199,6 +204,14 @@ class TemplateSystem(BasePlugin): """Return filenames which are dependencies for a template.""" raise NotImplementedError() + def get_deps(self, filename): + """Return paths to dependencies for the template loaded from filename.""" + raise NotImplementedError() + + def get_string_deps(self, text): + """Find dependencies for a template string.""" + raise NotImplementedError() + def render_template(self, template_name, output_name, context): """Render template to a file using context. @@ -215,6 +228,10 @@ class TemplateSystem(BasePlugin): """Inject the directory with the lowest priority in the template search mechanism.""" raise NotImplementedError() + def get_template_path(self, template_name): + """Get the path to a template or return None.""" + raise NotImplementedError() + class TaskMultiplier(BasePlugin): """Take a task and return *more* tasks.""" @@ -245,13 +262,18 @@ class PageCompiler(BasePlugin): } config_dependencies = [] - def register_extra_dependencies(self, post): - """Add additional dependencies to the post object. + def _read_extra_deps(self, post): + """Read contents of .dep file and return them as a list.""" + dep_path = post.base_path + '.dep' + if os.path.isfile(dep_path): + with io.open(dep_path, 'r+', encoding='utf8') as depf: + deps = [l.strip() for l in depf.readlines()] + return deps + return [] - Current main use is the ReST page compiler, which puts extra - dependencies into a .dep file. - """ - pass + def register_extra_dependencies(self, post): + """Add dependency to post object to check .dep file.""" + post.add_dependency(lambda: self._read_extra_deps(post), 'fragment') def compile_html(self, source, dest, is_two_file=False): """Compile the source, save it on dest.""" @@ -334,6 +356,12 @@ class ConfigPlugin(BasePlugin): name = "dummy_config_plugin" +class ShortcodePlugin(BasePlugin): + """A plugin that adds a shortcode.""" + + name = "dummy_shortcode_plugin" + + class Importer(Command): """Basic structure for importing data into Nikola. @@ -397,7 +425,7 @@ class Importer(Command): """Go through self.items and save them.""" def import_story(self): - """Create a story.""" + """Create a page.""" raise NotImplementedError() def import_post(self): diff --git a/nikola/plugins/basic_import.py b/nikola/plugins/basic_import.py index 04f1091..cf98ebc 100644 --- a/nikola/plugins/basic_import.py +++ b/nikola/plugins/basic_import.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -76,8 +76,11 @@ class ImportMixin(object): return channel @staticmethod - def configure_redirections(url_map): + def configure_redirections(url_map, base_dir=''): """Configure redirections from an url_map.""" + index = base_dir + 'index.html' + if index.startswith('/'): + index = index[1:] redirections = [] for k, v in url_map.items(): if not k[-1] == '/': @@ -86,11 +89,10 @@ class ImportMixin(object): # remove the initial "/" because src is a relative file path src = (urlparse(k).path + 'index.html')[1:] dst = (urlparse(v).path) - if src == 'index.html': + if src == index: utils.LOGGER.warn("Can't do a redirect for: {0!r}".format(k)) else: redirections.append((src, dst)) - return redirections def generate_base_site(self): @@ -125,9 +127,12 @@ class ImportMixin(object): def write_content(cls, filename, content, rewrite_html=True): """Write content to file.""" if rewrite_html: - doc = html.document_fromstring(content) - doc.rewrite_links(replacer) - content = html.tostring(doc, encoding='utf8') + try: + doc = html.document_fromstring(content) + doc.rewrite_links(replacer) + content = html.tostring(doc, encoding='utf8') + except etree.ParserError: + content = content.encode('utf-8') else: content = content.encode('utf-8') @@ -135,6 +140,24 @@ class ImportMixin(object): with open(filename, "wb+") as fd: fd.write(content) + @classmethod + def write_post(cls, filename, content, headers, compiler, rewrite_html=True): + """Ask the specified compiler to write the post to disk.""" + if rewrite_html: + try: + doc = html.document_fromstring(content) + doc.rewrite_links(replacer) + content = html.tostring(doc, encoding='utf8') + except etree.ParserError: + pass + if isinstance(content, utils.bytes_str): + content = content.decode('utf-8') + compiler.create_post( + filename, + content=content, + onefile=True, + **headers) + @staticmethod def write_metadata(filename, title, slug, post_date, description, tags, **kwargs): """Write metadata to meta file.""" diff --git a/nikola/plugins/command/__init__.py b/nikola/plugins/command/__init__.py index 2aa5267..62d7086 100644 --- a/nikola/plugins/command/__init__.py +++ b/nikola/plugins/command/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/command/auto/__init__.py b/nikola/plugins/command/auto/__init__.py index e339c06..a82dc3e 100644 --- a/nikola/plugins/command/auto/__init__.py +++ b/nikola/plugins/command/auto/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -43,6 +43,7 @@ except ImportError: import webbrowser from wsgiref.simple_server import make_server import wsgiref.util +import pkg_resources from blinker import signal try: @@ -61,7 +62,6 @@ except ImportError: FileSystemEventHandler = object PatternMatchingEventHandler = object - from nikola.plugin_categories import Command from nikola.utils import dns_sd, req_missing, get_logger, get_theme_path, STDERR_HANDLER LRJS_PATH = os.path.join(os.path.dirname(__file__), 'livereload.js') @@ -102,7 +102,7 @@ class CommandAuto(Command): 'long': 'address', 'type': str, 'default': '127.0.0.1', - 'help': 'Address to bind (default: 127.0.0.1 – localhost)', + 'help': 'Address to bind (default: 127.0.0.1 -- localhost)', }, { 'name': 'browser', @@ -143,7 +143,7 @@ class CommandAuto(Command): self.cmd_arguments = ['nikola', 'build'] if self.site.configuration_filename != 'conf.py': - self.cmd_arguments = ['--conf=' + self.site.configuration_filename] + self.cmd_arguments + self.cmd_arguments.append('--conf=' + self.site.configuration_filename) # Run an initial build so we are up-to-date subprocess.call(self.cmd_arguments) @@ -157,7 +157,7 @@ class CommandAuto(Command): # Do not duplicate entries -- otherwise, multiple rebuilds are triggered watched = set([ - 'templates/', 'plugins/', + 'templates/' ] + [get_theme_path(name) for name in self.site.THEMES]) for item in self.site.config['post_pages']: watched.add(os.path.dirname(item[0])) @@ -167,6 +167,10 @@ class CommandAuto(Command): watched.add(item) for item in self.site.config['LISTINGS_FOLDERS']: watched.add(item) + for item in self.site._plugin_places: + watched.add(item) + # Nikola itself (useful for developers) + watched.add(pkg_resources.resource_filename('nikola', '')) out_folder = self.site.config['OUTPUT_FOLDER'] if options and options.get('browser'): @@ -298,8 +302,8 @@ class CommandAuto(Command): mimetype = 'text/html' if uri.endswith('/') else mimetypes.guess_type(uri)[0] or 'application/octet-stream' if os.path.isdir(f_path): - if not f_path.endswith('/'): # Redirect to avoid breakage - start_response('301 Redirect', [('Location', p_uri.path + '/')]) + if not p_uri.path.endswith('/'): # Redirect to avoid breakage + start_response('301 Moved Permanently', [('Location', p_uri.path + '/')]) return [] f_path = os.path.join(f_path, self.site.config['INDEX_FILE']) mimetype = 'text/html' diff --git a/nikola/plugins/command/bootswatch_theme.py b/nikola/plugins/command/bootswatch_theme.py index afd15af..4808fdb 100644 --- a/nikola/plugins/command/bootswatch_theme.py +++ b/nikola/plugins/command/bootswatch_theme.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -36,6 +36,13 @@ from nikola import utils LOGGER = utils.get_logger('bootswatch_theme', utils.STDERR_HANDLER) +def _check_for_theme(theme, themes): + for t in themes: + if t.endswith(os.sep + theme): + return True + return False + + class CommandBootswatchTheme(Command): """Given a swatch name from bootswatch.com and a parent theme, creates a custom theme.""" @@ -79,12 +86,12 @@ class CommandBootswatchTheme(Command): version = '' # See if we need bootswatch for bootstrap v2 or v3 - themes = utils.get_theme_chain(parent) - if 'bootstrap3' not in themes and 'bootstrap3-jinja' not in themes: + themes = utils.get_theme_chain(parent, self.site.themes_dirs) + if not _check_for_theme('bootstrap3', themes) and not _check_for_theme('bootstrap3-jinja', themes): version = '2' - elif 'bootstrap' not in themes and 'bootstrap-jinja' not in themes: + elif not _check_for_theme('bootstrap', themes) and not _check_for_theme('bootstrap-jinja', themes): LOGGER.warn('"bootswatch_theme" only makes sense for themes that use bootstrap') - elif 'bootstrap3-gradients' in themes or 'bootstrap3-gradients-jinja' in themes: + elif _check_for_theme('bootstrap3-gradients', themes) or _check_for_theme('bootstrap3-gradients-jinja', themes): LOGGER.warn('"bootswatch_theme" doesn\'t work well with the bootstrap3-gradients family') LOGGER.info("Creating '{0}' theme from '{1}' and '{2}'".format(name, swatch, parent)) diff --git a/nikola/plugins/command/check.py b/nikola/plugins/command/check.py index bfc6ee2..0141a6b 100644 --- a/nikola/plugins/command/check.py +++ b/nikola/plugins/command/check.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -32,6 +32,7 @@ import os import re import sys import time +import logbook try: from urllib import unquote from urlparse import urlparse, urljoin, urldefrag @@ -164,9 +165,9 @@ class CommandCheck(Command): print(self.help()) return False if options['verbose']: - self.logger.level = 1 + self.logger.level = logbook.DEBUG else: - self.logger.level = 4 + self.logger.level = logbook.NOTICE failure = False if options['links']: failure |= self.scan_links(options['find_sources'], options['remote']) @@ -245,7 +246,7 @@ class CommandCheck(Command): target = urldefrag(target)[0] if any([urlparse(target).netloc.endswith(_) for _ in ['example.com', 'example.net', 'example.org']]): - self.logger.info("Not testing example address \"{0}\".".format(target)) + self.logger.debug("Not testing example address \"{0}\".".format(target)) continue # absolute URL to root-relative @@ -274,7 +275,7 @@ class CommandCheck(Command): if self.checked_remote_targets[target] in [301, 308]: self.logger.warn("Remote link PERMANENTLY redirected in {0}: {1} [Error {2}]".format(filename, target, self.checked_remote_targets[target])) elif self.checked_remote_targets[target] in [302, 307]: - self.logger.notice("Remote link temporarily redirected in {1}: {2} [HTTP: {3}]".format(filename, target, self.checked_remote_targets[target])) + self.logger.debug("Remote link temporarily redirected in {0}: {1} [HTTP: {2}]".format(filename, target, self.checked_remote_targets[target])) elif self.checked_remote_targets[target] > 399: self.logger.error("Broken link in {0}: {1} [Error {2}]".format(filename, target, self.checked_remote_targets[target])) continue @@ -302,7 +303,7 @@ class CommandCheck(Command): if redir_status_code in [301, 308]: self.logger.warn("Remote link moved PERMANENTLY to \"{0}\" and should be updated in {1}: {2} [HTTP: {3}]".format(resp.url, filename, target, redir_status_code)) if redir_status_code in [302, 307]: - self.logger.notice("Remote link temporarily redirected to \"{0}\" in {1}: {2} [HTTP: {3}]".format(resp.url, filename, target, redir_status_code)) + self.logger.debug("Remote link temporarily redirected to \"{0}\" in {1}: {2} [HTTP: {3}]".format(resp.url, filename, target, redir_status_code)) self.checked_remote_targets[resp.url] = resp.status_code self.checked_remote_targets[target] = redir_status_code else: @@ -343,7 +344,7 @@ class CommandCheck(Command): elif target_filename not in self.existing_targets: if os.path.exists(target_filename): - self.logger.info(u"Good link {0} => {1}".format(target, target_filename)) + self.logger.info("Good link {0} => {1}".format(target, target_filename)) self.existing_targets.add(target_filename) else: rv = True @@ -358,9 +359,9 @@ class CommandCheck(Command): def scan_links(self, find_sources=False, check_remote=False): """Check links on the site.""" - self.logger.info("Checking Links:") - self.logger.info("===============\n") - self.logger.notice("{0} mode".format(self.site.config['URL_TYPE'])) + self.logger.debug("Checking Links:") + self.logger.debug("===============\n") + self.logger.debug("{0} mode".format(self.site.config['URL_TYPE'])) failure = False # Maybe we should just examine all HTML files output_folder = self.site.config['OUTPUT_FOLDER'] @@ -380,14 +381,14 @@ class CommandCheck(Command): if self.analyze(fname, find_sources, False): failure = True if not failure: - self.logger.info("All links checked.") + self.logger.debug("All links checked.") return failure def scan_files(self): """Check files in the site, find missing and orphaned files.""" failure = False - self.logger.info("Checking Files:") - self.logger.info("===============\n") + self.logger.debug("Checking Files:") + self.logger.debug("===============\n") only_on_output, only_on_input = real_scan_files(self.site, self.cache) # Ignore folders @@ -406,16 +407,18 @@ class CommandCheck(Command): for f in only_on_input: self.logger.warn(f) if not failure: - self.logger.info("All files checked.") + self.logger.debug("All files checked.") return failure def clean_files(self): """Remove orphaned files.""" only_on_output, _ = real_scan_files(self.site, self.cache) for f in only_on_output: - self.logger.info('removed: {0}'.format(f)) + self.logger.debug('removed: {0}'.format(f)) os.unlink(f) + warn_flag = bool(only_on_output) + # Find empty directories and remove them output_folder = self.site.config['OUTPUT_FOLDER'] all_dirs = [] @@ -425,7 +428,12 @@ class CommandCheck(Command): for d in all_dirs: try: os.rmdir(d) - self.logger.info('removed: {0}/'.format(d)) + self.logger.debug('removed: {0}/'.format(d)) + warn_flag = True except OSError: pass + + if warn_flag: + self.logger.warn('Some files or directories have been removed, your site may need rebuilding') + return True diff --git a/nikola/plugins/command/console.py b/nikola/plugins/command/console.py index 3d3daab..c6a8376 100644 --- a/nikola/plugins/command/console.py +++ b/nikola/plugins/command/console.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Chris Warrick, Roberto Alsina and others. +# Copyright © 2012-2016 Chris Warrick, Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -75,7 +75,7 @@ If there is no console to use specified (as -b, -i, -p) it tries IPython, then f ] def ipython(self, willful=True): - """IPython shell.""" + """Run an IPython shell.""" try: import IPython except ImportError as e: @@ -84,12 +84,13 @@ If there is no console to use specified (as -b, -i, -p) it tries IPython, then f raise e # That’s how _execute knows whether to try something else. else: site = self.context['site'] # NOQA + nikola_site = self.context['site'] # NOQA conf = self.context['conf'] # NOQA commands = self.context['commands'] # NOQA IPython.embed(header=self.header.format('IPython')) def bpython(self, willful=True): - """bpython shell.""" + """Run a bpython shell.""" try: import bpython except ImportError as e: @@ -100,7 +101,7 @@ If there is no console to use specified (as -b, -i, -p) it tries IPython, then f bpython.embed(banner=self.header.format('bpython'), locals_=self.context) def plain(self, willful=True): - """Plain Python shell.""" + """Run a plain Python shell.""" import code try: import readline @@ -130,6 +131,7 @@ If there is no console to use specified (as -b, -i, -p) it tries IPython, then f self.context = { 'conf': self.site.config, 'site': self.site, + 'nikola_site': self.site, 'commands': self.site.commands, } if options['bpython']: diff --git a/nikola/plugins/command/deploy.py b/nikola/plugins/command/deploy.py index 757c0d2..c2289e8 100644 --- a/nikola/plugins/command/deploy.py +++ b/nikola/plugins/command/deploy.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -30,6 +30,7 @@ from __future__ import print_function import io from datetime import datetime from dateutil.tz import gettz +import dateutil import os import subprocess import time @@ -37,7 +38,7 @@ import time from blinker import signal from nikola.plugin_categories import Command -from nikola.utils import get_logger, remove_file, unicode_str, makedirs, STDERR_HANDLER +from nikola.utils import get_logger, clean_before_deployment, STDERR_HANDLER class CommandDeploy(Command): @@ -45,7 +46,7 @@ class CommandDeploy(Command): name = "deploy" - doc_usage = "[[preset [preset...]]" + doc_usage = "[preset [preset...]]" doc_purpose = "deploy the site" doc_description = "Deploy the site by executing deploy commands from the presets listed on the command line. If no presets are specified, `default` is executed." logger = None @@ -55,27 +56,42 @@ class CommandDeploy(Command): self.logger = get_logger('deploy', STDERR_HANDLER) # Get last successful deploy date timestamp_path = os.path.join(self.site.config['CACHE_FOLDER'], 'lastdeploy') + + # Get last-deploy from persistent state + last_deploy = self.site.state.get('last_deploy') + if last_deploy is None: + # If there is a last-deploy saved, move it to the new state persistence thing + # FIXME: remove in Nikola 8 + if os.path.isfile(timestamp_path): + try: + with io.open(timestamp_path, 'r', encoding='utf8') as inf: + last_deploy = dateutil.parser.parse(inf.read()) + clean = False + except (IOError, Exception) as e: + self.logger.debug("Problem when reading `{0}`: {1}".format(timestamp_path, e)) + last_deploy = datetime(1970, 1, 1) + clean = True + os.unlink(timestamp_path) # Remove because from now on it's in state + else: # Just a default + last_deploy = datetime(1970, 1, 1) + clean = True + else: + last_deploy = dateutil.parser.parse(last_deploy) + clean = False + if self.site.config['COMMENT_SYSTEM_ID'] == 'nikolademo': self.logger.warn("\nWARNING WARNING WARNING WARNING\n" "You are deploying using the nikolademo Disqus account.\n" "That means you will not be able to moderate the comments in your own site.\n" "And is probably not what you want to do.\n" - "Think about it for 5 seconds, I'll wait :-)\n\n") + "Think about it for 5 seconds, I'll wait :-)\n" + "(press Ctrl+C to abort)\n") time.sleep(5) - deploy_drafts = self.site.config.get('DEPLOY_DRAFTS', True) - deploy_future = self.site.config.get('DEPLOY_FUTURE', False) - undeployed_posts = [] - if not (deploy_drafts and deploy_future): - # Remove drafts and future posts - out_dir = self.site.config['OUTPUT_FOLDER'] - self.site.scan_posts() - for post in self.site.timeline: - if (not deploy_drafts and post.is_draft) or \ - (not deploy_future and post.publish_later): - remove_file(os.path.join(out_dir, post.destination_path())) - remove_file(os.path.join(out_dir, post.source_path)) - undeployed_posts.append(post) + # Remove drafts and future posts if requested + undeployed_posts = clean_before_deployment(self.site) + if undeployed_posts: + self.logger.notice("Deleted {0} posts due to DEPLOY_* settings".format(len(undeployed_posts))) if args: presets = args @@ -97,27 +113,22 @@ class CommandDeploy(Command): try: subprocess.check_call(command, shell=True) except subprocess.CalledProcessError as e: - self.logger.error('Failed deployment — command {0} ' + self.logger.error('Failed deployment -- command {0} ' 'returned {1}'.format(e.cmd, e.returncode)) return e.returncode self.logger.info("Successful deployment") - try: - with io.open(timestamp_path, 'r', encoding='utf8') as inf: - last_deploy = datetime.strptime(inf.read().strip(), "%Y-%m-%dT%H:%M:%S.%f") - clean = False - except (IOError, Exception) as e: - self.logger.debug("Problem when reading `{0}`: {1}".format(timestamp_path, e)) - last_deploy = datetime(1970, 1, 1) - clean = True new_deploy = datetime.utcnow() self._emit_deploy_event(last_deploy, new_deploy, clean, undeployed_posts) - makedirs(self.site.config['CACHE_FOLDER']) # Store timestamp of successful deployment - with io.open(timestamp_path, 'w+', encoding='utf8') as outf: - outf.write(unicode_str(new_deploy.isoformat())) + self.site.state.set('last_deploy', new_deploy.isoformat()) + if clean: + self.logger.info( + 'Looks like this is the first time you deployed this site. ' + 'Let us know you are using Nikola ' + 'at <https://users.getnikola.com/add/> if you want!') def _emit_deploy_event(self, last_deploy, new_deploy, clean=False, undeployed=None): """Emit events for all timeline entries newer than last deploy. diff --git a/nikola/plugins/command/github_deploy.py b/nikola/plugins/command/github_deploy.py index 2fe0d4e..b5ad322 100644 --- a/nikola/plugins/command/github_deploy.py +++ b/nikola/plugins/command/github_deploy.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2014-2015 Puneeth Chaganti and others. +# Copyright © 2014-2016 Puneeth Chaganti and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -27,15 +27,13 @@ """Deploy site to GitHub Pages.""" from __future__ import print_function -from datetime import datetime -import io import os import subprocess from textwrap import dedent from nikola.plugin_categories import Command from nikola.plugins.command.check import real_scan_files -from nikola.utils import get_logger, req_missing, makedirs, unicode_str, STDERR_HANDLER +from nikola.utils import get_logger, req_missing, clean_before_deployment, STDERR_HANDLER from nikola.__main__ import main from nikola import __version__ @@ -53,7 +51,7 @@ def check_ghp_import_installed(): except OSError: # req_missing defaults to `python=True` — and it’s meant to be like this. # `ghp-import` is installed via pip, but the only way to use it is by executing the script it installs. - req_missing(['ghp-import'], 'deploy the site to GitHub Pages') + req_missing(['ghp-import2'], 'deploy the site to GitHub Pages') class CommandGitHubDeploy(Command): @@ -61,7 +59,7 @@ class CommandGitHubDeploy(Command): name = 'github_deploy' - doc_usage = '' + doc_usage = '[-m COMMIT_MESSAGE]' doc_purpose = 'deploy the site to GitHub Pages' doc_description = dedent( """\ @@ -71,10 +69,19 @@ class CommandGitHubDeploy(Command): """ ) - + cmd_options = [ + { + 'name': 'commit_message', + 'short': 'm', + 'long': 'message', + 'default': 'Nikola auto commit.', + 'type': str, + 'help': 'Commit message (default: Nikola auto commit.)', + }, + ] logger = None - def _execute(self, command, args): + def _execute(self, options, args): """Run the deployment.""" self.logger = get_logger(CommandGitHubDeploy.name, STDERR_HANDLER) @@ -92,41 +99,69 @@ class CommandGitHubDeploy(Command): for f in only_on_output: os.unlink(f) + # Remove drafts and future posts if requested (Issue #2406) + undeployed_posts = clean_before_deployment(self.site) + if undeployed_posts: + self.logger.notice("Deleted {0} posts due to DEPLOY_* settings".format(len(undeployed_posts))) + # Commit and push - self._commit_and_push() + self._commit_and_push(options['commit_message']) return - def _commit_and_push(self): - """Commit all the files and push.""" - source = self.site.config['GITHUB_SOURCE_BRANCH'] - deploy = self.site.config['GITHUB_DEPLOY_BRANCH'] - remote = self.site.config['GITHUB_REMOTE_NAME'] - source_commit = uni_check_output(['git', 'rev-parse', source]) - commit_message = ( - 'Nikola auto commit.\n\n' - 'Source commit: %s' - 'Nikola version: %s' % (source_commit, __version__) - ) - output_folder = self.site.config['OUTPUT_FOLDER'] - - command = ['ghp-import', '-n', '-m', commit_message, '-p', '-r', remote, '-b', deploy, output_folder] - + def _run_command(self, command, xfail=False): + """Run a command that may or may not fail.""" self.logger.info("==> {0}".format(command)) try: subprocess.check_call(command) + return 0 except subprocess.CalledProcessError as e: + if xfail: + return e.returncode self.logger.error( - 'Failed GitHub deployment — command {0} ' + 'Failed GitHub deployment -- command {0} ' 'returned {1}'.format(e.cmd, e.returncode) ) - return e.returncode + raise SystemError(e.returncode) - self.logger.info("Successful deployment") + def _commit_and_push(self, commit_first_line): + """Commit all the files and push.""" + source = self.site.config['GITHUB_SOURCE_BRANCH'] + deploy = self.site.config['GITHUB_DEPLOY_BRANCH'] + remote = self.site.config['GITHUB_REMOTE_NAME'] + autocommit = self.site.config['GITHUB_COMMIT_SOURCE'] + try: + if autocommit: + commit_message = ( + '{0}\n\n' + 'Nikola version: {1}'.format(commit_first_line, __version__) + ) + e = self._run_command(['git', 'checkout', source], True) + if e != 0: + self._run_command(['git', 'checkout', '-b', source]) + self._run_command(['git', 'add', '.']) + # Figure out if there is anything to commit + e = self._run_command(['git', 'diff-index', '--quiet', 'HEAD'], True) + if e != 0: + self._run_command(['git', 'commit', '-am', commit_message]) + else: + self.logger.notice('Nothing to commit to source branch.') + + source_commit = uni_check_output(['git', 'rev-parse', source]) + commit_message = ( + '{0}\n\n' + 'Source commit: {1}' + 'Nikola version: {2}'.format(commit_first_line, source_commit, __version__) + ) + output_folder = self.site.config['OUTPUT_FOLDER'] - # Store timestamp of successful deployment - timestamp_path = os.path.join(self.site.config["CACHE_FOLDER"], "lastdeploy") - new_deploy = datetime.utcnow() - makedirs(self.site.config["CACHE_FOLDER"]) - with io.open(timestamp_path, "w+", encoding="utf8") as outf: - outf.write(unicode_str(new_deploy.isoformat())) + command = ['ghp-import', '-n', '-m', commit_message, '-p', '-r', remote, '-b', deploy, output_folder] + + self._run_command(command) + + if autocommit: + self._run_command(['git', 'push', '-u', remote, source]) + except SystemError as e: + return e.args[0] + + self.logger.info("Successful deployment") diff --git a/nikola/plugins/command/import_wordpress.py b/nikola/plugins/command/import_wordpress.py index 69ef144..0b48583 100644 --- a/nikola/plugins/command/import_wordpress.py +++ b/nikola/plugins/command/import_wordpress.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -38,6 +38,11 @@ from lxml import etree from collections import defaultdict try: + import html2text +except: + html2text = None + +try: from urlparse import urlparse from urllib import unquote except ImportError: @@ -170,6 +175,20 @@ class CommandImportWordpress(Command, ImportMixin): 'help': "Export comments as .wpcomment files", }, { + 'name': 'html2text', + 'long': 'html2text', + 'default': False, + 'type': bool, + 'help': "Uses html2text (needs to be installed with pip) to transform WordPress posts to MarkDown during import", + }, + { + 'name': 'transform_to_markdown', + 'long': 'transform-to-markdown', + 'default': False, + 'type': bool, + 'help': "Uses WordPress page compiler to transform WordPress posts to HTML and then use html2text to transform them to MarkDown during import", + }, + { 'name': 'transform_to_html', 'long': 'transform-to-html', 'default': False, @@ -191,14 +210,35 @@ class CommandImportWordpress(Command, ImportMixin): 'help': "Automatically installs the WordPress page compiler (either locally or in the new site) if required by other options.\nWarning: the compiler is GPL software!", }, { - 'name': 'tag_saniziting_strategy', - 'long': 'tag-saniziting-strategy', + 'name': 'tag_sanitizing_strategy', + 'long': 'tag-sanitizing-strategy', 'default': 'first', 'help': 'lower: Convert all tag and category names to lower case\nfirst: Keep first spelling of tag or category name', }, + { + 'name': 'one_file', + 'long': 'one-file', + 'default': False, + 'type': bool, + 'help': "Save imported posts in the more modern one-file format.", + }, ] all_tags = set([]) + def _get_compiler(self): + """Return whatever compiler we will use.""" + self._find_wordpress_compiler() + if self.wordpress_page_compiler is not None: + return self.wordpress_page_compiler + plugin_info = self.site.plugin_manager.getPluginByName('markdown', 'PageCompiler') + if plugin_info is not None: + if not plugin_info.is_activated: + self.site.plugin_manager.activatePluginByName(plugin_info.name) + plugin_info.plugin_object.set_site(self.site) + return plugin_info.plugin_object + else: + LOGGER.error("Can't find markdown post compiler.") + def _find_wordpress_compiler(self): """Find WordPress compiler plugin.""" if self.wordpress_page_compiler is not None: @@ -223,6 +263,8 @@ class CommandImportWordpress(Command, ImportMixin): 'putting these arguments before the filename if you ' 'are running into problems.'.format(args)) + self.onefile = options.get('one_file', False) + self.import_into_existing_site = False self.url_map = {} self.timezone = None @@ -239,6 +281,9 @@ class CommandImportWordpress(Command, ImportMixin): self.export_categories_as_categories = options.get('export_categories_as_categories', False) self.export_comments = options.get('export_comments', False) + self.html2text = options.get('html2text', False) + self.transform_to_markdown = options.get('transform_to_markdown', False) + self.transform_to_html = options.get('transform_to_html', False) self.use_wordpress_compiler = options.get('use_wordpress_compiler', False) self.install_wordpress_compiler = options.get('install_wordpress_compiler', False) @@ -257,10 +302,18 @@ class CommandImportWordpress(Command, ImportMixin): self.separate_qtranslate_content = options.get('separate_qtranslate_content') self.translations_pattern = options.get('translations_pattern') - if self.transform_to_html and self.use_wordpress_compiler: - LOGGER.warn("It does not make sense to combine --transform-to-html with --use-wordpress-compiler, as the first converts all posts to HTML and the latter option affects zero posts.") + count = (1 if self.html2text else 0) + (1 if self.transform_to_html else 0) + (1 if self.transform_to_markdown else 0) + if count > 1: + LOGGER.error("You can use at most one of the options --html2text, --transform-to-html and --transform-to-markdown.") + return False + if (self.html2text or self.transform_to_html or self.transform_to_markdown) and self.use_wordpress_compiler: + LOGGER.warn("It does not make sense to combine --use-wordpress-compiler with any of --html2text, --transform-to-html and --transform-to-markdown, as the latter convert all posts to HTML and the first option then affects zero posts.") + + if (self.html2text or self.transform_to_markdown) and not html2text: + LOGGER.error("You need to install html2text via 'pip install html2text' before you can use the --html2text and --transform-to-markdown options.") + return False - if self.transform_to_html: + if self.transform_to_html or self.transform_to_markdown: self._find_wordpress_compiler() if not self.wordpress_page_compiler and self.install_wordpress_compiler: if not install_plugin(self.site, 'wordpress_compiler', output_dir='plugins'): # local install @@ -334,7 +387,7 @@ class CommandImportWordpress(Command, ImportMixin): self.context['TRANSLATIONS'] = format_default_translations_config( self.extra_languages) self.context['REDIRECTIONS'] = self.configure_redirections( - self.url_map) + self.url_map, self.base_dir) if self.timezone: self.context['TIMEZONE'] = self.timezone if self.export_categories_as_categories: @@ -350,7 +403,7 @@ class CommandImportWordpress(Command, ImportMixin): tag_str = tag except AttributeError: tag_str = tag - tag = utils.slugify(tag_str) + tag = utils.slugify(tag_str, self.lang) src_url = '{}tag/{}'.format(self.context['SITE_URL'], tag) dst_url = self.site.link('tag', tag) if src_url != dst_url: @@ -382,7 +435,7 @@ class CommandImportWordpress(Command, ImportMixin): if b'<atom:link rel=' in line: continue xml.append(line) - return b'\n'.join(xml) + return b''.join(xml) @classmethod def get_channel_from_file(cls, filename): @@ -396,7 +449,8 @@ class CommandImportWordpress(Command, ImportMixin): wordpress_namespace = channel.nsmap['wp'] context = SAMPLE_CONF.copy() - context['DEFAULT_LANG'] = get_text_tag(channel, 'language', 'en')[:2] + self.lang = get_text_tag(channel, 'language', 'en')[:2] + context['DEFAULT_LANG'] = self.lang context['TRANSLATIONS_PATTERN'] = DEFAULT_TRANSLATIONS_PATTERN context['BLOG_TITLE'] = get_text_tag(channel, 'title', 'PUT TITLE HERE') @@ -428,7 +482,7 @@ class CommandImportWordpress(Command, ImportMixin): PAGES = '(\n' for extension in extensions: POSTS += ' ("posts/*.{0}", "posts", "post.tmpl"),\n'.format(extension) - PAGES += ' ("stories/*.{0}", "stories", "story.tmpl"),\n'.format(extension) + PAGES += ' ("pages/*.{0}", "pages", "story.tmpl"),\n'.format(extension) POSTS += ')\n' PAGES += ')\n' context['POSTS'] = POSTS @@ -446,9 +500,6 @@ class CommandImportWordpress(Command, ImportMixin): def download_url_content_to_file(self, url, dst_path): """Download some content (attachments) to a file.""" - if self.no_downloads: - return - try: request = requests.get(url, auth=self.auth) if request.status_code >= 400: @@ -468,10 +519,13 @@ class CommandImportWordpress(Command, ImportMixin): 'foo') path = urlparse(url).path dst_path = os.path.join(*([self.output_folder, 'files'] + list(path.split('/')))) - dst_dir = os.path.dirname(dst_path) - utils.makedirs(dst_dir) - LOGGER.info("Downloading {0} => {1}".format(url, dst_path)) - self.download_url_content_to_file(url, dst_path) + if self.no_downloads: + LOGGER.info("Skipping downloading {0} => {1}".format(url, dst_path)) + else: + dst_dir = os.path.dirname(dst_path) + utils.makedirs(dst_dir) + LOGGER.info("Downloading {0} => {1}".format(url, dst_path)) + self.download_url_content_to_file(url, dst_path) dst_url = '/'.join(dst_path.split(os.sep)[2:]) links[link] = '/' + dst_url links[url] = '/' + dst_url @@ -517,6 +571,8 @@ class CommandImportWordpress(Command, ImportMixin): if meta_key in metadata: image_meta = metadata[meta_key] + if not image_meta: + continue dst_meta = {} def add(our_key, wp_key, is_int=False, ignore_zero=False, is_float=False): @@ -562,15 +618,18 @@ class CommandImportWordpress(Command, ImportMixin): meta = {} meta['size'] = size.decode('utf-8') if width_key in metadata[size_key][size] and height_key in metadata[size_key][size]: - meta['width'] = metadata[size_key][size][width_key] - meta['height'] = metadata[size_key][size][height_key] + meta['width'] = int(metadata[size_key][size][width_key]) + meta['height'] = int(metadata[size_key][size][height_key]) path = urlparse(url).path dst_path = os.path.join(*([self.output_folder, 'files'] + list(path.split('/')))) - dst_dir = os.path.dirname(dst_path) - utils.makedirs(dst_dir) - LOGGER.info("Downloading {0} => {1}".format(url, dst_path)) - self.download_url_content_to_file(url, dst_path) + if self.no_downloads: + LOGGER.info("Skipping downloading {0} => {1}".format(url, dst_path)) + else: + dst_dir = os.path.dirname(dst_path) + utils.makedirs(dst_dir) + LOGGER.info("Downloading {0} => {1}".format(url, dst_path)) + self.download_url_content_to_file(url, dst_path) dst_url = '/'.join(dst_path.split(os.sep)[2:]) links[url] = '/' + dst_url @@ -638,10 +697,10 @@ class CommandImportWordpress(Command, ImportMixin): return content @staticmethod - def transform_caption(content): + def transform_caption(content, use_html=False): """Transform captions.""" - new_caption = re.sub(r'\[/caption\]', '', content) - new_caption = re.sub(r'\[caption.*\]', '', new_caption) + new_caption = re.sub(r'\[/caption\]', '</h1>' if use_html else '', content) + new_caption = re.sub(r'\[caption.*\]', '<h1>' if use_html else '', new_caption) return new_caption @@ -664,6 +723,26 @@ class CommandImportWordpress(Command, ImportMixin): except TypeError: # old versions of the plugin don't support the additional argument content = self.wordpress_page_compiler.compile_to_string(content) return content, 'html', True + elif self.transform_to_markdown: + # First convert to HTML with WordPress plugin + additional_data = {} + if attachments is not None: + additional_data['attachments'] = attachments + try: + content = self.wordpress_page_compiler.compile_to_string(content, additional_data=additional_data) + except TypeError: # old versions of the plugin don't support the additional argument + content = self.wordpress_page_compiler.compile_to_string(content) + # Now convert to MarkDown with html2text + h = html2text.HTML2Text() + content = h.handle(content) + return content, 'md', False + elif self.html2text: + # TODO: what to do with [code] blocks? + # content = self.transform_code(content) + content = self.transform_caption(content, use_html=True) + h = html2text.HTML2Text() + content = h.handle(content) + return content, 'md', False elif self.use_wordpress_compiler: return content, 'wp', False else: @@ -781,6 +860,12 @@ class CommandImportWordpress(Command, ImportMixin): out_folder = 'posts' title = get_text_tag(item, 'title', 'NO TITLE') + + # titles can have line breaks in them, particularly when they are + # created by third-party tools that post to Wordpress. + # Handle windows-style and unix-style line endings. + title = title.replace('\r\n', ' ').replace('\n', ' ') + # link is something like http://foo.com/2012/09/01/hello-world/ # So, take the path, utils.slugify it, and that's our slug link = get_text_tag(item, 'link', None) @@ -813,7 +898,7 @@ class CommandImportWordpress(Command, ImportMixin): else: if len(pathlist) > 1: out_folder = os.path.join(*([out_folder] + pathlist[:-1])) - slug = utils.slugify(pathlist[-1]) + slug = utils.slugify(pathlist[-1], self.lang) description = get_text_tag(item, 'description', '') post_date = get_text_tag( @@ -928,14 +1013,32 @@ class CommandImportWordpress(Command, ImportMixin): meta_slug = slug tags, other_meta = self._create_metadata(status, excerpt, tags, categories, post_name=os.path.join(out_folder, slug)) - self.write_metadata(os.path.join(self.output_folder, out_folder, - out_meta_filename), - title, meta_slug, post_date, description, tags, **other_meta) - self.write_content( - os.path.join(self.output_folder, - out_folder, out_content_filename), - content, - rewrite_html) + + meta = { + "title": title, + "slug": meta_slug, + "date": post_date, + "description": description, + "tags": ','.join(tags), + } + meta.update(other_meta) + if self.onefile: + self.write_post( + os.path.join(self.output_folder, + out_folder, out_content_filename), + content, + meta, + self._get_compiler(), + rewrite_html) + else: + self.write_metadata(os.path.join(self.output_folder, out_folder, + out_meta_filename), + title, meta_slug, post_date, description, tags, **other_meta) + self.write_content( + os.path.join(self.output_folder, + out_folder, out_content_filename), + content, + rewrite_html) if self.export_comments: comments = [] @@ -995,7 +1098,7 @@ class CommandImportWordpress(Command, ImportMixin): if post_type == 'post': out_folder_slug = self.import_postpage_item(item, wordpress_namespace, 'posts', attachments) else: - out_folder_slug = self.import_postpage_item(item, wordpress_namespace, 'stories', attachments) + out_folder_slug = self.import_postpage_item(item, wordpress_namespace, 'pages', attachments) # Process attachment data if attachments is not None: # If post was exported, store data diff --git a/nikola/plugins/command/init.py b/nikola/plugins/command/init.py index 2dbee43..3d6669c 100644 --- a/nikola/plugins/command/init.py +++ b/nikola/plugins/command/init.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -75,10 +75,12 @@ SAMPLE_CONF = { 'POSTS': """( ("posts/*.rst", "posts", "post.tmpl"), ("posts/*.txt", "posts", "post.tmpl"), + ("posts/*.html", "posts", "post.tmpl"), )""", 'PAGES': """( - ("stories/*.rst", "stories", "story.tmpl"), - ("stories/*.txt", "stories", "story.tmpl"), + ("pages/*.rst", "pages", "story.tmpl"), + ("pages/*.txt", "pages", "story.tmpl"), + ("pages/*.html", "pages", "story.tmpl"), )""", 'COMPILERS': """{ "rest": ('.rst', '.txt'), @@ -219,6 +221,18 @@ def prepare_config(config): return p +def test_destination(destination, demo=False): + """Check if the destination already exists, which can break demo site creation.""" + # Issue #2214 + if demo and os.path.exists(destination): + LOGGER.warning("The directory {0} already exists, and a new demo site cannot be initialized in an existing directory.".format(destination)) + LOGGER.warning("Please remove the directory and try again, or use another directory.") + LOGGER.info("Hint: If you want to initialize a git repository in this directory, run `git init` in the directory after creating a Nikola site.") + return False + else: + return True + + class CommandInit(Command): """Create a new site.""" @@ -271,11 +285,11 @@ class CommandInit(Command): @classmethod def create_empty_site(cls, target): """Create an empty site with directories only.""" - for folder in ('files', 'galleries', 'listings', 'posts', 'stories'): + for folder in ('files', 'galleries', 'listings', 'posts', 'pages'): makedirs(os.path.join(target, folder)) @staticmethod - def ask_questions(target): + def ask_questions(target, demo=False): """Ask some questions about Nikola.""" def urlhandler(default, toconf): answer = ask('Site URL', 'https://example.com/') @@ -346,7 +360,7 @@ class CommandInit(Command): # Assuming that base contains all the locales, and that base does # not inherit from anywhere. try: - messages = load_messages(['base'], tr, default) + messages = load_messages(['base'], tr, default, themes_dirs=['themes']) SAMPLE_CONF['NAVIGATION_LINKS'] = format_navigation_links(langs, default, messages, SAMPLE_CONF['STRIP_INDEXES']) except nikola.utils.LanguageNotFoundError as e: print(" ERROR: the language '{0}' is not supported.".format(e.lang)) @@ -440,7 +454,7 @@ class CommandInit(Command): print("If you do not want to answer and want to go with the defaults instead, simply restart with the `-q` parameter.") for query, default, toconf, destination in questions: - if target and destination == '!target': + if target and destination == '!target' and test_destination(target, demo): # Skip the destination question if we know it already pass else: @@ -457,8 +471,9 @@ class CommandInit(Command): if toconf: SAMPLE_CONF[destination] = answer if destination == '!target': - while not answer: - print(' ERROR: you need to specify a target directory.\n') + while not answer or not test_destination(answer, demo): + if not answer: + print(' ERROR: you need to specify a target directory.\n') answer = ask(query, default) STORAGE['target'] = answer @@ -474,7 +489,7 @@ class CommandInit(Command): except IndexError: target = None if not options.get('quiet'): - st = self.ask_questions(target=target) + st = self.ask_questions(target=target, demo=options.get('demo')) try: if not target: target = st['target'] @@ -487,11 +502,13 @@ class CommandInit(Command): Options: -q, --quiet Do not ask questions about config. -d, --demo Create a site filled with example data.""") - return False + return 1 if not options.get('demo'): self.create_empty_site(target) LOGGER.info('Created empty site at {0}.'.format(target)) else: + if not test_destination(target, True): + return 2 self.copy_sample_site(target) LOGGER.info("A new site with example data has been created at " "{0}.".format(target)) diff --git a/nikola/plugins/command/install_theme.py b/nikola/plugins/command/install_theme.py index bad335c..28f7aa3 100644 --- a/nikola/plugins/command/install_theme.py +++ b/nikola/plugins/command/install_theme.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -27,18 +27,9 @@ """Install a theme.""" from __future__ import print_function -import os -import io -import time -import requests -import pygments -from pygments.lexers import PythonLexer -from pygments.formatters import TerminalFormatter - -from nikola.plugin_categories import Command from nikola import utils - +from nikola.plugin_categories import Command LOGGER = utils.get_logger('install_theme', utils.STDERR_HANDLER) @@ -79,6 +70,7 @@ class CommandInstallTheme(Command): def _execute(self, options, args): """Install theme into current site.""" + p = self.site.plugin_manager.getPluginByName('theme', 'Command').plugin_object listing = options['list'] url = options['url'] if args: @@ -87,85 +79,13 @@ class CommandInstallTheme(Command): name = None if options['getpath'] and name: - path = utils.get_theme_path(name) - if path: - print(path) - else: - print('not installed') - return 0 + return p.get_path(name) if name is None and not listing: LOGGER.error("This command needs either a theme name or the -l option.") return False - try: - data = requests.get(url).json() - except requests.exceptions.SSLError: - LOGGER.warning("SSL error, using http instead of https (press ^C to abort)") - time.sleep(1) - url = url.replace('https', 'http', 1) - data = requests.get(url).json() - if listing: - print("Themes:") - print("-------") - for theme in sorted(data.keys()): - print(theme) - return True - else: - # `name` may be modified by the while loop. - origname = name - installstatus = self.do_install(name, data) - # See if the theme's parent is available. If not, install it - while True: - parent_name = utils.get_parent_theme_name(name) - if parent_name is None: - break - try: - utils.get_theme_path(parent_name) - break - except: # Not available - self.do_install(parent_name, data) - name = parent_name - if installstatus: - LOGGER.notice('Remember to set THEME="{0}" in conf.py to use this theme.'.format(origname)) - def do_install(self, name, data): - """Download and install a theme.""" - if name in data: - utils.makedirs(self.output_dir) - url = data[name] - LOGGER.info("Downloading '{0}'".format(url)) - try: - zip_data = requests.get(url).content - except requests.exceptions.SSLError: - LOGGER.warning("SSL error, using http instead of https (press ^C to abort)") - time.sleep(1) - url = url.replace('https', 'http', 1) - zip_data = requests.get(url).content - - zip_file = io.BytesIO() - zip_file.write(zip_data) - LOGGER.info("Extracting '{0}' into themes/".format(name)) - utils.extract_all(zip_file) - dest_path = os.path.join(self.output_dir, name) + if listing: + p.list_available(url) else: - dest_path = os.path.join(self.output_dir, name) - try: - theme_path = utils.get_theme_path(name) - LOGGER.error("Theme '{0}' is already installed in {1}".format(name, theme_path)) - except Exception: - LOGGER.error("Can't find theme {0}".format(name)) - - return False - - confpypath = os.path.join(dest_path, 'conf.py.sample') - if os.path.exists(confpypath): - LOGGER.notice('This theme has a sample config file. Integrate it with yours in order to make this theme work!') - print('Contents of the conf.py.sample file:\n') - with io.open(confpypath, 'r', encoding='utf-8') as fh: - if self.site.colorful: - print(utils.indent(pygments.highlight( - fh.read(), PythonLexer(), TerminalFormatter()), - 4 * ' ')) - else: - print(utils.indent(fh.read(), 4 * ' ')) - return True + p.do_install_deps(url, name) diff --git a/nikola/plugins/command/new_page.py b/nikola/plugins/command/new_page.py index 8843421..c09b4be 100644 --- a/nikola/plugins/command/new_page.py +++ b/nikola/plugins/command/new_page.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina, Chris Warrick and others. +# Copyright © 2012-2016 Roberto Alsina, Chris Warrick and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/new_post.py b/nikola/plugins/command/new_post.py index 30d009d..36cc04f 100644 --- a/nikola/plugins/command/new_post.py +++ b/nikola/plugins/command/new_post.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -29,10 +29,11 @@ from __future__ import unicode_literals, print_function import io import datetime +import operator import os -import sys +import shutil import subprocess -import operator +import sys from blinker import signal import dateutil.tz @@ -293,14 +294,14 @@ class CommandNewPost(Command): title = title.strip() if not path: - slug = utils.slugify(title) + slug = utils.slugify(title, lang=self.site.default_lang) else: if isinstance(path, utils.bytes_str): try: path = path.decode(sys.stdin.encoding) except (AttributeError, TypeError): # for tests path = path.decode('utf-8') - slug = utils.slugify(os.path.splitext(os.path.basename(path))[0]) + slug = utils.slugify(os.path.splitext(os.path.basename(path))[0], lang=self.site.default_lang) if isinstance(author, utils.bytes_str): try: @@ -324,14 +325,17 @@ class CommandNewPost(Command): 'description': '', 'type': 'text', } - output_path = os.path.dirname(entry[0]) - meta_path = os.path.join(output_path, slug + ".meta") - pattern = os.path.basename(entry[0]) - suffix = pattern[1:] + if not path: + pattern = os.path.basename(entry[0]) + suffix = pattern[1:] + output_path = os.path.dirname(entry[0]) + txt_path = os.path.join(output_path, slug + suffix) + meta_path = os.path.join(output_path, slug + ".meta") else: txt_path = os.path.join(self.site.original_cwd, path) + meta_path = os.path.splitext(txt_path)[0] + ".meta" if (not onefile and os.path.isfile(meta_path)) or \ os.path.isfile(txt_path): @@ -343,6 +347,9 @@ class CommandNewPost(Command): signal('existing_' + content_type).send(self, **event) LOGGER.error("The title already exists!") + LOGGER.info("Existing {0}'s text is at: {1}".format(content_type, txt_path)) + if not onefile: + LOGGER.info("Existing {0}'s metadata is at: {1}".format(content_type, meta_path)) return 8 d_name = os.path.dirname(txt_path) @@ -363,17 +370,22 @@ class CommandNewPost(Command): onefile = False LOGGER.warn('This compiler does not support one-file posts.') - if import_file: + if onefile and import_file: with io.open(import_file, 'r', encoding='utf-8') as fh: content = fh.read() - else: + elif not import_file: if is_page: content = self.site.MESSAGES[self.site.default_lang]["Write your page here."] else: content = self.site.MESSAGES[self.site.default_lang]["Write your post here."] - compiler_plugin.create_post( - txt_path, content=content, onefile=onefile, title=title, - slug=slug, date=date, tags=tags, is_page=is_page, **metadata) + + if (not onefile) and import_file: + # Two-file posts are copied on import (Issue #2380) + shutil.copy(import_file, txt_path) + else: + compiler_plugin.create_post( + txt_path, content=content, onefile=onefile, title=title, + slug=slug, date=date, tags=tags, is_page=is_page, **metadata) event = dict(path=txt_path) diff --git a/nikola/plugins/command/orphans.py b/nikola/plugins/command/orphans.py index f061d39..5e2574d 100644 --- a/nikola/plugins/command/orphans.py +++ b/nikola/plugins/command/orphans.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina, Chris Warrick and others. +# Copyright © 2012-2016 Roberto Alsina, Chris Warrick and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/plugin.py b/nikola/plugins/command/plugin.py index 1df7b71..364f343 100644 --- a/nikola/plugins/command/plugin.py +++ b/nikola/plugins/command/plugin.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -29,6 +29,7 @@ from __future__ import print_function import io import os +import sys import shutil import subprocess import time @@ -49,7 +50,7 @@ class CommandPlugin(Command): json = None name = "plugin" - doc_usage = "[[-u][--user] --install name] | [[-u] [-l |--upgrade|--list-installed] | [--uninstall name]]" + doc_usage = "[-u url] [--user] [-i name] [-r name] [--upgrade] [-l] [--list-installed]" doc_purpose = "manage plugins" output_dir = None needs_config = False @@ -176,8 +177,11 @@ class CommandPlugin(Command): plugins.append([plugin.name, p]) plugins.sort() + print('Installed Plugins:') + print('------------------') for name, path in plugins: print('{0} at {1}'.format(name, path)) + print('\n\nAlso, you have disabled these plugins: {}'.format(self.site.config['DISABLED_PLUGINS'])) return 0 def do_upgrade(self, url): @@ -251,7 +255,7 @@ class CommandPlugin(Command): LOGGER.notice('This plugin has Python dependencies.') LOGGER.info('Installing dependencies with pip...') try: - subprocess.check_call(('pip', 'install', '-r', reqpath)) + subprocess.check_call((sys.executable, '-m', 'pip', 'install', '-r', reqpath)) except subprocess.CalledProcessError: LOGGER.error('Could not install the dependencies.') print('Contents of the requirements.txt file:\n') @@ -292,12 +296,15 @@ class CommandPlugin(Command): def do_uninstall(self, name): """Uninstall a plugin.""" for plugin in self.site.plugin_manager.getAllPlugins(): # FIXME: this is repeated thrice - p = plugin.path - if os.path.isdir(p): - p = p + os.sep - else: - p = os.path.dirname(p) if name == plugin.name: # Uninstall this one + p = plugin.path + if os.path.isdir(p): + # Plugins that have a package in them need to delete parent + # Issue #2356 + p = p + os.sep + p = os.path.abspath(os.path.join(p, os.pardir)) + else: + p = os.path.dirname(p) LOGGER.warning('About to uninstall plugin: {0}'.format(name)) LOGGER.warning('This will delete {0}'.format(p)) sure = utils.ask_yesno('Are you sure?') diff --git a/nikola/plugins/command/rst2html/__init__.py b/nikola/plugins/command/rst2html/__init__.py index 68ea13d..c877f63 100644 --- a/nikola/plugins/command/rst2html/__init__.py +++ b/nikola/plugins/command/rst2html/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2015 Chris Warrick and others. +# Copyright © 2015-2016 Chris Warrick and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/command/serve.py b/nikola/plugins/command/serve.py index b647bb7..c9702d5 100644 --- a/nikola/plugins/command/serve.py +++ b/nikola/plugins/command/serve.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -78,7 +78,7 @@ class CommandServe(Command): 'long': 'address', 'type': str, 'default': '', - 'help': 'Address to bind (default: 0.0.0.0 – all local IPv4 interfaces)', + 'help': 'Address to bind (default: 0.0.0.0 -- all local IPv4 interfaces)', }, { 'name': 'detach', diff --git a/nikola/plugins/command/status.py b/nikola/plugins/command/status.py index 40f4f77..b3ffbb4 100644 --- a/nikola/plugins/command/status.py +++ b/nikola/plugins/command/status.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -27,7 +27,6 @@ """Display site status.""" from __future__ import print_function -import io import os from datetime import datetime from dateutil.tz import gettz, tzlocal @@ -42,7 +41,7 @@ class CommandStatus(Command): doc_purpose = "display site status" doc_description = "Show information about the posts and site deployment." - doc_usage = '[-l|--list-drafts] [-m|--list-modified] [-s|--list-scheduled]' + doc_usage = '[-d|--list-drafts] [-m|--list-modified] [-p|--list-private] [-P|--list-published] [-s|--list-scheduled]' logger = None cmd_options = [ { @@ -62,6 +61,22 @@ class CommandStatus(Command): 'help': 'List all modified files since last deployment', }, { + 'name': 'list_private', + 'short': 'p', + 'long': 'list-private', + 'type': bool, + 'default': False, + 'help': 'List all private posts', + }, + { + 'name': 'list_published', + 'short': 'P', + 'long': 'list-published', + 'type': bool, + 'default': False, + 'help': 'List all published posts', + }, + { 'name': 'list_scheduled', 'short': 's', 'long': 'list-scheduled', @@ -75,16 +90,12 @@ class CommandStatus(Command): """Display site status.""" self.site.scan_posts() - timestamp_path = os.path.join(self.site.config["CACHE_FOLDER"], "lastdeploy") - - last_deploy = None - - try: - with io.open(timestamp_path, "r", encoding="utf8") as inf: - last_deploy = datetime.strptime(inf.read().strip(), "%Y-%m-%dT%H:%M:%S.%f") - last_deploy_offset = datetime.utcnow() - last_deploy - except (IOError, Exception): - print("It does not seem like you’ve ever deployed the site (or cache missing).") + last_deploy = self.site.state.get('last_deploy') + if last_deploy is not None: + last_deploy = datetime.strptime(last_deploy, "%Y-%m-%dT%H:%M:%S.%f") + last_deploy_offset = datetime.utcnow() - last_deploy + else: + print("It does not seem like you've ever deployed the site (or cache missing).") if last_deploy: @@ -110,12 +121,23 @@ class CommandStatus(Command): posts_count = len(self.site.all_posts) + # find all published posts + posts_published = [post for post in self.site.all_posts if post.use_in_feeds] + posts_published = sorted(posts_published, key=lambda post: post.source_path) + + # find all private posts + posts_private = [post for post in self.site.all_posts if post.is_private] + posts_private = sorted(posts_private, key=lambda post: post.source_path) + # find all drafts posts_drafts = [post for post in self.site.all_posts if post.is_draft] posts_drafts = sorted(posts_drafts, key=lambda post: post.source_path) # find all scheduled posts with offset from now until publishing time - posts_scheduled = [(post.date - now, post) for post in self.site.all_posts if post.publish_later] + posts_scheduled = [ + (post.date - now, post) for post in self.site.all_posts + if post.publish_later and not (post.is_draft or post.is_private) + ] posts_scheduled = sorted(posts_scheduled, key=lambda offset_post: (offset_post[0], offset_post[1].source_path)) if len(posts_scheduled) > 0: @@ -128,7 +150,13 @@ class CommandStatus(Command): if options['list_drafts']: for post in posts_drafts: print("Draft: '{0}' ({1}; source: {2})".format(post.meta('title'), post.permalink(), post.source_path)) - print("{0} posts in total, {1} scheduled, and {2} drafts.".format(posts_count, len(posts_scheduled), len(posts_drafts))) + if options['list_private']: + for post in posts_private: + print("Private: '{0}' ({1}; source: {2})".format(post.meta('title'), post.permalink(), post.source_path)) + if options['list_published']: + for post in posts_published: + print("Published: '{0}' ({1}; source: {2})".format(post.meta('title'), post.permalink(), post.source_path)) + print("{0} posts in total, {1} scheduled, {2} drafts, {3} private and {4} published.".format(posts_count, len(posts_scheduled), len(posts_drafts), len(posts_private), len(posts_published))) def human_time(self, dt): """Translate time into a human-friendly representation.""" diff --git a/nikola/plugins/command/theme.plugin b/nikola/plugins/command/theme.plugin new file mode 100644 index 0000000..b0c1886 --- /dev/null +++ b/nikola/plugins/command/theme.plugin @@ -0,0 +1,13 @@ +[Core] +name = theme +module = theme + +[Documentation] +author = Roberto Alsina and Chris Warrick +version = 1.0 +website = https://getnikola.com/ +description = Manage Nikola themes + +[Nikola] +plugincategory = Command + diff --git a/nikola/plugins/command/theme.py b/nikola/plugins/command/theme.py new file mode 100644 index 0000000..7513491 --- /dev/null +++ b/nikola/plugins/command/theme.py @@ -0,0 +1,365 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2012-2016 Roberto Alsina, Chris Warrick 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. + +"""Manage themes.""" + +from __future__ import print_function +import os +import io +import shutil +import time +import requests + +import pygments +from pygments.lexers import PythonLexer +from pygments.formatters import TerminalFormatter +from pkg_resources import resource_filename + +from nikola.plugin_categories import Command +from nikola import utils + +LOGGER = utils.get_logger('theme', utils.STDERR_HANDLER) + + +class CommandTheme(Command): + """Manage themes.""" + + json = None + name = "theme" + doc_usage = "[-u url] [-i theme_name] [-r theme_name] [-l] [--list-installed] [-g] [-n theme_name] [-c template_name]" + doc_purpose = "manage themes" + output_dir = 'themes' + cmd_options = [ + { + 'name': 'install', + 'short': 'i', + 'long': 'install', + 'type': str, + 'default': '', + 'help': 'Install a theme.' + }, + { + 'name': 'uninstall', + 'long': 'uninstall', + 'short': 'r', + 'type': str, + 'default': '', + 'help': 'Uninstall a theme.' + }, + { + 'name': 'list', + 'short': 'l', + 'long': 'list', + 'type': bool, + 'default': False, + 'help': 'Show list of available themes.' + }, + { + 'name': 'list_installed', + 'long': 'list-installed', + 'type': bool, + 'help': "List the installed themes with their location.", + 'default': False + }, + { + 'name': 'url', + 'short': 'u', + 'long': 'url', + 'type': str, + 'help': "URL for the theme repository (default: " + "https://themes.getnikola.com/v7/themes.json)", + 'default': 'https://themes.getnikola.com/v7/themes.json' + }, + { + 'name': 'getpath', + 'short': 'g', + 'long': 'get-path', + 'type': str, + 'default': '', + 'help': "Print the path for installed theme", + }, + { + 'name': 'copy-template', + 'short': 'c', + 'long': 'copy-template', + 'type': str, + 'default': '', + 'help': 'Copy a built-in template into templates/ or your theme', + }, + { + 'name': 'new', + 'short': 'n', + 'long': 'new', + 'type': str, + 'default': '', + 'help': 'Create a new theme', + }, + { + 'name': 'new_engine', + 'long': 'engine', + 'type': str, + 'default': 'mako', + 'help': 'Engine to use for new theme (mako or jinja -- default: mako)', + }, + { + 'name': 'new_parent', + 'long': 'parent', + 'type': str, + 'default': 'base', + 'help': 'Parent to use for new theme (default: base)', + }, + ] + + def _execute(self, options, args): + """Install theme into current site.""" + url = options['url'] + + # See the "mode" we need to operate in + install = options.get('install') + uninstall = options.get('uninstall') + list_available = options.get('list') + list_installed = options.get('list_installed') + get_path = options.get('getpath') + copy_template = options.get('copy-template') + new = options.get('new') + new_engine = options.get('new_engine') + new_parent = options.get('new_parent') + command_count = [bool(x) for x in ( + install, + uninstall, + list_available, + list_installed, + get_path, + copy_template, + new)].count(True) + if command_count > 1 or command_count == 0: + print(self.help()) + return 2 + + if list_available: + return self.list_available(url) + elif list_installed: + return self.list_installed() + elif install: + return self.do_install_deps(url, install) + elif uninstall: + return self.do_uninstall(uninstall) + elif get_path: + return self.get_path(get_path) + elif copy_template: + return self.copy_template(copy_template) + elif new: + return self.new_theme(new, new_engine, new_parent) + + def do_install_deps(self, url, name): + """Install themes and their dependencies.""" + data = self.get_json(url) + # `name` may be modified by the while loop. + origname = name + installstatus = self.do_install(name, data) + # See if the theme's parent is available. If not, install it + while True: + parent_name = utils.get_parent_theme_name(utils.get_theme_path_real(name, self.site.themes_dirs)) + if parent_name is None: + break + try: + utils.get_theme_path_real(parent_name, self.site.themes_dirs) + break + except: # Not available + self.do_install(parent_name, data) + name = parent_name + if installstatus: + LOGGER.notice('Remember to set THEME="{0}" in conf.py to use this theme.'.format(origname)) + + def do_install(self, name, data): + """Download and install a theme.""" + if name in data: + utils.makedirs(self.output_dir) + url = data[name] + LOGGER.info("Downloading '{0}'".format(url)) + try: + zip_data = requests.get(url).content + except requests.exceptions.SSLError: + LOGGER.warning("SSL error, using http instead of https (press ^C to abort)") + time.sleep(1) + url = url.replace('https', 'http', 1) + zip_data = requests.get(url).content + + zip_file = io.BytesIO() + zip_file.write(zip_data) + LOGGER.info("Extracting '{0}' into themes/".format(name)) + utils.extract_all(zip_file) + dest_path = os.path.join(self.output_dir, name) + else: + dest_path = os.path.join(self.output_dir, name) + try: + theme_path = utils.get_theme_path_real(name, self.site.themes_dirs) + LOGGER.error("Theme '{0}' is already installed in {1}".format(name, theme_path)) + except Exception: + LOGGER.error("Can't find theme {0}".format(name)) + + return False + + confpypath = os.path.join(dest_path, 'conf.py.sample') + if os.path.exists(confpypath): + LOGGER.notice('This theme has a sample config file. Integrate it with yours in order to make this theme work!') + print('Contents of the conf.py.sample file:\n') + with io.open(confpypath, 'r', encoding='utf-8') as fh: + if self.site.colorful: + print(utils.indent(pygments.highlight( + fh.read(), PythonLexer(), TerminalFormatter()), + 4 * ' ')) + else: + print(utils.indent(fh.read(), 4 * ' ')) + return True + + def do_uninstall(self, name): + """Uninstall a theme.""" + try: + path = utils.get_theme_path_real(name, self.site.themes_dirs) + except Exception: + LOGGER.error('Unknown theme: {0}'.format(name)) + return 1 + # Don't uninstall builtin themes (Issue #2510) + blocked = os.path.dirname(utils.__file__) + if path.startswith(blocked): + LOGGER.error("Can't delete builtin theme: {0}".format(name)) + return 1 + LOGGER.warning('About to uninstall theme: {0}'.format(name)) + LOGGER.warning('This will delete {0}'.format(path)) + sure = utils.ask_yesno('Are you sure?') + if sure: + LOGGER.warning('Removing {0}'.format(path)) + shutil.rmtree(path) + return 0 + return 1 + + def get_path(self, name): + """Get path for an installed theme.""" + try: + path = utils.get_theme_path_real(name, self.site.themes_dirs) + print(path) + except Exception: + print("not installed") + return 0 + + def list_available(self, url): + """List all available themes.""" + data = self.get_json(url) + print("Available Themes:") + print("-----------------") + for theme in sorted(data.keys()): + print(theme) + return 0 + + def list_installed(self): + """List all installed themes.""" + print("Installed Themes:") + print("-----------------") + themes = [] + themes_dirs = self.site.themes_dirs + [resource_filename('nikola', os.path.join('data', 'themes'))] + for tdir in themes_dirs: + themes += [(i, os.path.join(tdir, i)) for i in os.listdir(tdir)] + for tname, tpath in sorted(set(themes)): + if os.path.isdir(tpath): + print("{0} at {1}".format(tname, tpath)) + + def copy_template(self, template): + """Copy the named template file from the parent to a local theme or to templates/.""" + # Find template + t = self.site.template_system.get_template_path(template) + if t is None: + LOGGER.error("Cannot find template {0} in the lookup.".format(template)) + return 2 + + # Figure out where to put it. + # Check if a local theme exists. + theme_path = utils.get_theme_path(self.site.THEMES[0]) + if theme_path.startswith('themes' + os.sep): + # Theme in local themes/ directory + base = os.path.join(theme_path, 'templates') + else: + # Put it in templates/ + base = 'templates' + + if not os.path.exists(base): + os.mkdir(base) + LOGGER.info("Created directory {0}".format(base)) + + try: + out = shutil.copy(t, base) + LOGGER.info("Copied template from {0} to {1}".format(t, out)) + except shutil.SameFileError: + LOGGER.error("This file already exists in your templates directory ({0}).".format(base)) + return 3 + + def new_theme(self, name, engine, parent): + """Create a new theme.""" + base = 'themes' + themedir = os.path.join(base, name) + LOGGER.info("Creating theme {0} with parent {1} and engine {2} in {3}".format(name, parent, engine, themedir)) + if not os.path.exists(base): + os.mkdir(base) + LOGGER.info("Created directory {0}".format(base)) + + # Check if engine and parent match + engine_file = utils.get_asset_path('engine', utils.get_theme_chain(parent, self.site.themes_dirs)) + with io.open(engine_file, 'r', encoding='utf-8') as fh: + parent_engine = fh.read().strip() + + if parent_engine != engine: + LOGGER.error("Cannot use engine {0} because parent theme '{1}' uses {2}".format(engine, parent, parent_engine)) + return 2 + + # Create theme + if not os.path.exists(themedir): + os.mkdir(themedir) + LOGGER.info("Created directory {0}".format(themedir)) + else: + LOGGER.error("Theme already exists") + return 2 + + with io.open(os.path.join(themedir, 'parent'), 'w', encoding='utf-8') as fh: + fh.write(parent + '\n') + LOGGER.info("Created file {0}".format(os.path.join(themedir, 'parent'))) + with io.open(os.path.join(themedir, 'engine'), 'w', encoding='utf-8') as fh: + fh.write(engine + '\n') + LOGGER.info("Created file {0}".format(os.path.join(themedir, 'engine'))) + + LOGGER.info("Theme {0} created successfully.".format(themedir)) + LOGGER.notice('Remember to set THEME="{0}" in conf.py to use this theme.'.format(name)) + + def get_json(self, url): + """Download the JSON file with all plugins.""" + if self.json is None: + try: + self.json = requests.get(url).json() + except requests.exceptions.SSLError: + LOGGER.warning("SSL error, using http instead of https (press ^C to abort)") + time.sleep(1) + url = url.replace('https', 'http', 1) + self.json = requests.get(url).json() + return self.json diff --git a/nikola/plugins/command/version.py b/nikola/plugins/command/version.py index b83d622..267837e 100644 --- a/nikola/plugins/command/version.py +++ b/nikola/plugins/command/version.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/compile/__init__.py b/nikola/plugins/compile/__init__.py index 60f1919..ff7e9a2 100644 --- a/nikola/plugins/compile/__init__.py +++ b/nikola/plugins/compile/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/compile/html.py b/nikola/plugins/compile/html.py index 6ff5de8..942d6da 100644 --- a/nikola/plugins/compile/html.py +++ b/nikola/plugins/compile/html.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -44,12 +44,24 @@ class CompileHtml(PageCompiler): def compile_html(self, source, dest, is_two_file=True): """Compile source file into HTML and save as dest.""" makedirs(os.path.dirname(dest)) + try: + post = self.site.post_per_input_file[source] + except KeyError: + post = None with io.open(dest, "w+", encoding="utf8") as out_file: with io.open(source, "r", encoding="utf8") as in_file: data = in_file.read() if not is_two_file: _, data = self.split_metadata(data) + data, shortcode_deps = self.site.apply_shortcodes(data, with_dependencies=True, extra_context=dict(post=post)) out_file.write(data) + if post is None: + if shortcode_deps: + self.logger.error( + "Cannot save dependencies for post {0} due to unregistered source file name", + source) + else: + post._depfile[dest] += shortcode_deps return True def create_post(self, path, **kw): diff --git a/nikola/plugins/compile/ipynb.py b/nikola/plugins/compile/ipynb.py index 1023b31..f3fdeea 100644 --- a/nikola/plugins/compile/ipynb.py +++ b/nikola/plugins/compile/ipynb.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2013-2015 Damián Avila, Chris Warrick and others. +# Copyright © 2013-2016 Damián Avila, Chris Warrick and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -32,21 +32,33 @@ import os import sys try: - import IPython - from IPython.nbconvert.exporters import HTMLExporter - if IPython.version_info[0] >= 3: # API changed with 3.0.0 - from IPython import nbformat - current_nbformat = nbformat.current_nbformat - from IPython.kernel import kernelspec - else: - import IPython.nbformat.current as nbformat - current_nbformat = 'json' - kernelspec = None - - from IPython.config import Config + from nbconvert.exporters import HTMLExporter + import nbformat + current_nbformat = nbformat.current_nbformat + from jupyter_client import kernelspec + from traitlets.config import Config flag = True + ipy_modern = True except ImportError: - flag = None + try: + import IPython + from IPython.nbconvert.exporters import HTMLExporter + if IPython.version_info[0] >= 3: # API changed with 3.0.0 + from IPython import nbformat + current_nbformat = nbformat.current_nbformat + from IPython.kernel import kernelspec + ipy_modern = True + else: + import IPython.nbformat.current as nbformat + current_nbformat = 'json' + kernelspec = None + ipy_modern = False + + from IPython.config import Config + flag = True + except ImportError: + flag = None + ipy_modern = None from nikola.plugin_categories import PageCompiler from nikola.utils import makedirs, req_missing, get_logger, STDERR_HANDLER @@ -69,7 +81,6 @@ class CompileIPynb(PageCompiler): """Export notebooks as HTML strings.""" if flag is None: req_missing(['ipython[notebook]>=2.0.0'], 'build this site (compile ipynb)') - HTMLExporter.default_template = 'basic' c = Config(self.site.config['IPYNB_CONFIG']) exportHtml = HTMLExporter(config=c) with io.open(source, "r", encoding="utf8") as in_file: @@ -80,8 +91,21 @@ class CompileIPynb(PageCompiler): def compile_html(self, source, dest, is_two_file=True): """Compile source file into HTML and save as dest.""" makedirs(os.path.dirname(dest)) + try: + post = self.site.post_per_input_file[source] + except KeyError: + post = None with io.open(dest, "w+", encoding="utf8") as out_file: - out_file.write(self.compile_html_string(source, is_two_file)) + output = self.compile_html_string(source, is_two_file) + output, shortcode_deps = self.site.apply_shortcodes(output, filename=source, with_dependencies=True, extra_context=dict(post=post)) + out_file.write(output) + if post is None: + if shortcode_deps: + self.logger.error( + "Cannot save dependencies for post {0} due to unregistered source file name", + source) + else: + post._depfile[dest] += shortcode_deps def read_metadata(self, post, file_metadata_regexp=None, unslugify_titles=False, lang=None): """Read metadata directly from ipynb file. @@ -118,7 +142,7 @@ class CompileIPynb(PageCompiler): # imported .ipynb file, guaranteed to start with "{" because it’s JSON. nb = nbformat.reads(content, current_nbformat) else: - if IPython.version_info[0] >= 3: + if ipy_modern: nb = nbformat.v4.new_notebook() nb["cells"] = [nbformat.v4.new_markdown_cell(content)] else: @@ -151,7 +175,7 @@ class CompileIPynb(PageCompiler): nb["metadata"]["nikola"] = metadata with io.open(path, "w+", encoding="utf8") as fd: - if IPython.version_info[0] >= 3: + if ipy_modern: nbformat.write(nb, fd, 4) else: nbformat.write(nb, fd, 'ipynb') diff --git a/nikola/plugins/compile/markdown/__init__.py b/nikola/plugins/compile/markdown/__init__.py index 93438a3..2e4234c 100644 --- a/nikola/plugins/compile/markdown/__init__.py +++ b/nikola/plugins/compile/markdown/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -69,13 +69,25 @@ class CompileMarkdown(PageCompiler): req_missing(['markdown'], 'build this site (compile Markdown)') makedirs(os.path.dirname(dest)) self.extensions += self.site.config.get("MARKDOWN_EXTENSIONS") + try: + post = self.site.post_per_input_file[source] + except KeyError: + post = None with io.open(dest, "w+", encoding="utf8") as out_file: with io.open(source, "r", encoding="utf8") as in_file: data = in_file.read() if not is_two_file: _, data = self.split_metadata(data) - output = markdown(data, self.extensions) + output = markdown(data, self.extensions, output_format="html5") + output, shortcode_deps = self.site.apply_shortcodes(output, filename=source, with_dependencies=True, extra_context=dict(post=post)) out_file.write(output) + if post is None: + if shortcode_deps: + self.logger.error( + "Cannot save dependencies for post {0} due to unregistered source file name", + source) + else: + post._depfile[dest] += shortcode_deps def create_post(self, path, **kw): """Create a new post.""" diff --git a/nikola/plugins/compile/markdown/mdx_gist.py b/nikola/plugins/compile/markdown/mdx_gist.py index a930de5..25c071f 100644 --- a/nikola/plugins/compile/markdown/mdx_gist.py +++ b/nikola/plugins/compile/markdown/mdx_gist.py @@ -31,161 +31,48 @@ Extension to Python Markdown for Embedded Gists (gist.github.com). Basic Example: - >>> import markdown - >>> text = ''' - ... Text of the gist: - ... [:gist: 4747847] - ... ''' - >>> html = markdown.markdown(text, [GistExtension()]) - >>> print(html) - <p>Text of the gist: - <div class="gist"> - <script src="https://gist.github.com/4747847.js"></script> - <noscript> - <pre>import this</pre> - </noscript> - </div> - </p> + Text of the gist: + [:gist: 4747847] Example with filename: - >>> import markdown - >>> text = ''' - ... Text of the gist: - ... [:gist: 4747847 zen.py] - ... ''' - >>> html = markdown.markdown(text, [GistExtension()]) - >>> print(html) - <p>Text of the gist: - <div class="gist"> - <script src="https://gist.github.com/4747847.js?file=zen.py"></script> - <noscript> - <pre>import this</pre> - </noscript> - </div> - </p> + Text of the gist: + [:gist: 4747847 zen.py] Basic Example with hexidecimal id: - >>> import markdown - >>> text = ''' - ... Text of the gist: - ... [:gist: c4a43d6fdce612284ac0] - ... ''' - >>> html = markdown.markdown(text, [GistExtension()]) - >>> print(html) - <p>Text of the gist: - <div class="gist"> - <script src="https://gist.github.com/c4a43d6fdce612284ac0.js"></script> - <noscript> - <pre>Moo</pre> - </noscript> - </div> - </p> + Text of the gist: + [:gist: c4a43d6fdce612284ac0] Example with hexidecimal id filename: - >>> import markdown - >>> text = ''' - ... Text of the gist: - ... [:gist: c4a43d6fdce612284ac0 cow.txt] - ... ''' - >>> html = markdown.markdown(text, [GistExtension()]) - >>> print(html) - <p>Text of the gist: - <div class="gist"> - <script src="https://gist.github.com/c4a43d6fdce612284ac0.js?file=cow.txt"></script> - <noscript> - <pre>Moo</pre> - </noscript> - </div> - </p> + Text of the gist: + [:gist: c4a43d6fdce612284ac0 cow.txt] Example using reStructuredText syntax: - >>> import markdown - >>> text = ''' - ... Text of the gist: - ... .. gist:: 4747847 zen.py - ... ''' - >>> html = markdown.markdown(text, [GistExtension()]) - >>> print(html) - <p>Text of the gist: - <div class="gist"> - <script src="https://gist.github.com/4747847.js?file=zen.py"></script> - <noscript> - <pre>import this</pre> - </noscript> - </div> - </p> + Text of the gist: + .. gist:: 4747847 zen.py Example using hexidecimal ID with reStructuredText syntax: - >>> import markdown - >>> text = ''' - ... Text of the gist: - ... .. gist:: c4a43d6fdce612284ac0 - ... ''' - >>> html = markdown.markdown(text, [GistExtension()]) - >>> print(html) - <p>Text of the gist: - <div class="gist"> - <script src="https://gist.github.com/c4a43d6fdce612284ac0.js"></script> - <noscript> - <pre>Moo</pre> - </noscript> - </div> - </p> + Text of the gist: + .. gist:: c4a43d6fdce612284ac0 Example using hexidecimal ID and filename with reStructuredText syntax: - >>> import markdown - >>> text = ''' - ... Text of the gist: - ... .. gist:: c4a43d6fdce612284ac0 cow.txt - ... ''' - >>> html = markdown.markdown(text, [GistExtension()]) - >>> print(html) - <p>Text of the gist: - <div class="gist"> - <script src="https://gist.github.com/c4a43d6fdce612284ac0.js?file=cow.txt"></script> - <noscript> - <pre>Moo</pre> - </noscript> - </div> - </p> + Text of the gist: + .. gist:: c4a43d6fdce612284ac0 cow.txt Error Case: non-existent Gist ID: - >>> import markdown - >>> text = ''' - ... Text of the gist: - ... [:gist: 0] - ... ''' - >>> html = markdown.markdown(text, [GistExtension()]) - >>> print(html) - <p>Text of the gist: - <div class="gist"> - <script src="https://gist.github.com/0.js"></script> - <noscript><!-- WARNING: Received a 404 response from Gist URL: https://gist.githubusercontent.com/raw/0 --></noscript> - </div> - </p> - -Error Case: non-existent file: - - >>> import markdown - >>> text = ''' - ... Text of the gist: - ... [:gist: 4747847 doesntexist.py] - ... ''' - >>> html = markdown.markdown(text, [GistExtension()]) - >>> print(html) - <p>Text of the gist: - <div class="gist"> - <script src="https://gist.github.com/4747847.js?file=doesntexist.py"></script> - <noscript><!-- WARNING: Received a 404 response from Gist URL: https://gist.githubusercontent.com/raw/4747847/doesntexist.py --></noscript> - </div> - </p> + Text of the gist: + [:gist: 0] + +Error Case: non-existent file: + + Text of the gist: + [:gist: 4747847 doesntexist.py] """ from __future__ import unicode_literals, print_function diff --git a/nikola/plugins/compile/markdown/mdx_nikola.py b/nikola/plugins/compile/markdown/mdx_nikola.py index 7984121..59a5d5b 100644 --- a/nikola/plugins/compile/markdown/mdx_nikola.py +++ b/nikola/plugins/compile/markdown/mdx_nikola.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/compile/markdown/mdx_podcast.py b/nikola/plugins/compile/markdown/mdx_podcast.py index 0f68e40..96a70ed 100644 --- a/nikola/plugins/compile/markdown/mdx_podcast.py +++ b/nikola/plugins/compile/markdown/mdx_podcast.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright © 2013-2015 Michael Rabbitt, Roberto Alsina and others. +# Copyright © 2013-2016 Michael Rabbitt, 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 diff --git a/nikola/plugins/compile/pandoc.py b/nikola/plugins/compile/pandoc.py index 85e84fc..2368ae9 100644 --- a/nikola/plugins/compile/pandoc.py +++ b/nikola/plugins/compile/pandoc.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -54,7 +54,22 @@ class CompilePandoc(PageCompiler): """Compile source file into HTML and save as dest.""" makedirs(os.path.dirname(dest)) try: + try: + post = self.site.post_per_input_file[source] + except KeyError: + post = None subprocess.check_call(['pandoc', '-o', dest, source] + self.site.config['PANDOC_OPTIONS']) + with open(dest, 'r', encoding='utf-8') as inf: + output, shortcode_deps = self.site.apply_shortcodes(inf.read(), with_dependencies=True) + with open(dest, 'w', encoding='utf-8') as outf: + outf.write(output) + if post is None: + if shortcode_deps: + self.logger.error( + "Cannot save dependencies for post {0} due to unregistered source file name", + source) + else: + post._depfile[dest] += shortcode_deps except OSError as e: if e.strreror == 'No such file or directory': req_missing(['pandoc'], 'build this site (compile with pandoc)', python=False) diff --git a/nikola/plugins/compile/php.py b/nikola/plugins/compile/php.py index a60e31a..d2559fd 100644 --- a/nikola/plugins/compile/php.py +++ b/nikola/plugins/compile/php.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/compile/rest/__init__.py b/nikola/plugins/compile/rest/__init__.py index 4b04958..b75849f 100644 --- a/nikola/plugins/compile/rest/__init__.py +++ b/nikola/plugins/compile/rest/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -36,9 +36,19 @@ import docutils.utils import docutils.io import docutils.readers.standalone import docutils.writers.html4css1 +import docutils.parsers.rst.directives +from docutils.parsers.rst import roles +from nikola.nikola import LEGAL_VALUES from nikola.plugin_categories import PageCompiler -from nikola.utils import unicode_str, get_logger, makedirs, write_metadata, STDERR_HANDLER +from nikola.utils import ( + unicode_str, + get_logger, + makedirs, + write_metadata, + STDERR_HANDLER, + LocaleBorg +) class CompileRest(PageCompiler): @@ -49,19 +59,6 @@ class CompileRest(PageCompiler): demote_headers = True logger = None - def _read_extra_deps(self, post): - """Read contents of .dep file and returns them as a list.""" - dep_path = post.base_path + '.dep' - if os.path.isfile(dep_path): - with io.open(dep_path, 'r+', encoding='utf8') as depf: - deps = [l.strip() for l in depf.readlines()] - return deps - return [] - - def register_extra_dependencies(self, post): - """Add dependency to post object to check .dep file.""" - post.add_dependency(lambda: self._read_extra_deps(post), 'fragment') - def compile_html_string(self, data, source_path=None, is_two_file=True): """Compile reST into HTML strings.""" # If errors occur, this will be added to the line number reported by @@ -73,16 +70,20 @@ class CompileRest(PageCompiler): add_ln = len(m_data.splitlines()) + 1 default_template_path = os.path.join(os.path.dirname(__file__), 'template.txt') + settings_overrides = { + 'initial_header_level': 1, + 'record_dependencies': True, + 'stylesheet_path': None, + 'link_stylesheet': True, + 'syntax_highlight': 'short', + 'math_output': 'mathjax', + 'template': default_template_path, + 'language_code': LEGAL_VALUES['DOCUTILS_LOCALES'].get(LocaleBorg().current_lang, 'en') + } + output, error_level, deps = rst2html( - data, settings_overrides={ - 'initial_header_level': 1, - 'record_dependencies': True, - 'stylesheet_path': None, - 'link_stylesheet': True, - 'syntax_highlight': 'short', - 'math_output': 'mathjax', - 'template': default_template_path, - }, logger=self.logger, source_path=source_path, l_add_ln=add_ln, transforms=self.site.rst_transforms) + data, settings_overrides=settings_overrides, logger=self.logger, source_path=source_path, l_add_ln=add_ln, transforms=self.site.rst_transforms, + no_title_transform=self.site.config.get('NO_DOCUTILS_TITLE_TRANSFORM', False)) if not isinstance(output, unicode_str): # To prevent some weird bugs here or there. # Original issue: empty files. `output` became a bytestring. @@ -94,18 +95,23 @@ class CompileRest(PageCompiler): makedirs(os.path.dirname(dest)) error_level = 100 with io.open(dest, "w+", encoding="utf8") as out_file: + try: + post = self.site.post_per_input_file[source] + except KeyError: + post = None with io.open(source, "r", encoding="utf8") as in_file: data = in_file.read() output, error_level, deps = self.compile_html_string(data, source, is_two_file) + output, shortcode_deps = self.site.apply_shortcodes(output, filename=source, with_dependencies=True, extra_context=dict(post=post)) out_file.write(output) - deps_path = dest + '.dep' - if deps.list: - deps.list = [p for p in deps.list if p != dest] # Don't depend on yourself (#1671) - with io.open(deps_path, "w+", encoding="utf8") as deps_file: - deps_file.write('\n'.join(deps.list)) + if post is None: + if deps.list: + self.logger.error( + "Cannot save dependencies for post {0} due to unregistered source file name", + source) else: - if os.path.isfile(deps_path): - os.unlink(deps_path) + post._depfile[dest] += deps.list + post._depfile[dest] += shortcode_deps if error_level < 3: return True else: @@ -176,11 +182,15 @@ class NikolaReader(docutils.readers.standalone.Reader): def __init__(self, *args, **kwargs): """Initialize the reader.""" self.transforms = kwargs.pop('transforms', []) + self.no_title_transform = kwargs.pop('no_title_transform', False) docutils.readers.standalone.Reader.__init__(self, *args, **kwargs) def get_transforms(self): """Get docutils transforms.""" - return docutils.readers.standalone.Reader(self).get_transforms() + self.transforms + transforms = docutils.readers.standalone.Reader(self).get_transforms() + self.transforms + if self.no_title_transform: + transforms = [t for t in transforms if str(t) != "<class 'docutils.transforms.frontmatter.DocTitle'>"] + return transforms def new_document(self): """Create and return a new empty document tree (root node).""" @@ -190,6 +200,16 @@ class NikolaReader(docutils.readers.standalone.Reader): return document +def shortcode_role(name, rawtext, text, lineno, inliner, + options={}, content=[]): + """A shortcode role that passes through raw inline HTML.""" + return [docutils.nodes.raw('', text, format='html')], [] + +roles.register_canonical_role('raw-html', shortcode_role) +roles.register_canonical_role('html', shortcode_role) +roles.register_canonical_role('sc', shortcode_role) + + def add_node(node, visit_function=None, depart_function=None): """Register a Docutils node class. @@ -235,7 +255,8 @@ def rst2html(source, source_path=None, source_class=docutils.io.StringInput, parser=None, parser_name='restructuredtext', writer=None, writer_name='html', settings=None, settings_spec=None, settings_overrides=None, config_section=None, - enable_exit_status=None, logger=None, l_add_ln=0, transforms=None): + enable_exit_status=None, logger=None, l_add_ln=0, transforms=None, + no_title_transform=False): """Set up & run a ``Publisher``, and return a dictionary of document parts. Dictionary keys are the names of parts, and values are Unicode strings; @@ -253,7 +274,7 @@ def rst2html(source, source_path=None, source_class=docutils.io.StringInput, reStructuredText syntax errors. """ if reader is None: - reader = NikolaReader(transforms=transforms) + reader = NikolaReader(transforms=transforms, no_title_transform=no_title_transform) # For our custom logging, we have special needs and special settings we # specify here. # logger a logger from Nikola @@ -274,3 +295,10 @@ def rst2html(source, source_path=None, source_class=docutils.io.StringInput, pub.publish(enable_exit_status=enable_exit_status) return pub.writer.parts['docinfo'] + pub.writer.parts['fragment'], pub.document.reporter.max_level, pub.settings.record_dependencies + +# Alignment helpers for extensions +_align_options_base = ('left', 'center', 'right') + + +def _align_choice(argument): + return docutils.parsers.rst.directives.choice(argument, _align_options_base + ("none", "")) diff --git a/nikola/plugins/compile/rest/chart.py b/nikola/plugins/compile/rest/chart.py index ed8a250..24f459b 100644 --- a/nikola/plugins/compile/rest/chart.py +++ b/nikola/plugins/compile/rest/chart.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -52,6 +52,7 @@ class Plugin(RestExtension): global _site _site = self.site = site directives.register_directive('chart', Chart) + self.site.register_shortcode('chart', _gen_chart) return super(Plugin, self).set_site(site) @@ -72,52 +73,68 @@ class Chart(Directive): has_content = True required_arguments = 1 option_spec = { - "copy": directives.unchanged, + "box_mode": directives.unchanged, + "classes": directives.unchanged, "css": directives.unchanged, + "defs": directives.unchanged, "disable_xml_declaration": directives.unchanged, "dots_size": directives.unchanged, + "dynamic_print_values": directives.unchanged, "explicit_size": directives.unchanged, "fill": directives.unchanged, - "font_sizes": directives.unchanged, + "force_uri_protocol": directives.unchanged, + "half_pie": directives.unchanged, "height": directives.unchanged, "human_readable": directives.unchanged, "include_x_axis": directives.unchanged, + "inner_radius": directives.unchanged, "interpolate": directives.unchanged, "interpolation_parameters": directives.unchanged, "interpolation_precision": directives.unchanged, + "inverse_y_axis": directives.unchanged, "js": directives.unchanged, - "label_font_size": directives.unchanged, "legend_at_bottom": directives.unchanged, + "legend_at_bottom_columns": directives.unchanged, "legend_box_size": directives.unchanged, - "legend_font_size": directives.unchanged, "logarithmic": directives.unchanged, - "major_label_font_size": directives.unchanged, "margin": directives.unchanged, - "no_data_font_size": directives.unchanged, + "margin_bottom": directives.unchanged, + "margin_left": directives.unchanged, + "margin_right": directives.unchanged, + "margin_top": directives.unchanged, + "max_scale": directives.unchanged, + "min_scale": directives.unchanged, + "missing_value_fill_truncation": directives.unchanged, "no_data_text": directives.unchanged, "no_prefix": directives.unchanged, "order_min": directives.unchanged, "pretty_print": directives.unchanged, + "print_labels": directives.unchanged, "print_values": directives.unchanged, + "print_values_position": directives.unchanged, "print_zeroes": directives.unchanged, "range": directives.unchanged, "rounded_bars": directives.unchanged, + "secondary_range": directives.unchanged, "show_dots": directives.unchanged, "show_legend": directives.unchanged, "show_minor_x_labels": directives.unchanged, + "show_minor_y_labels": directives.unchanged, + "show_only_major_dots": directives.unchanged, + "show_x_guides": directives.unchanged, + "show_x_labels": directives.unchanged, + "show_y_guides": directives.unchanged, "show_y_labels": directives.unchanged, "spacing": directives.unchanged, + "stack_from_top": directives.unchanged, "strict": directives.unchanged, "stroke": directives.unchanged, + "stroke_style": directives.unchanged, "style": directives.unchanged, "title": directives.unchanged, - "title_font_size": directives.unchanged, - "to_dict": directives.unchanged, "tooltip_border_radius": directives.unchanged, - "tooltip_font_size": directives.unchanged, "truncate_label": directives.unchanged, "truncate_legend": directives.unchanged, - "value_font_size": directives.unchanged, "value_formatter": directives.unchanged, "width": directives.unchanged, "x_label_rotation": directives.unchanged, @@ -126,37 +143,55 @@ class Chart(Directive): "x_labels_major_count": directives.unchanged, "x_labels_major_every": directives.unchanged, "x_title": directives.unchanged, + "x_value_formatter": directives.unchanged, + "xrange": directives.unchanged, "y_label_rotation": directives.unchanged, "y_labels": directives.unchanged, + "y_labels_major": directives.unchanged, + "y_labels_major_count": directives.unchanged, + "y_labels_major_every": directives.unchanged, "y_title": directives.unchanged, "zero": directives.unchanged, } def run(self): """Run the directive.""" - if pygal is None: - msg = req_missing(['pygal'], 'use the Chart directive', optional=True) - return [nodes.raw('', '<div class="text-error">{0}</div>'.format(msg), format='html')] - options = {} - if 'style' in self.options: - style_name = self.options.pop('style') - else: - style_name = 'BlueStyle' - if '(' in style_name: # Parametric style - style = eval('pygal.style.' + style_name) - else: - style = getattr(pygal.style, style_name) - for k, v in self.options.items(): + self.options['site'] = None + html = _gen_chart(self.arguments[0], data='\n'.join(self.content), **self.options) + return [nodes.raw('', html, format='html')] + + +def _gen_chart(chart_type, **_options): + if pygal is None: + msg = req_missing(['pygal'], 'use the Chart directive', optional=True) + return '<div class="text-error">{0}</div>'.format(msg) + options = {} + data = _options.pop('data') + _options.pop('post', None) + _options.pop('site') + if 'style' in _options: + style_name = _options.pop('style') + else: + style_name = 'BlueStyle' + if '(' in style_name: # Parametric style + style = eval('pygal.style.' + style_name) + else: + style = getattr(pygal.style, style_name) + for k, v in _options.items(): + try: options[k] = literal_eval(v) - chart = pygal - for o in self.arguments[0].split('.'): - chart = getattr(chart, o) - chart = chart(style=style) - if _site and _site.invariant: - chart.no_prefix = True - chart.config(**options) - for line in self.content: + except: + options[k] = v + chart = pygal + for o in chart_type.split('.'): + chart = getattr(chart, o) + chart = chart(style=style) + if _site and _site.invariant: + chart.no_prefix = True + chart.config(**options) + for line in data.splitlines(): + line = line.strip() + if line: label, series = literal_eval('({0})'.format(line)) chart.add(label, series) - data = chart.render().decode('utf8') - return [nodes.raw('', data, format='html')] + return chart.render().decode('utf8') diff --git a/nikola/plugins/compile/rest/doc.py b/nikola/plugins/compile/rest/doc.py index 578f012..55f576d 100644 --- a/nikola/plugins/compile/rest/doc.py +++ b/nikola/plugins/compile/rest/doc.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -29,7 +29,7 @@ from docutils import nodes from docutils.parsers.rst import roles -from nikola.utils import split_explicit_title +from nikola.utils import split_explicit_title, LOGGER from nikola.plugin_categories import RestExtension @@ -42,12 +42,12 @@ class Plugin(RestExtension): """Set Nikola site.""" self.site = site roles.register_canonical_role('doc', doc_role) + self.site.register_shortcode('doc', doc_shortcode) doc_role.site = site return super(Plugin, self).set_site(site) -def doc_role(name, rawtext, text, lineno, inliner, - options={}, content=[]): +def _doc_link(rawtext, text, options={}, content=[]): """Handle the doc role.""" # split link's text and post's slug in role content has_explicit_title, title, slug = split_explicit_title(text) @@ -66,22 +66,48 @@ def doc_role(name, rawtext, text, lineno, inliner, if post is None: raise ValueError except ValueError: + return False, False, None, None, slug + + if not has_explicit_title: + # use post's title as link's text + title = post.title() + permalink = post.permalink() + + return True, twin_slugs, title, permalink, slug + + +def doc_role(name, rawtext, text, lineno, inliner, options={}, content=[]): + """Handle the doc role.""" + success, twin_slugs, title, permalink, slug = _doc_link(rawtext, text, options, content) + if success: + if twin_slugs: + inliner.reporter.warning( + 'More than one post with the same slug. Using "{0}"'.format(permalink)) + LOGGER.warn( + 'More than one post with the same slug. Using "{0}" for doc role'.format(permalink)) + node = make_link_node(rawtext, title, permalink, options) + return [node], [] + else: msg = inliner.reporter.error( '"{0}" slug doesn\'t exist.'.format(slug), line=lineno) prb = inliner.problematic(rawtext, rawtext, msg) return [prb], [msg] - if not has_explicit_title: - # use post's title as link's text - title = post.title() - permalink = post.permalink() - if twin_slugs: - msg = inliner.reporter.warning( - 'More than one post with the same slug. Using "{0}"'.format(permalink)) - node = make_link_node(rawtext, title, permalink, options) - return [node], [] +def doc_shortcode(*args, **kwargs): + """Implement the doc shortcode.""" + text = kwargs['data'] + success, twin_slugs, title, permalink, slug = _doc_link(text, text, LOGGER) + if success: + if twin_slugs: + LOGGER.warn( + 'More than one post with the same slug. Using "{0}" for doc shortcode'.format(permalink)) + return '<a href="{0}">{1}</a>'.format(permalink, title) + else: + LOGGER.error( + '"{0}" slug doesn\'t exist.'.format(slug)) + return '<span class="error text-error" style="color: red;">Invalid link: {0}</span>'.format(text) def make_link_node(rawtext, text, url, options): diff --git a/nikola/plugins/compile/rest/listing.py b/nikola/plugins/compile/rest/listing.py index 07f1686..4dfbedc 100644 --- a/nikola/plugins/compile/rest/listing.py +++ b/nikola/plugins/compile/rest/listing.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -136,6 +136,7 @@ class Plugin(RestExtension): # leaving these to make the code directive work with # docutils < 0.9 CodeBlock.site = site + Listing.site = site directives.register_directive('code', CodeBlock) directives.register_directive('code-block', CodeBlock) directives.register_directive('sourcecode', CodeBlock) @@ -189,8 +190,11 @@ class Listing(Include): self.content = fileobject.read().splitlines() self.state.document.settings.record_dependencies.add(fpath) target = urlunsplit(("link", 'listing', fpath.replace('\\', '/'), '', '')) + src_target = urlunsplit(("link", 'listing_source', fpath.replace('\\', '/'), '', '')) + src_label = self.site.MESSAGES('Source') generated_nodes = ( - [core.publish_doctree('`{0} <{1}>`_'.format(_fname, target))[0]]) + [core.publish_doctree('`{0} <{1}>`_ `({2}) <{3}>`_' .format( + _fname, target, src_label, src_target))[0]]) generated_nodes += self.get_code_from_file(fileobject) return generated_nodes diff --git a/nikola/plugins/compile/rest/media.py b/nikola/plugins/compile/rest/media.py index d075e44..8a69586 100644 --- a/nikola/plugins/compile/rest/media.py +++ b/nikola/plugins/compile/rest/media.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -48,6 +48,7 @@ class Plugin(RestExtension): """Set Nikola site.""" self.site = site directives.register_directive('media', Media) + self.site.register_shortcode('media', _gen_media_embed) return super(Plugin, self).set_site(site) @@ -60,9 +61,13 @@ class Media(Directive): def run(self): """Run media directive.""" - if micawber is None: - msg = req_missing(['micawber'], 'use the media directive', optional=True) - return [nodes.raw('', '<div class="text-error">{0}</div>'.format(msg), format='html')] + html = _gen_media_embed(" ".join(self.arguments)) + return [nodes.raw('', html, format='html')] - providers = micawber.bootstrap_basic() - return [nodes.raw('', micawber.parse_text(" ".join(self.arguments), providers), format='html')] + +def _gen_media_embed(url, *q, **kw): + if micawber is None: + msg = req_missing(['micawber'], 'use the media directive', optional=True) + return '<div class="text-error">{0}</div>'.format(msg) + providers = micawber.bootstrap_basic() + return micawber.parse_text(url, providers) diff --git a/nikola/plugins/compile/rest/post_list.py b/nikola/plugins/compile/rest/post_list.py index df9376b..8cfd5bf 100644 --- a/nikola/plugins/compile/rest/post_list.py +++ b/nikola/plugins/compile/rest/post_list.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2013-2015 Udo Spallek, Roberto Alsina and others. +# Copyright © 2013-2016 Udo Spallek, Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -37,6 +37,7 @@ from docutils.parsers.rst import Directive, directives from nikola import utils from nikola.plugin_categories import RestExtension +from nikola.packages.datecond import date_in_range # WARNING: the directive name is post-list # (with a DASH instead of an UNDERSCORE) @@ -50,6 +51,7 @@ class Plugin(RestExtension): def set_site(self, site): """Set Nikola site.""" self.site = site + self.site.register_shortcode('post-list', _do_post_list) directives.register_directive('post-list', PostList) PostList.site = site return super(Plugin, self).set_site(site) @@ -61,7 +63,7 @@ class PostList(Directive): Post List ========= :Directive Arguments: None. - :Directive Options: lang, start, stop, reverse, sort, tags, categories, slugs, all, template, id + :Directive Options: lang, start, stop, reverse, sort, date, tags, categories, sections, slugs, post_type, all, template, id :Directive Content: None. The posts appearing in the list can be filtered by options. @@ -85,10 +87,19 @@ class PostList(Directive): Reverse the order of the post-list. Defaults is to not reverse the order of posts. - ``sort``: string + ``sort`` : string Sort post list by one of each post's attributes, usually ``title`` or a custom ``priority``. Defaults to None (chronological sorting). + ``date`` : string + Show posts that match date range specified by this option. Format: + + * comma-separated clauses (AND) + * clause: attribute comparison_operator value (spaces optional) + * attribute: year, month, day, hour, month, second, weekday, isoweekday; or empty for full datetime + * comparison_operator: == != <= >= < > + * value: integer or dateutil-compatible date input + ``tags`` : string [, string...] Filter posts to show only posts having at least one of the ``tags``. Defaults to None. @@ -97,13 +108,21 @@ class PostList(Directive): Filter posts to show only posts having one of the ``categories``. Defaults to None. + ``sections`` : string [, string...] + Filter posts to show only posts having one of the ``sections``. + Defaults to None. + ``slugs`` : string [, string...] Filter posts to show only posts having at least one of the ``slugs``. Defaults to None. + ``post_type`` (or ``type``) : string + Show only ``posts``, ``pages`` or ``all``. + Replaces ``all``. Defaults to ``posts``. + ``all`` : flag - Shows all posts and pages in the post list. - Defaults to show only posts with set *use_in_feeds*. + (deprecated, use ``post_type`` instead) + Shows all posts and pages in the post list. Defaults to show only posts. ``lang`` : string The language of post *titles* and *links*. @@ -125,11 +144,15 @@ class PostList(Directive): 'sort': directives.unchanged, 'tags': directives.unchanged, 'categories': directives.unchanged, + 'sections': directives.unchanged, 'slugs': directives.unchanged, + 'post_type': directives.unchanged, + 'type': directives.unchanged, 'all': directives.flag, 'lang': directives.unchanged, 'template': directives.path, 'id': directives.unchanged, + 'date': directives.unchanged, } def run(self): @@ -138,75 +161,151 @@ class PostList(Directive): stop = self.options.get('stop') reverse = self.options.get('reverse', False) tags = self.options.get('tags') - tags = [t.strip().lower() for t in tags.split(',')] if tags else [] categories = self.options.get('categories') - categories = [c.strip().lower() for c in categories.split(',')] if categories else [] + sections = self.options.get('sections') slugs = self.options.get('slugs') - slugs = [s.strip() for s in slugs.split(',')] if slugs else [] - show_all = self.options.get('all', False) + post_type = self.options.get('post_type') + type = self.options.get('type', False) + all = self.options.get('all', False) lang = self.options.get('lang', utils.LocaleBorg().current_lang) template = self.options.get('template', 'post_list_directive.tmpl') sort = self.options.get('sort') - if self.site.invariant: # for testing purposes - post_list_id = self.options.get('id', 'post_list_' + 'fixedvaluethatisnotauuid') - else: - post_list_id = self.options.get('id', 'post_list_' + uuid.uuid4().hex) + date = self.options.get('date') - filtered_timeline = [] - posts = [] - step = -1 if reverse is None else None - if show_all is None: - timeline = [p for p in self.site.timeline] + output, deps = _do_post_list(start, stop, reverse, tags, categories, sections, slugs, post_type, type, + all, lang, template, sort, state=self.state, site=self.site, date=date) + self.state.document.settings.record_dependencies.add("####MAGIC####TIMELINE") + for d in deps: + self.state.document.settings.record_dependencies.add(d) + if output: + return [nodes.raw('', output, format='html')] else: - timeline = [p for p in self.site.timeline if p.use_in_feeds] - - if categories: - timeline = [p for p in timeline if p.meta('category', lang=lang).lower() in categories] - - for post in timeline: - if tags: - cont = True - tags_lower = [t.lower() for t in post.tags] - for tag in tags: - if tag in tags_lower: - cont = False - - if cont: - continue - - filtered_timeline.append(post) - - if sort: - filtered_timeline = natsort.natsorted(filtered_timeline, key=lambda post: post.meta[lang][sort], alg=natsort.ns.F | natsort.ns.IC) - - for post in filtered_timeline[start:stop:step]: - if slugs: - cont = True - for slug in slugs: - if slug == post.meta('slug'): - cont = False - - if cont: - continue - - bp = post.translated_base_path(lang) - if os.path.exists(bp): - self.state.document.settings.record_dependencies.add(bp) + return [] - posts += [post] - if not posts: - return [] - self.state.document.settings.record_dependencies.add("####MAGIC####TIMELINE") +def _do_post_list(start=None, stop=None, reverse=False, tags=None, categories=None, + sections=None, slugs=None, post_type='post', type=False, all=False, + lang=None, template='post_list_directive.tmpl', sort=None, + id=None, data=None, state=None, site=None, date=None, filename=None, post=None): + if lang is None: + lang = utils.LocaleBorg().current_lang + if site.invariant: # for testing purposes + post_list_id = id or 'post_list_' + 'fixedvaluethatisnotauuid' + else: + post_list_id = id or 'post_list_' + uuid.uuid4().hex + + # Get post from filename if available + if filename: + self_post = site.post_per_input_file.get(filename) + else: + self_post = None + + if self_post: + self_post.register_depfile("####MAGIC####TIMELINE", lang=lang) + + # If we get strings for start/stop, make them integers + if start is not None: + start = int(start) + if stop is not None: + stop = int(stop) + + # Parse tags/categories/sections/slugs (input is strings) + tags = [t.strip().lower() for t in tags.split(',')] if tags else [] + categories = [c.strip().lower() for c in categories.split(',')] if categories else [] + sections = [s.strip().lower() for s in sections.split(',')] if sections else [] + slugs = [s.strip() for s in slugs.split(',')] if slugs else [] + + filtered_timeline = [] + posts = [] + step = -1 if reverse is None else None + + if type is not False: + post_type = type + + # TODO: remove in v8 + if all is not False: + timeline = [p for p in site.timeline] + elif post_type == 'page' or post_type == 'pages': + timeline = [p for p in site.timeline if not p.use_in_feeds] + elif post_type == 'all': + timeline = [p for p in site.timeline] + else: # post + timeline = [p for p in site.timeline if p.use_in_feeds] + + # TODO: replaces all, uncomment in v8 + # if post_type == 'page' or post_type == 'pages': + # timeline = [p for p in site.timeline if not p.use_in_feeds] + # elif post_type == 'all': + # timeline = [p for p in site.timeline] + # else: # post + # timeline = [p for p in site.timeline if p.use_in_feeds] + + if categories: + timeline = [p for p in timeline if p.meta('category', lang=lang).lower() in categories] + + if sections: + timeline = [p for p in timeline if p.section_name(lang).lower() in sections] + + for post in timeline: + if tags: + cont = True + tags_lower = [t.lower() for t in post.tags] + for tag in tags: + if tag in tags_lower: + cont = False + + if cont: + continue + + filtered_timeline.append(post) + + if sort: + filtered_timeline = natsort.natsorted(filtered_timeline, key=lambda post: post.meta[lang][sort], alg=natsort.ns.F | natsort.ns.IC) + + if date: + filtered_timeline = [p for p in filtered_timeline if date_in_range(date, p.date)] + + for post in filtered_timeline[start:stop:step]: + if slugs: + cont = True + for slug in slugs: + if slug == post.meta('slug'): + cont = False + + if cont: + continue + + bp = post.translated_base_path(lang) + if os.path.exists(bp) and state: + state.document.settings.record_dependencies.add(bp) + elif os.path.exists(bp) and self_post: + self_post.register_depfile(bp, lang=lang) + + posts += [post] + + if not posts: + return '', [] + + template_deps = site.template_system.template_deps(template) + if state: + # Register template as a dependency (Issue #2391) + for d in template_deps: + state.document.settings.record_dependencies.add(d) + elif self_post: + for d in template_deps: + self_post.register_depfile(d, lang=lang) + + template_data = { + 'lang': lang, + 'posts': posts, + # Need to provide str, not TranslatableSetting (Issue #2104) + 'date_format': site.GLOBAL_CONTEXT.get('date_format')[lang], + 'post_list_id': post_list_id, + 'messages': site.MESSAGES, + } + output = site.template_system.render_template( + template, None, template_data) + return output, template_deps - template_data = { - 'lang': lang, - 'posts': posts, - # Need to provide str, not TranslatableSetting (Issue #2104) - 'date_format': self.site.GLOBAL_CONTEXT.get('date_format')[lang], - 'post_list_id': post_list_id, - 'messages': self.site.MESSAGES, - } - output = self.site.template_system.render_template( - template, None, template_data) - return [nodes.raw('', output, format='html')] +# Request file name from shortcode (Issue #2412) +_do_post_list.nikola_shortcode_pass_filename = True diff --git a/nikola/plugins/compile/rest/slides.py b/nikola/plugins/compile/rest/slides.py index eb2cc97..7c5b34b 100644 --- a/nikola/plugins/compile/rest/slides.py +++ b/nikola/plugins/compile/rest/slides.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/compile/rest/soundcloud.py b/nikola/plugins/compile/rest/soundcloud.py index 2577ff1..9fabe70 100644 --- a/nikola/plugins/compile/rest/soundcloud.py +++ b/nikola/plugins/compile/rest/soundcloud.py @@ -4,7 +4,7 @@ from docutils import nodes from docutils.parsers.rst import Directive, directives - +from nikola.plugins.compile.rest import _align_choice, _align_options_base from nikola.plugin_categories import RestExtension @@ -22,11 +22,13 @@ class Plugin(RestExtension): return super(Plugin, self).set_site(site) -CODE = ("""<iframe width="{width}" height="{height}" +CODE = """\ +<div class="soundcloud-player{align}"> +<iframe width="{width}" height="{height}" scrolling="no" frameborder="no" -src="https://w.soundcloud.com/player/?url=http://api.soundcloud.com/{preslug}/""" - """{sid}"> -</iframe>""") +src="https://w.soundcloud.com/player/?url=http://api.soundcloud.com/{preslug}/{sid}"> +</iframe> +</div>""" class SoundCloud(Directive): @@ -44,6 +46,7 @@ class SoundCloud(Directive): option_spec = { 'width': directives.positive_int, 'height': directives.positive_int, + "align": _align_choice } preslug = "tracks" @@ -57,6 +60,10 @@ class SoundCloud(Directive): 'preslug': self.preslug, } options.update(self.options) + if self.options.get('align') in _align_options_base: + options['align'] = ' align-' + self.options['align'] + else: + options['align'] = '' return [nodes.raw('', CODE.format(**options), format='html')] def check_content(self): diff --git a/nikola/plugins/compile/rest/thumbnail.py b/nikola/plugins/compile/rest/thumbnail.py index c24134a..37e0973 100644 --- a/nikola/plugins/compile/rest/thumbnail.py +++ b/nikola/plugins/compile/rest/thumbnail.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2014-2015 Pelle Nilsson and others. +# Copyright © 2014-2016 Pelle Nilsson and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/rest/vimeo.py b/nikola/plugins/compile/rest/vimeo.py index 29ce5c1..f1ac6c3 100644 --- a/nikola/plugins/compile/rest/vimeo.py +++ b/nikola/plugins/compile/rest/vimeo.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -28,6 +28,7 @@ from docutils import nodes from docutils.parsers.rst import Directive, directives +from nikola.plugins.compile.rest import _align_choice, _align_options_base import requests import json @@ -48,10 +49,12 @@ class Plugin(RestExtension): return super(Plugin, self).set_site(site) -CODE = """<iframe src="//player.vimeo.com/video/{vimeo_id}" +CODE = """<div class="vimeo-video{align}"> +<iframe src="https://player.vimeo.com/video/{vimeo_id}" width="{width}" height="{height}" frameborder="0" webkitAllowFullScreen="webkitAllowFullScreen" mozallowfullscreen="mozallowfullscreen" allowFullScreen="allowFullScreen"> </iframe> +</div> """ VIDEO_DEFAULT_HEIGHT = 500 @@ -73,6 +76,7 @@ class Vimeo(Directive): option_spec = { "width": directives.positive_int, "height": directives.positive_int, + "align": _align_choice } # set to False for not querying the vimeo api for size @@ -92,6 +96,10 @@ class Vimeo(Directive): return err self.set_video_size() options.update(self.options) + if self.options.get('align') in _align_options_base: + options['align'] = ' align-' + self.options['align'] + else: + options['align'] = '' return [nodes.raw('', CODE.format(**options), format='html')] def check_modules(self): @@ -107,7 +115,7 @@ class Vimeo(Directive): if json: # we can attempt to retrieve video attributes from vimeo try: - url = ('//vimeo.com/api/v2/video/{0}' + url = ('https://vimeo.com/api/v2/video/{0}' '.json'.format(self.arguments[0])) data = requests.get(url).text video_attributes = json.loads(data)[0] diff --git a/nikola/plugins/compile/rest/youtube.py b/nikola/plugins/compile/rest/youtube.py index b3b84b0..b3dde62 100644 --- a/nikola/plugins/compile/rest/youtube.py +++ b/nikola/plugins/compile/rest/youtube.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -28,7 +28,7 @@ from docutils import nodes from docutils.parsers.rst import Directive, directives - +from nikola.plugins.compile.rest import _align_choice, _align_options_base from nikola.plugin_categories import RestExtension @@ -46,10 +46,11 @@ class Plugin(RestExtension): CODE = """\ -<iframe width="{width}" -height="{height}" -src="//www.youtube.com/embed/{yid}?rel=0&hd=1&wmode=transparent" -></iframe>""" +<div class="youtube-video{align}"> +<iframe width="{width}" height="{height}" +src="https://www.youtube.com/embed/{yid}?rel=0&hd=1&wmode=transparent" +></iframe> +</div>""" class Youtube(Directive): @@ -67,6 +68,7 @@ class Youtube(Directive): option_spec = { "width": directives.positive_int, "height": directives.positive_int, + "align": _align_choice } def run(self): @@ -78,6 +80,10 @@ class Youtube(Directive): 'height': 344, } options.update(self.options) + if self.options.get('align') in _align_options_base: + options['align'] = ' align-' + self.options['align'] + else: + options['align'] = '' return [nodes.raw('', CODE.format(**options), format='html')] def check_content(self): diff --git a/nikola/plugins/misc/__init__.py b/nikola/plugins/misc/__init__.py index c0d8961..518fac1 100644 --- a/nikola/plugins/misc/__init__.py +++ b/nikola/plugins/misc/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/misc/scan_posts.py b/nikola/plugins/misc/scan_posts.py index 9db4533..f584a05 100644 --- a/nikola/plugins/misc/scan_posts.py +++ b/nikola/plugins/misc/scan_posts.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -35,6 +35,8 @@ from nikola.plugin_categories import PostScanner from nikola import utils from nikola.post import Post +LOGGER = utils.get_logger('scan_posts', utils.STDERR_HANDLER) + class ScanPosts(PostScanner): """Scan posts in the site.""" @@ -87,15 +89,19 @@ class ScanPosts(PostScanner): continue else: seen.add(base_path) - post = Post( - base_path, - self.site.config, - dest_dir, - use_in_feeds, - self.site.MESSAGES, - template_name, - self.site.get_compiler(base_path) - ) - timeline.append(post) + try: + post = Post( + base_path, + self.site.config, + dest_dir, + use_in_feeds, + self.site.MESSAGES, + template_name, + self.site.get_compiler(base_path) + ) + timeline.append(post) + except Exception as err: + LOGGER.error('Error reading post {}'.format(base_path)) + raise err return timeline diff --git a/nikola/plugins/shortcode/gist.plugin b/nikola/plugins/shortcode/gist.plugin new file mode 100644 index 0000000..cd19a72 --- /dev/null +++ b/nikola/plugins/shortcode/gist.plugin @@ -0,0 +1,13 @@ +[Core] +name = gist +module = gist + +[Nikola] +plugincategory = Shortcode + +[Documentation] +author = Roberto Alsina +version = 0.1 +website = https://getnikola.com/ +description = Gist shortcode + diff --git a/nikola/plugins/shortcode/gist.py b/nikola/plugins/shortcode/gist.py new file mode 100644 index 0000000..64fd0d9 --- /dev/null +++ b/nikola/plugins/shortcode/gist.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# This file is public domain according to its author, Brian Hsu + +"""Gist directive for reStructuredText.""" + +import requests + +from nikola.plugin_categories import ShortcodePlugin + + +class Plugin(ShortcodePlugin): + """Plugin for gist directive.""" + + name = "gist" + + def set_site(self, site): + """Set Nikola site.""" + self.site = site + site.register_shortcode('gist', self.handler) + return super(Plugin, self).set_site(site) + + def get_raw_gist_with_filename(self, gistID, filename): + """Get raw gist text for a filename.""" + url = '/'.join(("https://gist.github.com/raw", gistID, filename)) + return requests.get(url).text + + def get_raw_gist(self, gistID): + """Get raw gist text.""" + url = "https://gist.github.com/raw/{0}".format(gistID) + try: + return requests.get(url).text + except requests.exceptions.RequestException: + raise self.error('Cannot get gist for url={0}'.format(url)) + + def handler(self, gistID, filename=None, site=None, data=None, lang=None, post=None): + """Create HTML for gist.""" + if 'https://' in gistID: + gistID = gistID.split('/')[-1].strip() + else: + gistID = gistID.strip() + embedHTML = "" + rawGist = "" + + if filename is not None: + rawGist = (self.get_raw_gist_with_filename(gistID, filename)) + embedHTML = ('<script src="https://gist.github.com/{0}.js' + '?file={1}"></script>').format(gistID, filename) + else: + rawGist = (self.get_raw_gist(gistID)) + embedHTML = ('<script src="https://gist.github.com/{0}.js">' + '</script>').format(gistID) + + output = '''{} + <noscript><pre>{}</pre></noscript>'''.format(embedHTML, rawGist) + + return output, [] diff --git a/nikola/plugins/task/__init__.py b/nikola/plugins/task/__init__.py index fd9a48f..4eeae62 100644 --- a/nikola/plugins/task/__init__.py +++ b/nikola/plugins/task/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 3cdd33b..303d349 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index 081d21d..ec61800 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2015 Juanjo Conti. +# Copyright © 2015-2016 Juanjo Conti and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -35,6 +35,8 @@ except ImportError: from urllib.parse import urljoin # NOQA from collections import defaultdict +from blinker import signal + from nikola.plugin_categories import Task from nikola import utils @@ -47,13 +49,20 @@ class RenderAuthors(Task): 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 = { @@ -78,12 +87,10 @@ class RenderAuthors(Task): "index_file": self.site.config['INDEX_FILE'], } - yield self.group_task() self.site.scan_posts() + yield self.group_task() - 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: + 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 @@ -244,10 +251,13 @@ class RenderAuthors(Task): } return utils.apply_filters(task, kw['filters']) - def slugify_author_name(self, name): + 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 = '' if self.site.config['SLUG_AUTHOR_PATH']: - name = utils.slugify(name) + name = utils.slugify(name, lang) return name def author_index_path(self, name, lang): @@ -272,13 +282,13 @@ class RenderAuthors(Task): return [_f for _f in [ self.site.config['TRANSLATIONS'][lang], self.site.config['AUTHOR_PATH'], - self.slugify_author_name(name), + self.slugify_author_name(name, lang), 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] + self.slugify_author_name(name, lang) + ".html"] if _f] def author_atom_path(self, name, lang): """Link to an author's Atom feed. @@ -288,7 +298,7 @@ class RenderAuthors(Task): 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 + self.site.config['AUTHOR_PATH'], self.slugify_author_name(name, lang) + ".atom"] if _f] def author_rss_path(self, name, lang): @@ -299,7 +309,7 @@ class RenderAuthors(Task): 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 + self.site.config['AUTHOR_PATH'], self.slugify_author_name(name, lang) + ".xml"] if _f] def _add_extension(self, path, extension): diff --git a/nikola/plugins/task/bundles.py b/nikola/plugins/task/bundles.py index e709133..b33d8e0 100644 --- a/nikola/plugins/task/bundles.py +++ b/nikola/plugins/task/bundles.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/task/copy_assets.py b/nikola/plugins/task/copy_assets.py index 2cab71a..4ed7414 100644 --- a/nikola/plugins/task/copy_assets.py +++ b/nikola/plugins/task/copy_assets.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/task/copy_files.py b/nikola/plugins/task/copy_files.py index 0488011..6f6cfb8 100644 --- a/nikola/plugins/task/copy_files.py +++ b/nikola/plugins/task/copy_files.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/task/galleries.py b/nikola/plugins/task/galleries.py index d3f1db7..edfd33d 100644 --- a/nikola/plugins/task/galleries.py +++ b/nikola/plugins/task/galleries.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -33,7 +33,6 @@ import io import json import mimetypes import os -import sys try: from urlparse import urljoin except ImportError: @@ -86,6 +85,8 @@ class Galleries(Task, ImageProcessor): 'tzinfo': site.tzinfo, 'comments_in_galleries': site.config['COMMENTS_IN_GALLERIES'], 'generate_rss': site.config['GENERATE_RSS'], + 'preserve_exif_data': site.config['PRESERVE_EXIF_DATA'], + 'exif_whitelist': site.config['EXIF_WHITELIST'], } # Verify that no folder in GALLERY_FOLDERS appears twice @@ -93,8 +94,8 @@ class Galleries(Task, ImageProcessor): for source, dest in self.kw['gallery_folders'].items(): if source in appearing_paths or dest in appearing_paths: problem = source if source in appearing_paths else dest - utils.LOGGER.error("The gallery input or output folder '{0}' appears in more than one entry in GALLERY_FOLDERS, exiting.".format(problem)) - sys.exit(1) + utils.LOGGER.error("The gallery input or output folder '{0}' appears in more than one entry in GALLERY_FOLDERS, ignoring.".format(problem)) + continue appearing_paths.add(source) appearing_paths.add(dest) @@ -115,10 +116,11 @@ class Galleries(Task, ImageProcessor): if len(candidates) == 1: return candidates[0] self.logger.error("Gallery name '{0}' is not unique! Possible output paths: {1}".format(name, candidates)) + raise RuntimeError("Gallery name '{0}' is not unique! Possible output paths: {1}".format(name, candidates)) else: self.logger.error("Unknown gallery '{0}'!".format(name)) self.logger.info("Known galleries: " + str(list(self.proper_gallery_links.keys()))) - sys.exit(1) + raise RuntimeError("Unknown gallery '{0}'!".format(name)) def gallery_path(self, name, lang): """Link to an image gallery's path. @@ -173,6 +175,7 @@ class Galleries(Task, ImageProcessor): for k, v in self.site.GLOBAL_CONTEXT['template_hooks'].items(): self.kw['||template_hooks|{0}||'.format(k)] = v._items + self.site.scan_posts() yield self.group_task() template_name = "gallery.tmpl" @@ -194,13 +197,6 @@ class Galleries(Task, ImageProcessor): # Create image list, filter exclusions image_list = self.get_image_list(gallery) - # Sort as needed - # Sort by date - if self.kw['sort_by_date']: - image_list.sort(key=lambda a: self.image_date(a)) - else: # Sort by name - image_list.sort() - # Create thumbnails and large images in destination for image in image_list: for task in self.create_target_images(image, input_folder): @@ -211,8 +207,6 @@ class Galleries(Task, ImageProcessor): for task in self.remove_excluded_image(image, input_folder): yield task - crumbs = utils.get_crumbs(gallery, index_folder=self) - for lang in self.kw['translations']: # save navigation links as dependencies self.kw['navigation_links|{0}'.format(lang)] = self.kw['global_context']['navigation_links'](lang) @@ -242,7 +236,7 @@ class Galleries(Task, ImageProcessor): img_titles = [] for fn in image_name_list: name_without_ext = os.path.splitext(os.path.basename(fn))[0] - img_titles.append(utils.unslugify(name_without_ext)) + img_titles.append(utils.unslugify(name_without_ext, lang)) else: img_titles = [''] * len(image_name_list) @@ -266,7 +260,7 @@ class Galleries(Task, ImageProcessor): context["folders"] = natsort.natsorted( folders, alg=natsort.ns.F | natsort.ns.IC) - context["crumbs"] = crumbs + context["crumbs"] = utils.get_crumbs(gallery, index_folder=self, lang=lang) context["permalink"] = self.site.link("gallery", gallery, lang) context["enable_comments"] = self.kw['comments_in_galleries'] context["thumbnail_size"] = self.kw["thumbnail_size"] @@ -423,6 +417,8 @@ class Galleries(Task, ImageProcessor): # may break) if post.title == 'index': post.title = os.path.split(gallery)[1] + # Register the post (via #2417) + self.site.post_per_input_file[index_path] = post else: post = None return post @@ -482,7 +478,8 @@ class Galleries(Task, ImageProcessor): 'targets': [thumb_path], 'actions': [ (self.resize_image, - (img, thumb_path, self.kw['thumbnail_size'])) + (img, thumb_path, self.kw['thumbnail_size'], False, self.kw['preserve_exif_data'], + self.kw['exif_whitelist'])) ], 'clean': True, 'uptodate': [utils.config_changed({ @@ -497,7 +494,8 @@ class Galleries(Task, ImageProcessor): 'targets': [orig_dest_path], 'actions': [ (self.resize_image, - (img, orig_dest_path, self.kw['max_image_size'])) + (img, orig_dest_path, self.kw['max_image_size'], False, self.kw['preserve_exif_data'], + self.kw['exif_whitelist'])) ], 'clean': True, 'uptodate': [utils.config_changed({ @@ -558,6 +556,18 @@ class Galleries(Task, ImageProcessor): url = '/'.join(os.path.relpath(p, os.path.dirname(output_name) + os.sep).split(os.sep)) return url + all_data = list(zip(img_list, thumbs, img_titles)) + + if self.kw['sort_by_date']: + all_data.sort(key=lambda a: self.image_date(a[0])) + else: # Sort by name + all_data.sort(key=lambda a: a[0]) + + if all_data: + img_list, thumbs, img_titles = zip(*all_data) + else: + img_list, thumbs, img_titles = [], [], [] + photo_array = [] for img, thumb, title in zip(img_list, thumbs, img_titles): w, h = _image_size_cache.get(thumb, (None, None)) @@ -591,6 +601,18 @@ class Galleries(Task, ImageProcessor): def make_url(url): return urljoin(self.site.config['BASE_URL'], url.lstrip('/')) + all_data = list(zip(img_list, dest_img_list, img_titles)) + + if self.kw['sort_by_date']: + all_data.sort(key=lambda a: self.image_date(a[0])) + else: # Sort by name + all_data.sort(key=lambda a: a[0]) + + if all_data: + img_list, dest_img_list, img_titles = zip(*all_data) + else: + img_list, dest_img_list, img_titles = [], [], [] + items = [] for img, srcimg, title in list(zip(dest_img_list, img_list, img_titles))[:self.kw["feed_length"]]: img_size = os.stat( diff --git a/nikola/plugins/task/gzip.py b/nikola/plugins/task/gzip.py index aaa213d..79a11dc 100644 --- a/nikola/plugins/task/gzip.py +++ b/nikola/plugins/task/gzip.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index 2ab97fa..8ecd1de 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -36,6 +36,7 @@ except ImportError: from nikola.plugin_categories import Task from nikola import utils +from nikola.nikola import _enclosure class Indexes(Task): @@ -51,6 +52,7 @@ class Indexes(Task): 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): @@ -77,6 +79,10 @@ class Indexes(Task): "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'], @@ -85,6 +91,7 @@ class Indexes(Task): "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" @@ -110,8 +117,6 @@ class Indexes(Task): 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) @@ -145,16 +150,16 @@ class Indexes(Task): 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"]: + if self.site.config["POSTS_SECTION_ARE_INDEXES"]: context["pagekind"].append("index") - kw["posts_section_title"] = self.site.config['POSTS_SECTION_TITLE'](lang) + 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 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)) @@ -168,7 +173,37 @@ class Indexes(Task): task['basename'] = self.name yield task - if not self.site.config["STORY_INDEX"]: + # 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 = { "translations": self.site.config['TRANSLATIONS'], @@ -207,7 +242,7 @@ class Indexes(Task): for post in post_list: # If there is an index.html pending to be created from - # a story, do not generate the STORY_INDEX + # a page, do not generate the PAGE_INDEX if post.destination_path(lang) == short_destination: should_render = False else: @@ -252,7 +287,7 @@ class Indexes(Task): self.site, extension=extension) - def index_section_path(self, name, lang, is_feed=False): + def index_section_path(self, name, lang, is_feed=False, is_rss=False): """Link to the index for a section. Example: @@ -264,6 +299,8 @@ class Indexes(Task): 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]: @@ -298,3 +335,12 @@ class Indexes(Task): 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/listings.py b/nikola/plugins/task/listings.py index 891f361..e694aa5 100644 --- a/nikola/plugins/task/listings.py +++ b/nikola/plugins/task/listings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -29,12 +29,11 @@ from __future__ import unicode_literals, print_function from collections import defaultdict -import sys import os import lxml.html from pygments import highlight -from pygments.lexers import get_lexer_for_filename, TextLexer +from pygments.lexers import get_lexer_for_filename, guess_lexer, TextLexer import natsort from nikola.plugin_categories import Task @@ -55,6 +54,7 @@ class Listings(Task): def set_site(self, site): """Set Nikola site.""" site.register_path_handler('listing', self.listing_path) + site.register_path_handler('listing_source', self.listing_source_path) # We need to prepare some things for the listings path handler to work. @@ -73,7 +73,7 @@ class Listings(Task): if source in appearing_paths or dest in appearing_paths: problem = source if source in appearing_paths else dest utils.LOGGER.error("The listings input or output folder '{0}' appears in more than one entry in LISTINGS_FOLDERS, exiting.".format(problem)) - sys.exit(1) + continue appearing_paths.add(source) appearing_paths.add(dest) @@ -127,7 +127,11 @@ class Listings(Task): try: lexer = get_lexer_for_filename(in_name) except: - lexer = TextLexer() + try: + lexer = guess_lexer(fd.read()) + except: + lexer = TextLexer() + fd.seek(0) code = highlight(fd.read(), lexer, utils.NikolaPygmentsHTML(in_name)) title = os.path.basename(in_name) else: @@ -145,7 +149,7 @@ class Listings(Task): os.path.join( self.kw['output_folder'], output_folder)))) - if self.site.config['COPY_SOURCES'] and in_name: + if in_name: source_link = permalink[:-5] # remove '.html' else: source_link = None @@ -238,19 +242,35 @@ class Listings(Task): 'uptodate': [utils.config_changed(uptodate, 'nikola.plugins.task.listings:source')], 'clean': True, }, self.kw["filters"]) - if self.site.config['COPY_SOURCES']: - rel_name = os.path.join(rel_path, f) - rel_output_name = os.path.join(output_folder, rel_path, f) - self.register_output_name(input_folder, rel_name, rel_output_name) - out_name = os.path.join(self.kw['output_folder'], rel_output_name) - yield utils.apply_filters({ - 'basename': self.name, - 'name': out_name, - 'file_dep': [in_name], - 'targets': [out_name], - 'actions': [(utils.copy_file, [in_name, out_name])], - 'clean': True, - }, self.kw["filters"]) + + rel_name = os.path.join(rel_path, f) + rel_output_name = os.path.join(output_folder, rel_path, f) + self.register_output_name(input_folder, rel_name, rel_output_name) + out_name = os.path.join(self.kw['output_folder'], rel_output_name) + yield utils.apply_filters({ + 'basename': self.name, + 'name': out_name, + 'file_dep': [in_name], + 'targets': [out_name], + 'actions': [(utils.copy_file, [in_name, out_name])], + 'clean': True, + }, self.kw["filters"]) + + def listing_source_path(self, name, lang): + """A link to the source code for a listing. + + It will try to use the file name if it's not ambiguous, or the file path. + + Example: + + link://listing_source/hello.py => /listings/tutorial/hello.py + + link://listing_source/tutorial/hello.py => /listings/tutorial/hello.py + """ + result = self.listing_path(name, lang) + if result[-1].endswith('.html'): + result[-1] = result[-1][:-5] + return result def listing_path(self, namep, lang): """A link to a listing. @@ -275,14 +295,14 @@ class Listings(Task): # ambiguities. if len(self.improper_input_file_mapping[name]) > 1: utils.LOGGER.error("Using non-unique listing name '{0}', which maps to more than one listing name ({1})!".format(name, str(self.improper_input_file_mapping[name]))) - sys.exit(1) + return ["ERROR"] 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 = list(self.improper_input_file_mapping[name])[0] break else: utils.LOGGER.error("Unknown listing name {0}!".format(namep)) - sys.exit(1) + return ["ERROR"] if not name.endswith(os.sep + self.site.config["INDEX_FILE"]): name += '.html' path_parts = name.split(os.sep) diff --git a/nikola/plugins/task/pages.py b/nikola/plugins/task/pages.py index 8d41035..7d8287b 100644 --- a/nikola/plugins/task/pages.py +++ b/nikola/plugins/task/pages.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -54,7 +54,7 @@ class RenderPages(Task): if post.is_post: context = {'pagekind': ['post_page']} else: - context = {'pagekind': ['story_page']} + context = {'pagekind': ['story_page', 'page_page']} for task in self.site.generic_page_renderer(lang, post, kw["filters"], context): task['uptodate'] = task['uptodate'] + [config_changed(kw, 'nikola.plugins.task.pages')] task['basename'] = self.name diff --git a/nikola/plugins/task/posts.py b/nikola/plugins/task/posts.py index 8735beb..fe10c5f 100644 --- a/nikola/plugins/task/posts.py +++ b/nikola/plugins/task/posts.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/task/py3_switch.py b/nikola/plugins/task/py3_switch.py index 930c593..2ff4e2d 100644 --- a/nikola/plugins/task/py3_switch.py +++ b/nikola/plugins/task/py3_switch.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -51,7 +51,7 @@ PY2_BARBS = [ "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!", + "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" diff --git a/nikola/plugins/task/redirect.py b/nikola/plugins/task/redirect.py index 2d4eba4..b170b81 100644 --- a/nikola/plugins/task/redirect.py +++ b/nikola/plugins/task/redirect.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -50,7 +50,7 @@ class Redirect(Task): yield self.group_task() if kw['redirections']: for src, dst in kw["redirections"]: - src_path = os.path.join(kw["output_folder"], src) + src_path = os.path.join(kw["output_folder"], src.lstrip('/')) yield utils.apply_filters({ 'basename': self.name, 'name': src_path, diff --git a/nikola/plugins/task/robots.py b/nikola/plugins/task/robots.py index 7c7f5df..8537fc8 100644 --- a/nikola/plugins/task/robots.py +++ b/nikola/plugins/task/robots.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/task/rss.py b/nikola/plugins/task/rss.py index be57f5c..780559b 100644 --- a/nikola/plugins/task/rss.py +++ b/nikola/plugins/task/rss.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -34,6 +34,7 @@ except ImportError: from urllib.parse import urljoin # NOQA from nikola import utils +from nikola.nikola import _enclosure from nikola.plugin_categories import Task @@ -97,7 +98,7 @@ class GenerateRSS(Task): (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, - None, kw["feed_links_append_query"]))], + _enclosure, kw["feed_links_append_query"]))], 'task_dep': ['render_posts'], 'clean': True, diff --git a/nikola/plugins/task/scale_images.py b/nikola/plugins/task/scale_images.py index e55dc6c..2b483ae 100644 --- a/nikola/plugins/task/scale_images.py +++ b/nikola/plugins/task/scale_images.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2014-2015 Pelle Nilsson and others. +# Copyright © 2014-2016 Pelle Nilsson and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -71,8 +71,8 @@ class ScaleImage(Task, ImageProcessor): def process_image(self, src, dst, thumb): """Resize an image.""" - self.resize_image(src, dst, self.kw['max_image_size'], False) - self.resize_image(src, thumb, self.kw['image_thumbnail_size'], False) + self.resize_image(src, dst, self.kw['max_image_size'], False, preserve_exif_data=self.kw['preserve_exif_data'], exif_whitelist=self.kw['exif_whitelist']) + self.resize_image(src, thumb, self.kw['image_thumbnail_size'], False, preserve_exif_data=self.kw['preserve_exif_data'], exif_whitelist=self.kw['exif_whitelist']) def gen_tasks(self): """Copy static files into the output folder.""" @@ -82,6 +82,8 @@ class ScaleImage(Task, ImageProcessor): 'image_folders': self.site.config['IMAGE_FOLDERS'], 'output_folder': self.site.config['OUTPUT_FOLDER'], 'filters': self.site.config['FILTERS'], + 'preserve_exif_data': self.site.config['PRESERVE_EXIF_DATA'], + 'exif_whitelist': self.site.config['EXIF_WHITELIST'], } self.image_ext_list = self.image_ext_list_builtin diff --git a/nikola/plugins/task/sitemap/__init__.py b/nikola/plugins/task/sitemap/__init__.py index 90acdd3..64fcb45 100644 --- a/nikola/plugins/task/sitemap/__init__.py +++ b/nikola/plugins/task/sitemap/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -158,7 +158,7 @@ class Sitemap(LateTask): continue alternates = [] if post: - for lang in kw['translations']: + for lang in post.translated_to: alt_url = post.permalink(lang=lang, absolute=True) if encodelink(loc) == alt_url: continue @@ -215,7 +215,7 @@ class Sitemap(LateTask): loc = urljoin(base_url, base_path + path) alternates = [] if post: - for lang in kw['translations']: + for lang in post.translated_to: alt_url = post.permalink(lang=lang, absolute=True) if encodelink(loc) == alt_url: continue diff --git a/nikola/plugins/task/sources.py b/nikola/plugins/task/sources.py index f782ad4..0d77aba 100644 --- a/nikola/plugins/task/sources.py +++ b/nikola/plugins/task/sources.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/task/tags.py b/nikola/plugins/task/tags.py index 6d9d495..8b4683e 100644 --- a/nikola/plugins/task/tags.py +++ b/nikola/plugins/task/tags.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -29,7 +29,6 @@ from __future__ import unicode_literals import json import os -import sys import natsort try: from urlparse import urljoin @@ -38,6 +37,7 @@ except ImportError: from nikola.plugin_categories import Task from nikola import utils +from nikola.nikola import _enclosure class RenderTags(Task): @@ -97,31 +97,31 @@ class RenderTags(Task): if not self.site.posts_per_tag and not self.site.posts_per_category: return - if kw['category_path'] == kw['tag_path']: - tags = {self.slugify_tag_name(tag): tag for tag in self.site.posts_per_tag.keys()} - cats = {tuple(self.slugify_category_name(category)): category for category in self.site.posts_per_category.keys()} - categories = {k[0]: v for k, v in cats.items() if len(k) == 1} - intersect = set(tags.keys()) & set(categories.keys()) - if len(intersect) > 0: - for slug in intersect: - utils.LOGGER.error("Category '{0}' and tag '{1}' both have the same slug '{2}'!".format('/'.join(categories[slug]), tags[slug], slug)) - sys.exit(1) - - # Test for category slug clashes - categories = {} - for category in self.site.posts_per_category.keys(): - slug = tuple(self.slugify_category_name(category)) - for part in slug: - if len(part) == 0: - utils.LOGGER.error("Category '{0}' yields invalid slug '{1}'!".format(category, '/'.join(slug))) - sys.exit(1) - if slug in categories: - other_category = categories[slug] - utils.LOGGER.error('You have categories that are too similar: {0} and {1}'.format(category, other_category)) - utils.LOGGER.error('Category {0} is used in: {1}'.format(category, ', '.join([p.source_path for p in self.site.posts_per_category[category]]))) - utils.LOGGER.error('Category {0} is used in: {1}'.format(other_category, ', '.join([p.source_path for p in self.site.posts_per_category[other_category]]))) - sys.exit(1) - categories[slug] = category + for lang in kw["translations"]: + if kw['category_path'][lang] == kw['tag_path'][lang]: + tags = {self.slugify_tag_name(tag, lang): tag for tag in self.site.tags_per_language[lang]} + cats = {tuple(self.slugify_category_name(category, lang)): category for category in self.site.posts_per_category.keys()} + categories = {k[0]: v for k, v in cats.items() if len(k) == 1} + intersect = set(tags.keys()) & set(categories.keys()) + if len(intersect) > 0: + for slug in intersect: + utils.LOGGER.error("Category '{0}' and tag '{1}' both have the same slug '{2}' for language {3}!".format('/'.join(categories[slug]), tags[slug], slug, lang)) + + # Test for category slug clashes + categories = {} + for category in self.site.posts_per_category.keys(): + slug = tuple(self.slugify_category_name(category, lang)) + for part in slug: + if len(part) == 0: + utils.LOGGER.error("Category '{0}' yields invalid slug '{1}'!".format(category, '/'.join(slug))) + raise RuntimeError("Category '{0}' yields invalid slug '{1}'!".format(category, '/'.join(slug))) + if slug in categories: + other_category = categories[slug] + utils.LOGGER.error('You have categories that are too similar: {0} and {1} (language {2})'.format(category, other_category, lang)) + utils.LOGGER.error('Category {0} is used in: {1}'.format(category, ', '.join([p.source_path for p in self.site.posts_per_category[category]]))) + utils.LOGGER.error('Category {0} is used in: {1}'.format(other_category, ', '.join([p.source_path for p in self.site.posts_per_category[other_category]]))) + raise RuntimeError("Category '{0}' yields invalid slug '{1}'!".format(category, '/'.join(slug))) + categories[slug] = category tag_list = list(self.site.posts_per_tag.items()) cat_list = list(self.site.posts_per_category.items()) @@ -185,7 +185,7 @@ class RenderTags(Task): task['clean'] = True yield utils.apply_filters(task, kw['filters']) - def _create_tags_page(self, kw, include_tags=True, include_categories=True): + def _create_tags_page(self, kw, lang, include_tags=True, include_categories=True): """Create a global "all your tags/categories" page for each language.""" categories = [cat.category_name for cat in self.site.category_hierarchy] has_categories = (categories != []) and include_categories @@ -193,59 +193,59 @@ class RenderTags(Task): kw = kw.copy() if include_categories: kw['categories'] = categories - for lang in kw["translations"]: - tags = natsort.natsorted([tag for tag in self.site.tags_per_language[lang] - if len(self.site.posts_per_tag[tag]) >= kw["taglist_minimum_post_count"]], - alg=natsort.ns.F | natsort.ns.IC) - has_tags = (tags != []) and include_tags - if include_tags: - kw['tags'] = tags - output_name = os.path.join( - kw['output_folder'], self.site.path('tag_index' if has_tags else 'category_index', None, lang)) - context = {} - if has_categories and has_tags: - context["title"] = kw["messages"][lang]["Tags and Categories"] - elif has_categories: - context["title"] = kw["messages"][lang]["Categories"] - else: - context["title"] = kw["messages"][lang]["Tags"] - if has_tags: - context["items"] = [(tag, self.site.link("tag", tag, lang)) for tag - in tags] - else: - context["items"] = None - if has_categories: - 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), - node.indent_levels, node.indent_change_before, - node.indent_change_after) - for node in self.site.category_hierarchy] - else: - context["cat_items"] = None - context["permalink"] = self.site.link("tag_index" if has_tags else "category_index", None, lang) - context["description"] = context["title"] - context["pagekind"] = ["list", "tags_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.tags:page')] - task['basename'] = str(self.name) - yield task + tags = natsort.natsorted([tag for tag in self.site.tags_per_language[lang] + if len(self.site.posts_per_tag[tag]) >= kw["taglist_minimum_post_count"]], + alg=natsort.ns.F | natsort.ns.IC) + has_tags = (tags != []) and include_tags + if include_tags: + kw['tags'] = tags + output_name = os.path.join( + kw['output_folder'], self.site.path('tag_index' if has_tags else 'category_index', None, lang)) + context = {} + if has_categories and has_tags: + context["title"] = kw["messages"][lang]["Tags and Categories"] + elif has_categories: + context["title"] = kw["messages"][lang]["Categories"] + else: + context["title"] = kw["messages"][lang]["Tags"] + if has_tags: + context["items"] = [(tag, self.site.link("tag", tag, lang)) for tag + in tags] + else: + context["items"] = None + if has_categories: + 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), + node.indent_levels, node.indent_change_before, + node.indent_change_after) + for node in self.site.category_hierarchy] + else: + context["cat_items"] = None + context["permalink"] = self.site.link("tag_index" if has_tags else "category_index", None, lang) + context["description"] = context["title"] + context["pagekind"] = ["list", "tags_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.tags:page')] + task['basename'] = str(self.name) + yield task def list_tags_page(self, kw): """Create a global "all your tags/categories" page for each language.""" - if self.site.config['TAG_PATH'] == self.site.config['CATEGORY_PATH']: - yield self._create_tags_page(kw, True, True) - else: - yield self._create_tags_page(kw, False, True) - yield self._create_tags_page(kw, True, False) + for lang in kw["translations"]: + if self.site.config['TAG_PATH'][lang] == self.site.config['CATEGORY_PATH'][lang]: + yield self._create_tags_page(kw, lang, True, True) + else: + yield self._create_tags_page(kw, lang, False, True) + yield self._create_tags_page(kw, lang, True, False) def _get_title(self, tag, is_category): if is_category: @@ -253,9 +253,9 @@ class RenderTags(Task): else: return tag - def _get_indexes_title(self, tag, is_category, lang, messages): + def _get_indexes_title(self, tag, nice_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 + return titles[lang][tag] if lang in titles and tag in titles[lang] else messages[lang]["Posts about %s"] % nice_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'] @@ -290,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 = self._get_indexes_title(title, is_category, lang, kw["messages"]) + indexes_title = self._get_indexes_title(tag, 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) @@ -312,7 +312,7 @@ class RenderTags(Task): context["category"] = tag context["category_path"] = self.site.parse_category_name(tag) context["tag"] = title - context["title"] = self._get_indexes_title(title, is_category, lang, kw["messages"]) + context["title"] = self._get_indexes_title(tag, title, is_category, lang, kw["messages"]) context["posts"] = post_list context["permalink"] = self.site.link(kind, tag, lang) context["kind"] = kind @@ -379,17 +379,20 @@ class RenderTags(Task): (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, None, kw["feed_link_append_query"]))], + feed_url, _enclosure, kw["feed_link_append_query"]))], 'clean': True, 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.tags:rss')] + deps_uptodate, 'task_dep': ['render_posts'], } return utils.apply_filters(task, kw['filters']) - def slugify_tag_name(self, name): + def slugify_tag_name(self, name, lang): """Slugify a tag name.""" + if lang is None: # TODO: remove in v8 + utils.LOGGER.warn("RenderTags.slugify_tag_name() called without language!") + lang = '' if self.site.config['SLUG_TAG_PATH']: - name = utils.slugify(name) + name = utils.slugify(name, lang) return name def tag_index_path(self, name, lang): @@ -430,13 +433,13 @@ class RenderTags(Task): return [_f for _f in [ self.site.config['TRANSLATIONS'][lang], self.site.config['TAG_PATH'][lang], - self.slugify_tag_name(name), + self.slugify_tag_name(name, lang), self.site.config['INDEX_FILE']] if _f] else: return [_f for _f in [ self.site.config['TRANSLATIONS'][lang], self.site.config['TAG_PATH'][lang], - self.slugify_tag_name(name) + ".html"] if _f] + self.slugify_tag_name(name, lang) + ".html"] if _f] def tag_atom_path(self, name, lang): """A link to a tag's Atom feed. @@ -446,7 +449,7 @@ class RenderTags(Task): link://tag_atom/cats => /tags/cats.atom """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name) + ".atom"] if + self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name, lang) + ".atom"] if _f] def tag_rss_path(self, name, lang): @@ -457,15 +460,18 @@ class RenderTags(Task): link://tag_rss/cats => /tags/cats.xml """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name) + ".xml"] if + self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name, lang) + ".xml"] if _f] - def slugify_category_name(self, name): + def slugify_category_name(self, name, lang): """Slugify a category name.""" + if lang is None: # TODO: remove in v8 + utils.LOGGER.warn("RenderTags.slugify_category_name() called without language!") + lang = '' path = self.site.parse_category_name(name) if self.site.config['CATEGORY_OUTPUT_FLAT_HIERARCHY']: path = path[-1:] # only the leaf - result = [self.slugify_tag_name(part) for part in path] + result = [self.slugify_tag_name(part, lang) for part in path] result[0] = self.site.config['CATEGORY_PREFIX'] + result[0] if not self.site.config['PRETTY_URLS']: result = ['-'.join(result)] @@ -485,11 +491,11 @@ class RenderTags(Task): if self.site.config['PRETTY_URLS']: return [_f for _f in [self.site.config['TRANSLATIONS'][lang], self.site.config['CATEGORY_PATH'][lang]] if - _f] + self.slugify_category_name(name) + [self.site.config['INDEX_FILE']] + _f] + self.slugify_category_name(name, lang) + [self.site.config['INDEX_FILE']] else: return [_f for _f in [self.site.config['TRANSLATIONS'][lang], self.site.config['CATEGORY_PATH'][lang]] if - _f] + self._add_extension(self.slugify_category_name(name), ".html") + _f] + self._add_extension(self.slugify_category_name(name, lang), ".html") def category_atom_path(self, name, lang): """A link to a category's Atom feed. @@ -500,7 +506,7 @@ class RenderTags(Task): """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], self.site.config['CATEGORY_PATH'][lang]] if - _f] + self._add_extension(self.slugify_category_name(name), ".atom") + _f] + self._add_extension(self.slugify_category_name(name, lang), ".atom") def category_rss_path(self, name, lang): """A link to a category's RSS feed. @@ -511,4 +517,4 @@ class RenderTags(Task): """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], self.site.config['CATEGORY_PATH'][lang]] if - _f] + self._add_extension(self.slugify_category_name(name), ".xml") + _f] + self._add_extension(self.slugify_category_name(name, lang), ".xml") diff --git a/nikola/plugins/template/__init__.py b/nikola/plugins/template/__init__.py index d416ad7..d5efd61 100644 --- a/nikola/plugins/template/__init__.py +++ b/nikola/plugins/template/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 diff --git a/nikola/plugins/template/jinja.py b/nikola/plugins/template/jinja.py index e7df102..5a2135f 100644 --- a/nikola/plugins/template/jinja.py +++ b/nikola/plugins/template/jinja.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -29,8 +29,8 @@ from __future__ import unicode_literals import os +import io import json -from collections import deque try: import jinja2 from jinja2 import meta @@ -47,23 +47,27 @@ class JinjaTemplates(TemplateSystem): name = "jinja" lookup = None dependency_cache = {} + per_file_cache = {} def __init__(self): """Initialize Jinja2 environment with extended set of filters.""" if jinja2 is None: return - self.lookup = jinja2.Environment() + + def set_directories(self, directories, cache_folder): + """Create a new template lookup with set directories.""" + if jinja2 is None: + req_missing(['jinja2'], 'use this theme') + cache_folder = os.path.join(cache_folder, 'jinja') + makedirs(cache_folder) + cache = jinja2.FileSystemBytecodeCache(cache_folder) + self.lookup = jinja2.Environment(bytecode_cache=cache) self.lookup.trim_blocks = True self.lookup.lstrip_blocks = True self.lookup.filters['tojson'] = json.dumps self.lookup.globals['enumerate'] = enumerate self.lookup.globals['isinstance'] = isinstance self.lookup.globals['tuple'] = tuple - - def set_directories(self, directories, cache_folder): - """Create a new template lookup with set directories.""" - if jinja2 is None: - req_missing(['jinja2'], 'use this theme') self.directories = directories self.create_lookup() @@ -88,36 +92,46 @@ class JinjaTemplates(TemplateSystem): if jinja2 is None: req_missing(['jinja2'], 'use this theme') template = self.lookup.get_template(template_name) - output = template.render(**context) + data = template.render(**context) if output_name is not None: makedirs(os.path.dirname(output_name)) - with open(output_name, 'w+') as output: - output.write(output.encode('utf8')) - return output + with io.open(output_name, 'w', encoding='utf-8') as output: + output.write(data) + return data def render_template_to_string(self, template, context): """Render template to a string using context.""" return self.lookup.from_string(template).render(**context) + def get_string_deps(self, text): + """Find dependencies for a template string.""" + deps = set([]) + ast = self.lookup.parse(text) + dep_names = meta.find_referenced_templates(ast) + for dep_name in dep_names: + filename = self.lookup.loader.get_source(self.lookup, dep_name)[1] + sub_deps = [filename] + self.get_deps(filename) + self.dependency_cache[dep_name] = sub_deps + deps |= set(sub_deps) + return list(deps) + + def get_deps(self, filename): + """Return paths to dependencies for the template loaded from filename.""" + with io.open(filename, 'r', encoding='utf-8') as fd: + text = fd.read() + return self.get_string_deps(text) + def template_deps(self, template_name): """Generate list of dependencies for a template.""" - # Cache the lists of dependencies for each template name. if self.dependency_cache.get(template_name) is None: - # Use a breadth-first search to find all templates this one - # depends on. - queue = deque([template_name]) - visited_templates = set([template_name]) - deps = [] - while len(queue) > 0: - curr = queue.popleft() - source, filename = self.lookup.loader.get_source(self.lookup, - curr)[:2] - deps.append(filename) - ast = self.lookup.parse(source) - dep_names = meta.find_referenced_templates(ast) - for dep_name in dep_names: - if (dep_name not in visited_templates and dep_name is not None): - visited_templates.add(dep_name) - queue.append(dep_name) - self.dependency_cache[template_name] = deps + filename = self.lookup.loader.get_source(self.lookup, template_name)[1] + self.dependency_cache[template_name] = [filename] + self.get_deps(filename) return self.dependency_cache[template_name] + + def get_template_path(self, template_name): + """Get the path to a template or return None.""" + try: + t = self.lookup.get_template(template_name) + return t.filename + except jinja2.TemplateNotFound: + return None diff --git a/nikola/plugins/template/mako.py b/nikola/plugins/template/mako.py index 6da21db..0c9bb64 100644 --- a/nikola/plugins/template/mako.py +++ b/nikola/plugins/template/mako.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -27,12 +27,13 @@ """Mako template handler.""" from __future__ import unicode_literals, print_function, absolute_import +import io import os import shutil import sys import tempfile -from mako import util, lexer, parsetree +from mako import exceptions, util, lexer, parsetree from mako.lookup import TemplateLookup from mako.template import Template from markupsafe import Markup # It's ok, Mako requires it @@ -54,9 +55,8 @@ class MakoTemplates(TemplateSystem): directories = [] cache_dir = None - def get_deps(self, filename): - """Get dependencies for a template (internal function).""" - text = util.read_file(filename) + def get_string_deps(self, text, filename=None): + """Find dependencies for a template string.""" lex = lexer.Lexer(text=text, filename=filename) lex.parse() @@ -65,8 +65,17 @@ class MakoTemplates(TemplateSystem): keyword = getattr(n, 'keyword', None) if keyword in ["inherit", "namespace"] or isinstance(n, parsetree.IncludeTag): deps.append(n.attributes['file']) + # Some templates will include "foo.tmpl" and we need paths, so normalize them + # using the template lookup + for i, d in enumerate(deps): + deps[i] = self.get_template_path(d) return deps + def get_deps(self, filename): + """Get paths to dependencies for a template.""" + text = util.read_file(filename) + return self.get_string_deps(text, filename) + def set_directories(self, directories, cache_folder): """Create a new template lookup with set directories.""" cache_dir = os.path.join(cache_folder, '.mako.tmp') @@ -108,14 +117,14 @@ class MakoTemplates(TemplateSystem): data = template.render_unicode(**context) if output_name is not None: makedirs(os.path.dirname(output_name)) - with open(output_name, 'w+') as output: + with io.open(output_name, 'w', encoding='utf-8') as output: output.write(data) return data def render_template_to_string(self, template, context): """Render template to a string using context.""" context.update(self.filters) - return Template(template).render(**context) + return Template(template, lookup=self.lookup).render(**context) def template_deps(self, template_name): """Generate list of dependencies for a template.""" @@ -126,10 +135,18 @@ class MakoTemplates(TemplateSystem): dep_filenames = self.get_deps(template.filename) deps = [template.filename] for fname in dep_filenames: - deps += self.template_deps(fname) - self.cache[template_name] = tuple(deps) + deps += [fname] + self.get_deps(fname) + self.cache[template_name] = deps return list(self.cache[template_name]) + def get_template_path(self, template_name): + """Get the path to a template or return None.""" + try: + t = self.lookup.get_template(template_name) + return t.filename + except exceptions.TopLevelLookupException: + return None + def striphtml(text): """Strip HTML tags from text.""" diff --git a/nikola/post.py b/nikola/post.py index f8039e0..37e4241 100644 --- a/nikola/post.py +++ b/nikola/post.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -43,6 +43,7 @@ except ImportError: from . import utils +from blinker import signal import dateutil.tz import lxml.html import natsort @@ -51,7 +52,7 @@ try: except ImportError: pyphen = None -from math import ceil +from math import ceil # for reading time feature # for tearDown with _reload we cannot use 'from import' to get forLocaleBorg import nikola.utils @@ -109,7 +110,6 @@ class Post(object): self.base_url = self.config['BASE_URL'] self.is_draft = False self.is_private = False - self.is_mathjax = False self.strip_indexes = self.config['STRIP_INDEXES'] self.index_file = self.config['INDEX_FILE'] self.pretty_urls = self.config['PRETTY_URLS'] @@ -136,6 +136,7 @@ class Post(object): self._dependency_file_page = defaultdict(list) self._dependency_uptodate_fragment = defaultdict(list) self._dependency_uptodate_page = defaultdict(list) + self._depfile = defaultdict(list) default_metadata, self.newstylemeta = get_meta(self, self.config['FILE_METADATA_REGEXP'], self.config['UNSLUGIFY_TITLES']) @@ -160,8 +161,14 @@ class Post(object): for lang in sorted(self.translated_to): default_metadata.update(self.meta[lang]) + # Load data field from metadata + self.data = Functionary(lambda: None, self.default_lang) + for lang in self.translations: + if self.meta[lang].get('data') is not None: + self.data[lang] = utils.load_data(self.meta[lang]['data']) + if 'date' not in default_metadata and not use_in_feeds: - # For stories we don't *really* need a date + # For pages we don't *really* need a date if self.config['__invariant__']: default_metadata['date'] = datetime.datetime(2013, 12, 31, 23, 59, 59, tzinfo=tzinfo) else: @@ -169,7 +176,10 @@ class Post(object): os.stat(self.source_path).st_ctime).replace(tzinfo=dateutil.tz.tzutc()).astimezone(tzinfo) # If time zone is set, build localized datetime. - self.date = to_datetime(self.meta[self.default_lang]['date'], tzinfo) + try: + self.date = to_datetime(self.meta[self.default_lang]['date'], tzinfo) + except ValueError: + raise ValueError("Invalid date '{0}' in file {1}".format(self.meta[self.default_lang]['date'], source_path)) if 'updated' not in default_metadata: default_metadata['updated'] = default_metadata.get('date', None) @@ -178,12 +188,11 @@ class Post(object): if 'title' not in default_metadata or 'slug' not in default_metadata \ or 'date' not in default_metadata: - raise OSError("You must set a title (found '{0}'), a slug (found " - "'{1}') and a date (found '{2}')! [in file " - "{3}]".format(default_metadata.get('title', None), - default_metadata.get('slug', None), - default_metadata.get('date', None), - source_path)) + raise ValueError("You must set a title (found '{0}'), a slug (found '{1}') and a date (found '{2}')! " + "[in file {3}]".format(default_metadata.get('title', None), + default_metadata.get('slug', None), + default_metadata.get('date', None), + source_path)) if 'type' not in default_metadata: # default value is 'text' @@ -223,9 +232,6 @@ class Post(object): self.use_in_feeds = use_in_feeds and not is_draft and not is_private \ and not self.publish_later - # If mathjax is a tag, or it's a ipynb post, then enable mathjax rendering support - self.is_mathjax = ('mathjax' in self.tags) or (self.compiler.name == 'ipynb') - # Register potential extra dependencies self.compiler.register_extra_dependencies(self) @@ -259,6 +265,17 @@ class Post(object): return False @property + def is_mathjax(self): + """True if this post has the mathjax tag in the current language or is a python notebook.""" + if self.compiler.name == 'ipynb': + return True + lang = nikola.utils.LocaleBorg().current_lang + if self.is_translation_available(lang): + return 'mathjax' in self.tags_for_language(lang) + # If it has math in ANY other language, enable it. Better inefficient than broken. + return 'mathjax' in self.alltags + + @property def alltags(self): """Return ALL the tags for this post.""" tags = [] @@ -416,6 +433,24 @@ class Post(object): if add == 'page' or add == 'both': self._dependency_uptodate_page[lang].append((is_callable, dependency)) + def register_depfile(self, dep, dest=None, lang=None): + """Register a dependency in the dependency file.""" + if not dest: + dest = self.translated_base_path(lang) + self._depfile[dest].append(dep) + + @staticmethod + def write_depfile(dest, deps_list): + """Write a depfile for a given language.""" + deps_path = dest + '.dep' + if deps_list: + deps_list = [p for p in deps_list if p != dest] # Don't depend on yourself (#1671) + with io.open(deps_path, "w+", encoding="utf8") as deps_file: + deps_file.write('\n'.join(deps_list)) + else: + if os.path.isfile(deps_path): + os.unlink(deps_path) + def _get_dependencies(self, deps_list): deps = [] for dep in deps_list: @@ -448,6 +483,8 @@ class Post(object): cand_3 = get_translation_candidate(self.config, self.metadata_path, lang) if os.path.exists(cand_3): deps.append(cand_3) + if self.meta('data', lang): + deps.append(self.meta('data', lang)) deps += self._get_dependencies(self._dependency_file_page[lang]) deps += self._get_dependencies(self._dependency_file_page[None]) return sorted(deps) @@ -482,7 +519,15 @@ class Post(object): self.compile_html( self.translated_source_path(lang), dest, - self.is_two_file), + self.is_two_file) + Post.write_depfile(dest, self._depfile[dest]) + + signal('compiled').send({ + 'source': self.translated_source_path(lang), + 'dest': dest, + 'post': self, + }) + if self.meta('password'): # TODO: get rid of this feature one day (v8?; warning added in v7.3.0.) LOGGER.warn("The post {0} is using the `password` attribute, which may stop working in the future.") @@ -788,7 +833,7 @@ class Post(object): slug = slug[0] else: slug = self.meta[lang]['section'].split(',')[0] if 'section' in self.meta[lang] else self.messages[lang]["Uncategorized"] - return utils.slugify(slug) + return utils.slugify(slug, lang) def permalink(self, lang=None, absolute=False, extension='.html', query=None): """Return permalink for a post.""" @@ -864,7 +909,7 @@ def re_meta(line, match=None): return (None,) -def _get_metadata_from_filename_by_regex(filename, metadata_regexp, unslugify_titles): +def _get_metadata_from_filename_by_regex(filename, metadata_regexp, unslugify_titles, lang): """Try to reed the metadata from the filename based on the given re. This requires to use symbolic group names in the pattern. @@ -879,7 +924,7 @@ def _get_metadata_from_filename_by_regex(filename, metadata_regexp, unslugify_ti for key, value in match.groupdict().items(): k = key.lower().strip() # metadata must be lowercase if k == 'title' and unslugify_titles: - meta[k] = unslugify(value, discard_numbers=False) + meta[k] = unslugify(value, lang, discard_numbers=False) else: meta[k] = value @@ -1043,7 +1088,8 @@ def get_meta(post, file_metadata_regexp=None, unslugify_titles=False, lang=None) if file_metadata_regexp is not None: meta.update(_get_metadata_from_filename_by_regex(post.source_path, file_metadata_regexp, - unslugify_titles)) + unslugify_titles, + post.default_lang)) compiler_meta = {} @@ -1062,7 +1108,7 @@ def get_meta(post, file_metadata_regexp=None, unslugify_titles=False, lang=None) if 'slug' not in meta: # If no slug is found in the metadata use the filename meta['slug'] = slugify(unicode_str(os.path.splitext( - os.path.basename(post.source_path))[0])) + os.path.basename(post.source_path))[0]), post.default_lang) if 'title' not in meta: # If no title is found, use the filename without extension @@ -1081,6 +1127,7 @@ def hyphenate(dom, _lang): lang = LEGAL_VALUES['PYPHEN_LOCALES'].get(_lang, pyphen.language_fallback(_lang)) else: utils.req_missing(['pyphen'], 'hyphenate texts', optional=True) + hyphenator = None if pyphen is not None and lang is not None: # If pyphen does exist, we tell the user when configuring the site. # If it does not support a language, we ignore it quietly. @@ -1089,6 +1136,7 @@ def hyphenate(dom, _lang): except KeyError: LOGGER.error("Cannot find hyphenation dictoniaries for {0} (from {1}).".format(lang, _lang)) LOGGER.error("Pyphen cannot be installed to ~/.local (pip install --user).") + if hyphenator is not None: for tag in ('p', 'li', 'span'): for node in dom.xpath("//%s[not(parent::pre)]" % tag): skip_node = False diff --git a/nikola/shortcodes.py b/nikola/shortcodes.py new file mode 100644 index 0000000..b11ddac --- /dev/null +++ b/nikola/shortcodes.py @@ -0,0 +1,339 @@ +# -*- 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. + +"""Support for Hugo-style shortcodes.""" + +from __future__ import unicode_literals +from .utils import LOGGER +import sys + + +# Constants +_TEXT = 1 +_SHORTCODE_START = 2 +_SHORTCODE_END = 3 + + +class ParsingError(Exception): + """Used for forwarding parsing error messages to apply_shortcodes.""" + + pass + + +def _format_position(data, pos): + """Return position formatted as line/column. + + This is used for prettier error messages. + """ + line = 0 + col = 0 + llb = '' # last line break + for c in data[:pos]: + if c == '\r' or c == '\n': + if llb and c != llb: + llb = '' + else: + line += 1 + col = 0 + llb = c + else: + col += 1 + llb = '' + return "line {0}, column {1}".format(line + 1, col + 1) + + +def _skip_whitespace(data, pos, must_be_nontrivial=False): + """Return first position after whitespace. + + If must_be_nontrivial is set to True, raises ParsingError + if no whitespace is found. + """ + if must_be_nontrivial: + if pos == len(data) or not data[pos].isspace(): + raise ParsingError("Expecting whitespace at {0}!".format(_format_position(data, pos))) + while pos < len(data): + if not data[pos].isspace(): + break + pos += 1 + return pos + + +def _skip_nonwhitespace(data, pos): + """Return first position not before pos which contains a non-whitespace character.""" + while pos < len(data): + if data[pos].isspace(): + break + pos += 1 + return pos + + +def _parse_quoted_string(data, start): + """Parse a quoted string starting at position start in data. + + Returns the position after the string followed by the string itself. + """ + value = '' + qc = data[start] + pos = start + 1 + while pos < len(data): + char = data[pos] + if char == '\\': + if pos + 1 < len(data): + value += data[pos + 1] + pos += 2 + else: + raise ParsingError("Unexpected end of data while escaping ({0})".format(_format_position(data, pos))) + elif (char == "'" or char == '"') and char == qc: + return pos + 1, value + else: + value += char + pos += 1 + raise ParsingError("Unexpected end of unquoted string (started at {0})!".format(_format_position(data, start))) + + +def _parse_unquoted_string(data, start, stop_at_equals): + """Parse an unquoted string starting at position start in data. + + Returns the position after the string followed by the string itself. + In case stop_at_equals is set to True, an equal sign will terminate + the string. + """ + value = '' + pos = start + while pos < len(data): + char = data[pos] + if char == '\\': + if pos + 1 < len(data): + value += data[pos + 1] + pos += 2 + else: + raise ParsingError("Unexpected end of data while escaping ({0})".format(_format_position(data, pos))) + elif char.isspace(): + break + elif char == '=' and stop_at_equals: + break + elif char == "'" or char == '"': + raise ParsingError("Unexpected quotation mark in unquoted string ({0})".format(_format_position(data, pos))) + else: + value += char + pos += 1 + return pos, value + + +def _parse_string(data, start, stop_at_equals=False, must_have_content=False): + """Parse a string starting at position start in data. + + Returns the position after the string, followed by the string itself, and + followed by a flog indicating whether the following character is an equals + sign (only set if stop_at_equals is True). + + If must_have_content is set to True, no empty unquoted strings are accepted. + """ + if start == len(data): + raise ParsingError("Expecting string, but found end of input!") + char = data[start] + if char == '"' or char == "'": + end, value = _parse_quoted_string(data, start) + has_content = True + else: + end, value = _parse_unquoted_string(data, start, stop_at_equals) + has_content = len(value) > 0 + if must_have_content and not has_content: + raise ParsingError("String starting at {0} must be non-empty!".format(_format_position(data, start))) + + next_is_equals = False + if stop_at_equals and end + 1 < len(data): + next_is_equals = (data[end] == '=') + return end, value, next_is_equals + + +def _parse_shortcode_args(data, start, shortcode_name, start_pos): + """When pointed to after a shortcode's name in a shortcode tag, parses the shortcode's arguments until '%}}'. + + Returns the position after '%}}', followed by a tuple (args, kw). + + name and start_pos are only used for formatting error messages. + """ + args = [] + kwargs = {} + + pos = start + while True: + # Skip whitespaces + try: + pos = _skip_whitespace(data, pos, must_be_nontrivial=True) + except ParsingError: + if not args and not kwargs: + raise ParsingError("Shortcode '{0}' starting at {1} is not terminated correctly with '%}}}}'!".format(shortcode_name, _format_position(data, start_pos))) + else: + raise ParsingError("Syntax error in shortcode '{0}' at {1}: expecting whitespace!".format(shortcode_name, _format_position(data, pos))) + if pos == len(data): + break + # Check for end of shortcode + if pos + 3 <= len(data) and data[pos:pos + 3] == '%}}': + return pos + 3, (args, kwargs) + # Read name + pos, name, next_is_equals = _parse_string(data, pos, stop_at_equals=True, must_have_content=True) + if next_is_equals: + # Read value + pos, value, _ = _parse_string(data, pos + 1, stop_at_equals=False, must_have_content=False) + # Store keyword argument + kwargs[name] = value + else: + # Store positional argument + args.append(name) + + raise ParsingError("Shortcode '{0}' starting at {1} is not terminated correctly with '%}}}}'!".format(shortcode_name, _format_position(data, start_pos))) + + +def _split_shortcodes(data): + """Given input data, splits it into a sequence of texts, shortcode starts and shortcode ends. + + Returns a list of tuples of the following forms: + + 1. (_TEXT, text) + 2. (_SHORTCODE_START, text, start, name, args) + 3. (_SHORTCODE_END, text, start, name) + + Here, text is the raw text represented by the token; start is the starting position in data + of the token; name is the name of the shortcode; and args is a tuple (args, kw) as returned + by _parse_shortcode_args. + """ + pos = 0 + result = [] + while pos < len(data): + # Search for shortcode start + start = data.find('{{%', pos) + if start < 0: + result.append((_TEXT, data[pos:])) + break + result.append((_TEXT, data[pos:start])) + # Extract name + name_start = _skip_whitespace(data, start + 3) + name_end = _skip_nonwhitespace(data, name_start) + name = data[name_start:name_end] + if not name: + raise ParsingError("Syntax error: '{{{{%' must be followed by shortcode name ({0})!".format(_format_position(data, start))) + # Finish shortcode + if name[0] == '/': + # This is a closing shortcode + name = name[1:] + end_start = _skip_whitespace(data, name_end) # start of '%}}' + pos = end_start + 3 + # Must be followed by '%}}' + if pos > len(data) or data[end_start:pos] != '%}}': + raise ParsingError("Syntax error: '{{{{% /{0}' must be followed by ' %}}}}' ({1})!".format(name, _format_position(data, end_start))) + result.append((_SHORTCODE_END, data[start:pos], start, name)) + elif name == '%}}': + raise ParsingError("Syntax error: '{{{{%' must be followed by shortcode name ({0})!".format(_format_position(data, start))) + else: + # This is an opening shortcode + pos, args = _parse_shortcode_args(data, name_end, shortcode_name=name, start_pos=start) + result.append((_SHORTCODE_START, data[start:pos], start, name, args)) + return result + + +# FIXME: in v8, get rid of with_dependencies +def apply_shortcodes(data, registry, site=None, filename=None, raise_exceptions=False, lang=None, with_dependencies=False, extra_context={}): + """Apply Hugo-style shortcodes on data. + + {{% name parameters %}} will end up calling the registered "name" function with the given parameters. + {{% name parameters %}} something {{% /name %}} will call name with the parameters and + one extra "data" parameter containing " something ". + + If raise_exceptions is set to True, instead of printing error messages and terminating, errors are + passed on as exceptions to the caller. + + The site parameter is passed with the same name to the shortcodes so they can access Nikola state. + + >>> print(apply_shortcodes('==> {{% foo bar=baz %}} <==', {'foo': lambda *a, **k: k['bar']})) + ==> baz <== + >>> print(apply_shortcodes('==> {{% foo bar=baz %}}some data{{% /foo %}} <==', {'foo': lambda *a, **k: k['bar']+k['data']})) + ==> bazsome data <== + """ + empty_string = data[:0] # same string type as data; to make Python 2 happy + try: + # Split input data into text, shortcodes and shortcode endings + sc_data = _split_shortcodes(data) + # Now process data + result = [] + dependencies = [] + pos = 0 + while pos < len(sc_data): + current = sc_data[pos] + if current[0] == _TEXT: + result.append(current[1]) + pos += 1 + elif current[0] == _SHORTCODE_END: + raise ParsingError("Found shortcode ending '{{{{% /{0} %}}}}' which isn't closing a started shortcode ({1})!".format(current[3], _format_position(data, current[2]))) + elif current[0] == _SHORTCODE_START: + name = current[3] + # Check if we can find corresponding ending + found = None + for p in range(pos + 1, len(sc_data)): + if sc_data[p][0] == _SHORTCODE_END and sc_data[p][3] == name: + found = p + break + if found: + # Found ending. Extract data argument: + data_arg = [] + for p in range(pos + 1, found): + data_arg.append(sc_data[p][1]) + data_arg = empty_string.join(data_arg) + pos = found + 1 + else: + # Single shortcode + pos += 1 + data_arg = '' + args, kw = current[4] + kw['site'] = site + kw['data'] = data_arg + kw['lang'] = lang + kw.update(extra_context) + if name in registry: + f = registry[name] + if getattr(f, 'nikola_shortcode_pass_filename', None): + kw['filename'] = filename + res = f(*args, **kw) + if not isinstance(res, tuple): # For backards compatibility + res = (res, []) + else: + LOGGER.error('Unknown shortcode {0} (started at {1})', name, _format_position(data, current[2])) + res = ('', []) + result.append(res[0]) + dependencies += res[1] + if with_dependencies: + return empty_string.join(result), dependencies + return empty_string.join(result) + except ParsingError as e: + if raise_exceptions: + # Throw up + raise e + if filename: + LOGGER.error("Shortcode error in file {0}: {1}".format(filename, e)) + else: + LOGGER.error("Shortcode error: {0}".format(e)) + sys.exit(1) diff --git a/nikola/state.py b/nikola/state.py new file mode 100644 index 0000000..6632e4f --- /dev/null +++ b/nikola/state.py @@ -0,0 +1,92 @@ +# -*- 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. + +"""Persistent state implementation.""" + +import json +import os +import shutil +import tempfile +import threading + +from . import utils + + +class Persistor(): + """Persist stuff in a place. + + This is an intentionally dumb implementation. It is *not* meant to be + fast, or useful for arbitrarily large data. Use lightly. + + Intentionally it has no namespaces, sections, etc. Use as a + responsible adult. + """ + + def __init__(self, path): + """Where do you want it persisted.""" + self._path = path + self._local = threading.local() + self._local.data = {} + + def _set_site(self, site): + """Set site and create path directory.""" + self._site = site + utils.makedirs(os.path.dirname(self._path)) + + def get(self, key): + """Get data stored in key.""" + self._read() + return self._local.data.get(key) + + def set(self, key, value): + """Store value in key.""" + self._read() + self._local.data[key] = value + self._save() + + def delete(self, key): + """Delete key and the value it contains.""" + self._read() + if key in self._local.data: + self._local.data.pop(key) + self._save() + + def _read(self): + if os.path.isfile(self._path): + with open(self._path) as inf: + self._local.data = json.load(inf) + + def _save(self): + dname = os.path.dirname(self._path) + with tempfile.NamedTemporaryFile(dir=dname, delete=False) as outf: + # TODO replace with encoding='utf-8' and mode 'w+' in v8 + tname = outf.name + data = json.dumps(self._local.data, sort_keys=True, indent=2) + try: + outf.write(data) + except TypeError: + outf.write(data.encode('utf-8')) + shutil.move(tname, self._path) diff --git a/nikola/utils.py b/nikola/utils.py index 3359514..068cb3a 100644 --- a/nikola/utils.py +++ b/nikola/utils.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 @@ -31,7 +31,6 @@ import calendar import datetime import dateutil.tz import hashlib -import husl import io import locale import logging @@ -56,6 +55,19 @@ except ImportError: from urllib.parse import urlparse, urlunparse # NOQA import warnings import PyRSS2Gen as rss +try: + import pytoml as toml +except ImportError: + toml = None +try: + import yaml +except ImportError: + yaml = None +try: + import husl +except ImportError: + husl = None + from collections import defaultdict, Callable, OrderedDict from logbook.compat import redirect_logging from logbook.more import ExceptionHandler, ColorizedStderrHandler @@ -69,7 +81,7 @@ from doit.cmdparse import CmdParse from nikola import DEBUG -__all__ = ('CustomEncoder', 'get_theme_path', 'get_theme_chain', 'load_messages', 'copy_tree', +__all__ = ('CustomEncoder', 'get_theme_path', 'get_theme_path_real', 'get_theme_chain', 'load_messages', 'copy_tree', 'copy_file', 'slugify', 'unslugify', 'to_datetime', 'apply_filters', 'config_changed', 'get_crumbs', 'get_tzname', 'get_asset_path', '_reload', 'unicode_str', 'bytes_str', 'unichr', 'Functionary', @@ -81,7 +93,8 @@ __all__ = ('CustomEncoder', 'get_theme_path', 'get_theme_chain', 'load_messages' 'adjust_name_for_index_path', 'adjust_name_for_index_link', 'NikolaPygmentsHTML', 'create_redirect', 'TreeNode', 'flatten_tree_structure', 'parse_escaped_hierarchical_category_name', - 'join_hierarchical_category_path', 'indent') + 'join_hierarchical_category_path', 'clean_before_deployment', 'indent', + 'load_data') # Are you looking for 'generic_rss_renderer'? # It's defined in nikola.nikola.Nikola (the site object). @@ -126,7 +139,7 @@ def get_logger(name, handlers): STDERR_HANDLER = [ColorfulStderrHandler( - level=logbook.NOTICE if not DEBUG else logbook.DEBUG, + level=logbook.INFO if not DEBUG else logbook.DEBUG, format_string=u'[{record.time:%Y-%m-%dT%H:%M:%SZ}] {record.level_name}: {record.channel}: {record.message}' )] @@ -302,7 +315,7 @@ class TranslatableSetting(object): self.overriden_default = False self.values = defaultdict() - if isinstance(inp, dict): + if isinstance(inp, dict) and inp: self.translated = True self.values.update(inp) if self.default_lang not in self.values.keys(): @@ -546,6 +559,8 @@ class config_changed(tools.config_changed): byte_data = data digest = hashlib.md5(byte_data).hexdigest() # LOGGER.debug('{{"{0}": {1}}}'.format(digest, byte_data)) + # Humanized format: + # LOGGER.debug('[Digest {0} for {2}]\n{1}\n[Digest {0} for {2}]'.format(digest, byte_data, self.identifier)) return digest else: raise Exception('Invalid type of config_changed parameter -- got ' @@ -570,24 +585,30 @@ class config_changed(tools.config_changed): sort_keys=True)) -def get_theme_path(theme, _themes_dir='themes'): +def get_theme_path_real(theme, themes_dirs): """Return the path where the given theme's files are located. Looks in ./themes and in the place where themes go when installed. """ - dir_name = os.path.join(_themes_dir, theme) - if os.path.isdir(dir_name): - return dir_name + for themes_dir in themes_dirs: + dir_name = os.path.join(themes_dir, theme) + if os.path.isdir(dir_name): + return dir_name dir_name = resource_filename('nikola', os.path.join('data', 'themes', theme)) if os.path.isdir(dir_name): return dir_name raise Exception("Can't find theme '{0}'".format(theme)) -def get_template_engine(themes, _themes_dir='themes'): +def get_theme_path(theme): + """Return the theme's path, which equals the theme's name.""" + return theme + + +def get_template_engine(themes): """Get template engine used by a given theme.""" for theme_name in themes: - engine_path = os.path.join(get_theme_path(theme_name, _themes_dir), 'engine') + engine_path = os.path.join(theme_name, 'engine') if os.path.isfile(engine_path): with open(engine_path) as fd: return fd.readlines()[0].strip() @@ -595,21 +616,24 @@ def get_template_engine(themes, _themes_dir='themes'): return 'mako' -def get_parent_theme_name(theme_name, _themes_dir='themes'): +def get_parent_theme_name(theme_name, themes_dirs=None): """Get name of parent theme.""" - parent_path = os.path.join(get_theme_path(theme_name, _themes_dir), 'parent') + parent_path = os.path.join(theme_name, 'parent') if os.path.isfile(parent_path): with open(parent_path) as fd: - return fd.readlines()[0].strip() + parent = fd.readlines()[0].strip() + if themes_dirs: + return get_theme_path_real(parent, themes_dirs) + return parent return None -def get_theme_chain(theme, _themes_dir='themes'): - """Create the full theme inheritance chain.""" - themes = [theme] +def get_theme_chain(theme, themes_dirs): + """Create the full theme inheritance chain including paths.""" + themes = [get_theme_path_real(theme, themes_dirs)] while True: - parent = get_parent_theme_name(themes[-1], _themes_dir) + parent = get_parent_theme_name(themes[-1], themes_dirs=themes_dirs) # Avoid silly loops if parent is None or parent in themes: break @@ -633,7 +657,7 @@ class LanguageNotFoundError(Exception): return 'cannot find language {0}'.format(self.lang) -def load_messages(themes, translations, default_lang): +def load_messages(themes, translations, default_lang, themes_dirs): """Load theme's messages into context. All the messages from parent themes are loaded, @@ -643,10 +667,12 @@ def load_messages(themes, translations, default_lang): oldpath = list(sys.path) for theme_name in themes[::-1]: msg_folder = os.path.join(get_theme_path(theme_name), 'messages') - default_folder = os.path.join(get_theme_path('base'), 'messages') + default_folder = os.path.join(get_theme_path_real('base', themes_dirs), 'messages') sys.path.insert(0, default_folder) sys.path.insert(0, msg_folder) english = __import__('messages_en') + # If we don't do the reload, the module is cached + _reload(english) for lang in list(translations.keys()): try: translation = __import__('messages_' + lang) @@ -665,6 +691,7 @@ def load_messages(themes, translations, default_lang): del(translation) except ImportError as orig: raise LanguageNotFoundError(lang, orig) + del(english) sys.path = oldpath return messages @@ -741,20 +768,22 @@ _slugify_strip_re = re.compile(r'[^+\w\s-]') _slugify_hyphenate_re = re.compile(r'[-\s]+') -def slugify(value, force=False): +def slugify(value, lang=None, force=False): u"""Normalize string, convert to lowercase, remove non-alpha characters, convert spaces to hyphens. From Django's "django/template/defaultfilters.py". - >>> print(slugify('áéí.óú')) + >>> print(slugify('áéí.óú', lang='en')) aeiou - >>> print(slugify('foo/bar')) + >>> print(slugify('foo/bar', lang='en')) foobar - >>> print(slugify('foo bar')) + >>> print(slugify('foo bar', lang='en')) foo-bar """ + if lang is None: # TODO: remove in v8 + LOGGER.warn("slugify() called without language!") if not isinstance(value, unicode_str): raise ValueError("Not a unicode object: {0}".format(value)) if USE_SLUGIFY or force: @@ -779,12 +808,14 @@ def slugify(value, force=False): return value -def unslugify(value, discard_numbers=True): +def unslugify(value, lang=None, discard_numbers=True): """Given a slug string (as a filename), return a human readable string. If discard_numbers is True, numbers right at the beginning of input will be removed. """ + if lang is None: # TODO: remove in v8 + LOGGER.warn("unslugify() called without language!") if discard_numbers: value = re.sub('^[0-9]+', '', value) value = re.sub('([_\-\.])', ' ', value) @@ -796,7 +827,7 @@ def encodelink(iri): """Given an encoded or unencoded link string, return an encoded string suitable for use as a link in HTML and XML.""" iri = unicodenormalize('NFC', iri) link = OrderedDict(urlparse(iri)._asdict()) - link['path'] = urlquote(urlunquote(link['path']).encode('utf-8')) + link['path'] = urlquote(urlunquote(link['path']).encode('utf-8'), safe="/~") try: link['netloc'] = link['netloc'].encode('utf-8').decode('idna').encode('idna').decode('utf-8') except UnicodeDecodeError: @@ -909,7 +940,7 @@ def apply_filters(task, filters, skip_ext=None): return task -def get_crumbs(path, is_file=False, index_folder=None): +def get_crumbs(path, is_file=False, index_folder=None, lang=None): """Create proper links for a crumb bar. index_folder is used if you want to use title from index file @@ -945,8 +976,10 @@ def get_crumbs(path, is_file=False, index_folder=None): for i, crumb in enumerate(crumbs[-3::-1]): # Up to parent folder only _path = '/'.join(['..'] * (i + 1)) _crumbs.append([_path, crumb]) - _crumbs.insert(0, ['.', crumbs[-2]]) # file's folder - _crumbs.insert(0, ['#', crumbs[-1]]) # file itself + if len(crumbs) >= 2: + _crumbs.insert(0, ['.', crumbs[-2]]) # file's folder + if len(crumbs) >= 1: + _crumbs.insert(0, ['#', crumbs[-1]]) # file itself else: for i, crumb in enumerate(crumbs[::-1]): _path = '/'.join(['..'] * i) or '#' @@ -962,12 +995,12 @@ def get_crumbs(path, is_file=False, index_folder=None): index_post = index_folder.parse_index(folder, '', '') folder = folder.replace(crumb, '') if index_post: - crumb = index_post.title() or crumb + crumb = index_post.title(lang) or crumb _crumbs[i][1] = crumb return list(reversed(_crumbs)) -def get_asset_path(path, themes, files_folders={'files': ''}, _themes_dir='themes', output_dir='output'): +def get_asset_path(path, themes, files_folders={'files': ''}, output_dir='output'): """Return the "real", absolute path to the asset. By default, it checks which theme provides the asset. @@ -976,27 +1009,24 @@ def get_asset_path(path, themes, files_folders={'files': ''}, _themes_dir='theme If it's not provided by either, it will be chacked in output, where it may have been created by another plugin. - >>> print(get_asset_path('assets/css/rst.css', ['bootstrap3', 'base'])) + >>> print(get_asset_path('assets/css/rst.css', get_theme_chain('bootstrap3', ['themes']))) /.../nikola/data/themes/base/assets/css/rst.css - >>> print(get_asset_path('assets/css/theme.css', ['bootstrap3', 'base'])) + >>> print(get_asset_path('assets/css/theme.css', get_theme_chain('bootstrap3', ['themes']))) /.../nikola/data/themes/bootstrap3/assets/css/theme.css - >>> print(get_asset_path('nikola.py', ['bootstrap3', 'base'], {'nikola': ''})) + >>> print(get_asset_path('nikola.py', get_theme_chain('bootstrap3', ['themes']), {'nikola': ''})) /.../nikola/nikola.py - >>> print(get_asset_path('nikola.py', ['bootstrap3', 'base'], {'nikola': 'nikola'})) + >>> print(get_asset_path('nikola.py', get_theme_chain('bootstrap3', ['themes']), {'nikola': 'nikola'})) None - >>> print(get_asset_path('nikola/nikola.py', ['bootstrap3', 'base'], {'nikola': 'nikola'})) + >>> print(get_asset_path('nikola/nikola.py', get_theme_chain('bootstrap3', ['themes']), {'nikola': 'nikola'})) /.../nikola/nikola.py """ for theme_name in themes: - candidate = os.path.join( - get_theme_path(theme_name, _themes_dir), - path - ) + candidate = os.path.join(get_theme_path(theme_name), path) if os.path.isfile(candidate): return candidate for src, rel_dst in files_folders.items(): @@ -1054,7 +1084,6 @@ class LocaleBorg(object): NOTE: never use locale.getlocale() , it can return values that locale.setlocale will not accept in Windows XP, 7 and pythons 2.6, 2.7, 3.3 Examples: "Spanish", "French" can't do the full circle set / get / set - That used to break calendar, but now seems is not the case, with month at least """ initialized = False @@ -1173,20 +1202,16 @@ class LocaleBorg(object): res = handler(month_no, lang) if res is not None: return res - if sys.version_info[0] == 3: # Python 3 - with calendar.different_locale(self.locales[lang]): - s = calendar.month_name[month_no] - # for py3 s is unicode - else: # Python 2 - with calendar.TimeEncoding(self.locales[lang]): - s = calendar.month_name[month_no] + old_lang = self.current_lang + self.__set_locale(lang) + s = calendar.month_name[month_no] + self.__set_locale(old_lang) + if sys.version_info[0] == 2: enc = self.encodings[lang] if not enc: enc = 'UTF-8' s = s.decode(enc) - # paranoid about calendar ending in the wrong locale (windows) - self.__set_locale(self.current_lang) return s def formatted_date(self, date_format, date): @@ -1294,9 +1319,9 @@ def demote_headers(doc, level=1): r = range(1 + level, 7) for i in reversed(r): # html headers go to 6, so we can’t “lower” beneath five - elements = doc.xpath('//h' + str(i)) - for e in elements: - e.tag = 'h' + str(i + level) + elements = doc.xpath('//h' + str(i)) + for e in elements: + e.tag = 'h' + str(i + level) def get_root_dir(): @@ -1337,10 +1362,10 @@ def get_translation_candidate(config, path, lang): cache/posts/fancy.post.html >>> print(get_translation_candidate(config, 'cache/posts/fancy.post.html', 'es')) cache/posts/fancy.post.es.html - >>> print(get_translation_candidate(config, 'cache/stories/charts.html', 'es')) - cache/stories/charts.es.html - >>> print(get_translation_candidate(config, 'cache/stories/charts.html', 'en')) - cache/stories/charts.html + >>> print(get_translation_candidate(config, 'cache/pages/charts.html', 'es')) + cache/pages/charts.es.html + >>> print(get_translation_candidate(config, 'cache/pages/charts.html', 'en')) + cache/pages/charts.html >>> config = {'TRANSLATIONS_PATTERN': '{path}.{ext}.{lang}', 'DEFAULT_LANG': 'en', 'TRANSLATIONS': {'es':'1', 'en': 1}} >>> print(get_translation_candidate(config, '*.rst', 'es')) @@ -1553,7 +1578,7 @@ class NikolaPygmentsHTML(HtmlFormatter): self.nclasses = classes super(NikolaPygmentsHTML, self).__init__( cssclass='code', linenos=linenos, linenostart=linenostart, nowrap=False, - lineanchors=slugify(anchor_ref, force=True), anchorlinenos=True) + lineanchors=slugify(anchor_ref, lang=LocaleBorg().current_lang, force=True), anchorlinenos=True) def wrap(self, source, outfile): """Wrap the ``source``, which is a generator yielding individual lines, in custom generators.""" @@ -1790,21 +1815,29 @@ def colorize_str_from_base_color(string, base_color): lightness and saturation untouched using HUSL colorspace. """ def hash_str(string, pos): - return hashlib.md5(string.encode('utf-8')).digest()[pos] + x = hashlib.md5(string.encode('utf-8')).digest()[pos] + try: + # Python 2: a string + # TODO: remove in v8 + return ord(x) + except TypeError: + # Python 3: already an integer + return x def degreediff(dega, degb): return min(abs(dega - degb), abs((degb - dega) + 360)) - def husl_similar_from_base(string, base_color): - h, s, l = husl.hex_to_husl(base_color) - old_h = h - idx = 0 - while degreediff(old_h, h) < 27 and idx < 16: - h = 360.0 * (float(hash_str(string, idx)) / 255) - idx += 1 - return husl.husl_to_hex(h, s, l) - - return husl_similar_from_base(string, base_color) + if husl is None: + req_missing(['husl'], 'Use color mixing (section colors)', + optional=True) + return base_color + h, s, l = husl.hex_to_husl(base_color) + old_h = h + idx = 0 + while degreediff(old_h, h) < 27 and idx < 16: + h = 360.0 * (float(hash_str(string, idx)) / 255) + idx += 1 + return husl.husl_to_hex(h, s, l) def color_hsl_adjust_hex(hexstr, adjust_h=None, adjust_s=None, adjust_l=None): @@ -1849,6 +1882,25 @@ def dns_sd(port, inet6): return None +def clean_before_deployment(site): + """Clean drafts and future posts before deployment.""" + undeployed_posts = [] + deploy_drafts = site.config.get('DEPLOY_DRAFTS', True) + deploy_future = site.config.get('DEPLOY_FUTURE', False) + if not (deploy_drafts and deploy_future): # == !drafts || !future + # Remove drafts and future posts + out_dir = site.config['OUTPUT_FOLDER'] + site.scan_posts() + for post in site.timeline: + if (not deploy_drafts and post.is_draft) or (not deploy_future and post.publish_later): + for lang in post.translated_to: + remove_file(os.path.join(out_dir, post.destination_path(lang))) + source_path = post.destination_path(lang, post.source_ext(True)) + remove_file(os.path.join(out_dir, source_path)) + undeployed_posts.append(post) + return undeployed_posts + + # Stolen from textwrap in Python 3.4.3. def indent(text, prefix, predicate=None): """Add 'prefix' to the beginning of selected lines in 'text'. @@ -1866,3 +1918,25 @@ def indent(text, prefix, predicate=None): for line in text.splitlines(True): yield (prefix + line if predicate(line) else line) return ''.join(prefixed_lines()) + + +def load_data(path): + """Given path to a file, load data from it.""" + ext = os.path.splitext(path)[-1] + loader = None + if ext in {'.yml', '.yaml'}: + loader = yaml + if yaml is None: + req_missing(['yaml'], 'use YAML data files') + return {} + elif ext in {'.json', '.js'}: + loader = json + elif ext in {'.toml', '.tml'}: + if toml is None: + req_missing(['toml'], 'use TOML data files') + return {} + loader = toml + if loader is None: + return + with io.open(path, 'r', encoding='utf8') as inf: + return loader.load(inf) diff --git a/nikola/winutils.py b/nikola/winutils.py index 3ea179b..6e341b8 100644 --- a/nikola/winutils.py +++ b/nikola/winutils.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# 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 |
