aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Agustin Henze <tin@sluc.org.ar>2013-05-30 17:41:32 -0300
committerLibravatar Agustin Henze <tin@sluc.org.ar>2013-05-30 17:41:32 -0300
commit47320d60c110fe6058937f996da750caf669d7ed (patch)
tree73288c7b833daa42ac200025ed41c940cc8a76f9
parent432fee57865e02af455e877a9597ef730397146c (diff)
parentf794eee787e9cde54e6b8f53e45d69c9ddc9936a (diff)
downloadnikola-47320d60c110fe6058937f996da750caf669d7ed.tar.bz2
nikola-47320d60c110fe6058937f996da750caf669d7ed.tar.xz
nikola-47320d60c110fe6058937f996da750caf669d7ed.tar.zst
Merge tag 'upstream/5.4.4'
Upstream version 5.4.4
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml4
-rw-r--r--.tx/config7
-rw-r--r--CHANGES.txt99
-rw-r--r--README.md19
-rw-r--r--docs/creating-a-theme.txt16
-rw-r--r--docs/extending.txt4
-rw-r--r--docs/internals.txt131
-rw-r--r--docs/manual.txt221
-rw-r--r--docs/theming.txt55
-rw-r--r--extra_plugins/README.txt62
-rw-r--r--extra_plugins/command_planetoid/index.tmpl26
-rw-r--r--extra_plugins/command_planetoid/post.tmpl10
-rwxr-xr-xextra_plugins/task_localsearch/files/assets/css/tipuesearch.css182
-rw-r--r--install_requirements.py35
-rw-r--r--nikola/conf.py.in43
-rw-r--r--nikola/data/themes/default/assets/css/code.css62
-rw-r--r--nikola/data/themes/default/assets/css/slides.css11
-rw-r--r--nikola/data/themes/default/assets/css/theme.css11
-rwxr-xr-xnikola/data/themes/default/assets/js/slides.jquery.js555
-rw-r--r--nikola/data/themes/default/bundles8
-rw-r--r--nikola/data/themes/default/messages/messages_ca.py23
-rw-r--r--nikola/data/themes/default/messages/messages_de.py25
-rw-r--r--nikola/data/themes/default/messages/messages_el.py23
-rw-r--r--nikola/data/themes/default/messages/messages_en.py21
-rw-r--r--nikola/data/themes/default/messages/messages_es.py23
-rw-r--r--nikola/data/themes/default/messages/messages_fr.py24
-rw-r--r--nikola/data/themes/default/messages/messages_it.py24
-rw-r--r--nikola/data/themes/default/messages/messages_ja.py23
-rw-r--r--nikola/data/themes/default/messages/messages_pl.py25
-rw-r--r--nikola/data/themes/default/messages/messages_pt_br.py (renamed from nikola/data/themes/default/messages/messages_pt-br.py)21
-rw-r--r--nikola/data/themes/default/messages/messages_ru.py23
-rw-r--r--nikola/data/themes/default/messages/messages_zh_cn.py (renamed from nikola/data/themes/default/messages/messages_zh-cn.py)45
-rw-r--r--nikola/data/themes/default/templates/base.tmpl6
-rw-r--r--nikola/data/themes/default/templates/base_helper.tmpl14
-rw-r--r--nikola/data/themes/default/templates/disqus_helper.tmpl7
-rw-r--r--nikola/data/themes/default/templates/index.tmpl10
-rw-r--r--nikola/data/themes/default/templates/index_helper.tmpl4
-rw-r--r--nikola/data/themes/default/templates/list_post.tmpl2
-rw-r--r--nikola/data/themes/default/templates/post.tmpl12
-rw-r--r--nikola/data/themes/default/templates/post_helper.tmpl26
-rw-r--r--nikola/data/themes/default/templates/story.tmpl10
-rw-r--r--nikola/data/themes/default/templates/tag.tmpl27
-rw-r--r--nikola/data/themes/default/templates/tags.tmpl18
-rw-r--r--nikola/data/themes/jinja-default/templates/base.tmpl10
-rw-r--r--nikola/data/themes/jinja-default/templates/index.tmpl12
-rw-r--r--nikola/data/themes/jinja-default/templates/list_post.tmpl2
-rw-r--r--nikola/data/themes/jinja-default/templates/post.tmpl27
-rw-r--r--nikola/data/themes/jinja-default/templates/story.tmpl21
-rw-r--r--nikola/data/themes/jinja-default/templates/tag.tmpl2
-rw-r--r--nikola/data/themes/jinja-default/templates/tags.tmpl4
-rw-r--r--nikola/data/themes/monospace/assets/css/code.css62
-rw-r--r--nikola/data/themes/monospace/bundles1
-rw-r--r--nikola/data/themes/monospace/templates/base.tmpl6
-rw-r--r--nikola/data/themes/monospace/templates/base_helper.tmpl36
-rw-r--r--nikola/data/themes/monospace/templates/disqus_helper.tmpl7
-rw-r--r--nikola/data/themes/monospace/templates/index.tmpl12
-rw-r--r--nikola/data/themes/monospace/templates/index_helper.tmpl4
-rw-r--r--nikola/data/themes/monospace/templates/list_post.tmpl2
-rw-r--r--nikola/data/themes/monospace/templates/post.tmpl15
-rw-r--r--nikola/data/themes/monospace/templates/post_helper.tmpl42
-rw-r--r--nikola/data/themes/monospace/templates/story.tmpl10
-rw-r--r--nikola/data/themes/monospace/templates/tag.tmpl2
l---------nikola/data/themes/orphan/assets/css/code.css1
-rw-r--r--nikola/data/themes/orphan/templates/base.tmpl4
-rw-r--r--nikola/data/themes/orphan/templates/base_helper.tmpl36
-rw-r--r--nikola/data/themes/orphan/templates/disqus_helper.tmpl7
-rw-r--r--nikola/data/themes/orphan/templates/index.tmpl10
-rw-r--r--nikola/data/themes/orphan/templates/index_helper.tmpl4
-rw-r--r--nikola/data/themes/orphan/templates/list_post.tmpl2
-rw-r--r--nikola/data/themes/orphan/templates/post.tmpl12
-rw-r--r--nikola/data/themes/orphan/templates/post_helper.tmpl44
-rw-r--r--nikola/data/themes/orphan/templates/story.tmpl10
-rw-r--r--nikola/data/themes/orphan/templates/tag.tmpl2
-rw-r--r--nikola/data/themes/site-planetoid/README1
-rw-r--r--nikola/data/themes/site-planetoid/engine1
-rw-r--r--nikola/data/themes/site-planetoid/parent1
-rw-r--r--nikola/data/themes/site-planetoid/templates/index.tmpl16
-rw-r--r--nikola/data/themes/site-planetoid/templates/post.tmpl9
-rw-r--r--nikola/data/themes/site-planetoid/templates/story.tmpl25
-rw-r--r--nikola/data/themes/site/assets/css/theme.css14
-rw-r--r--nikola/data/themes/site/templates/base.tmpl12
-rw-r--r--nikola/main.py7
-rw-r--r--nikola/nikola.py157
-rw-r--r--nikola/plugin_categories.py11
-rw-r--r--nikola/plugins/command_check.py21
-rw-r--r--nikola/plugins/command_console.py76
-rw-r--r--nikola/plugins/command_deploy.py26
-rw-r--r--nikola/plugins/command_import_blogger.py17
-rw-r--r--nikola/plugins/command_import_wordpress.py32
-rw-r--r--nikola/plugins/command_install_theme.py4
-rw-r--r--nikola/plugins/command_new_post.py36
-rw-r--r--nikola/plugins/command_planetoid.plugin (renamed from extra_plugins/command_planetoid.plugin)0
-rw-r--r--nikola/plugins/command_planetoid/__init__.py (renamed from extra_plugins/command_planetoid/__init__.py)117
-rw-r--r--nikola/plugins/compile_bbcode.py16
-rw-r--r--nikola/plugins/compile_html.py14
-rw-r--r--nikola/plugins/compile_ipynb.plugin (renamed from extra_plugins/compile_ipynb.plugin)0
-rw-r--r--nikola/plugins/compile_ipynb/README.txt (renamed from extra_plugins/compile_ipynb/README.txt)0
-rw-r--r--nikola/plugins/compile_ipynb/__init__.py (renamed from extra_plugins/compile_ipynb/__init__.py)18
-rw-r--r--nikola/plugins/compile_markdown/__init__.py51
-rw-r--r--nikola/plugins/compile_markdown/mdx_gist.py189
-rw-r--r--nikola/plugins/compile_markdown/mdx_nikola.py56
-rw-r--r--nikola/plugins/compile_markdown/mdx_podcast.py87
-rw-r--r--nikola/plugins/compile_misaka.plugin10
-rw-r--r--nikola/plugins/compile_misaka/__init__.py82
-rw-r--r--nikola/plugins/compile_rest/__init__.py81
-rw-r--r--nikola/plugins/compile_rest/dummy.py44
-rw-r--r--nikola/plugins/compile_rest/gist_directive.py2
-rw-r--r--nikola/plugins/compile_rest/listing.py121
-rw-r--r--nikola/plugins/compile_rest/pygments_code_block_directive.py424
-rw-r--r--nikola/plugins/compile_rest/slides.py79
-rw-r--r--nikola/plugins/compile_rest/soundcloud.py62
-rw-r--r--nikola/plugins/compile_rest/vimeo.py114
-rw-r--r--nikola/plugins/compile_rest/youtube.py57
-rw-r--r--nikola/plugins/compile_textile.py14
-rw-r--r--nikola/plugins/compile_txt2tags.py14
-rw-r--r--nikola/plugins/compile_wiki.py12
-rw-r--r--nikola/plugins/task_archive.py66
-rw-r--r--nikola/plugins/task_copy_assets.py25
-rw-r--r--nikola/plugins/task_create_bundles.py46
-rw-r--r--nikola/plugins/task_indexes.py35
-rw-r--r--nikola/plugins/task_localsearch.plugin (renamed from extra_plugins/task_localsearch.plugin)0
-rw-r--r--nikola/plugins/task_localsearch/MIT-LICENSE.txt (renamed from extra_plugins/task_localsearch/MIT-LICENSE.txt)0
-rw-r--r--nikola/plugins/task_localsearch/__init__.py (renamed from extra_plugins/task_localsearch/__init__.py)14
-rwxr-xr-xnikola/plugins/task_localsearch/files/assets/css/img/expand.pngbin0 -> 424 bytes
-rwxr-xr-xnikola/plugins/task_localsearch/files/assets/css/img/link.pngbin0 -> 463 bytes
-rw-r--r--nikola/plugins/task_localsearch/files/assets/css/img/loader.gif (renamed from extra_plugins/task_localsearch/files/assets/css/loader.gif)bin4178 -> 4178 bytes
-rw-r--r--nikola/plugins/task_localsearch/files/assets/css/img/search.gif (renamed from extra_plugins/task_localsearch/files/assets/css/search.gif)bin208 -> 208 bytes
-rwxr-xr-xnikola/plugins/task_localsearch/files/assets/css/tipuesearch.css232
-rw-r--r--nikola/plugins/task_localsearch/files/assets/js/tipuesearch.js (renamed from extra_plugins/task_localsearch/files/assets/js/tipuesearch.js)113
-rw-r--r--nikola/plugins/task_localsearch/files/assets/js/tipuesearch_set.js (renamed from extra_plugins/task_localsearch/files/assets/js/tipuesearch_set.js)0
-rwxr-xr-xnikola/plugins/task_localsearch/files/tipue_search.html31
-rw-r--r--nikola/plugins/task_mustache.plugin (renamed from extra_plugins/task_mustache.plugin)0
-rw-r--r--nikola/plugins/task_mustache/__init__.py (renamed from extra_plugins/task_mustache/__init__.py)26
-rw-r--r--nikola/plugins/task_mustache/mustache-template.html (renamed from extra_plugins/task_mustache/mustache-template.html)0
-rw-r--r--nikola/plugins/task_mustache/mustache.html (renamed from extra_plugins/task_mustache/mustache.html)0
-rw-r--r--nikola/plugins/task_redirect.py2
-rw-r--r--nikola/plugins/task_render_galleries.py29
-rw-r--r--nikola/plugins/task_render_listings.py12
-rw-r--r--nikola/plugins/task_render_pages.py5
-rw-r--r--nikola/plugins/task_render_posts.py84
-rw-r--r--nikola/plugins/task_render_rss.py11
-rw-r--r--nikola/plugins/task_render_sources.py21
-rw-r--r--nikola/plugins/task_render_tags.py20
-rw-r--r--nikola/plugins/task_sitemap/__init__.py94
-rw-r--r--nikola/plugins/task_sitemap/sitemap_gen.py2137
-rw-r--r--nikola/post.py312
-rw-r--r--nikola/rc4.py76
-rw-r--r--nikola/utils.py149
-rw-r--r--requirements-3.txt6
-rw-r--r--requirements.txt2
-rw-r--r--scripts/import_po.py29
-rw-r--r--scripts/nikola.bat2
-rwxr-xr-xsetup.py27
-rw-r--r--tests/base.py58
-rw-r--r--tests/test_compile_markdown.py122
-rw-r--r--tests/test_integration.py49
-rw-r--r--tests/test_plugin_importing.py6
-rw-r--r--tests/test_rst_extensions.py223
-rw-r--r--translations/nikola.messages/ca.po74
-rw-r--r--translations/nikola.messages/de.po75
-rw-r--r--translations/nikola.messages/el.po75
-rw-r--r--translations/nikola.messages/en.po72
-rw-r--r--translations/nikola.messages/es.po74
-rw-r--r--translations/nikola.messages/fr.po75
-rw-r--r--translations/nikola.messages/it.po74
-rw-r--r--translations/nikola.messages/ja.po75
-rw-r--r--translations/nikola.messages/pl.po75
-rw-r--r--translations/nikola.messages/pt_BR.po75
-rw-r--r--translations/nikola.messages/ru.po74
-rw-r--r--translations/nikola.messages/zh_CN.po75
171 files changed, 4815 insertions, 4679 deletions
diff --git a/.gitignore b/.gitignore
index 3af3e85..d556b59 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,5 @@
*.py[co]
*.db
-*.html
tmp/
output/
build/
diff --git a/.travis.yml b/.travis.yml
index c30c430..a4c3081 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,10 +4,8 @@ python:
- "2.7"
- "3.2"
- "3.3"
-# command to install dependencies
-# pip is run inside the script because we need have different requirements for Python 2 / 3.
install:
- - "python install_requirements.py"
+ - "pip install -r requirements.txt --use-mirrors"
- "pip install flake8 --use-mirrors"
- "pip install . --use-mirrors"
# We run tests and afterwards nikola to see if the command is executable.
diff --git a/.tx/config b/.tx/config
new file mode 100644
index 0000000..6a0ee7c
--- /dev/null
+++ b/.tx/config
@@ -0,0 +1,7 @@
+[main]
+host = https://www.transifex.com
+
+[nikola.messages]
+file_filter = translations/nikola.messages/<lang>.po
+source_lang = en
+
diff --git a/CHANGES.txt b/CHANGES.txt
index 376bcdc..9e7976d 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,100 @@
+New in 5.4.4
+============
+
+Features
+--------
+
+* New Japanese translation.
+* Nikola check exists with 1 if there is an error
+* New HIDE_UNTRANSLATED_POSTS option that ensures you don't have mixed-language pages (Issue #373)
+* New theme "site-planetoid" for use with the planetoid plugin.
+* New 'retired' tag for posts that should no longer be in feeds.
+
+Bugfixes
+--------
+
+* Added post data as a uptodate check for mustache (Issue #456)
+* Rebuild post pages when the post's translation list changes (Issue #458)
+* Handle "-h" (Issue #460)
+* Added correct help for console command (Issue #460)
+* Escape twittercard data (Issue #452)
+* Added missing "twittercard" in story template
+* Added support for per-language tags (Issue #450)
+* Fix wrong path splitting (Issue #434)
+* Remember locale even when set_locale failes (Issue #446)
+* Decode path argument in new_post (Issue #442)
+* task_indexes had missing config dependencies (Issue #441)
+* Removed bogus links to slides assets that were removed
+* Compressed files were seen as unknown by "nikola check"
+* local search and mustache plugins must be disabled by default (Issue #437)
+* Avoid failure if there are no tags and USE_GZIP is enabled (Issue #439)
+* Fix aspect ratio detection in Vimeo videos (Issue #440)
+* Blogger importer was passing wrong options to "nikola init" (Issue #408)
+
+New in 5.4.3
+============
+
+Features
+--------
+
+* Simpler slideshows based on Bootstrap's Carousel
+* New CREATE_MONTHLY_ARCHIVE option, defaults to False (Issue #433)
+* Added gist support for Markdown.
+* New "nocomments" metadata that disables comments for a page/post (Issue #278)
+* New HIDE_UNTRANSLATED_POSTS option (does nothing yet)
+* New EXTRA_HEAD_DATA option, which adds extra things in <HEAD> (Issue #385)
+* Moved translations to transifex.com
+* New custom sitemap generator (Issue #395)
+* New STRIP_INDEX_HTML option for cleaner URLs
+* New alternative markdown compiler based on misaka
+* New "internals" doc
+* Place links to RSS feeds more visible to the visitor
+* New CODE_COLOR_SCHEME option
+* New "template" metadata that changes the template for a page/post (Issue #199)
+* Added workaround for when Disqus doesn't support your exact locale
+ (spanish only at the moment) Issue #389
+* Extra plugins can be enabled via conf.py.
+* Password-protected pages.
+
+Bugfixes
+--------
+
+* Listings CSS fixes (Issue #416)
+* If dateutil is installed, try to use it to parse dates (Issue #419)
+* Fixed posterous import via import_wordpress (Issue #419)
+* Set locale to the value of "lang" in templates, so things like strftime
+ use localized values. (Issue #368)
+* Fixed console command.
+* Cleaned up arbitrary metadata.
+* Don't crash in posts without actual post text.
+* Nicer tag listing.
+* Fixed unicode bug in markdown compiler.
+* Fixed unicode crash with polish dates and %B (Issue #383)
+* Fixed localsearch plugin
+* Warn if combining USE_CDN with a theme providing a copy of bootstrap (Issue #386)
+* Improved localsearch README
+* Updated to Tipue 2.1
+* Don't index draft posts for Tipue (Issue #387)
+* Modernized all rst extensions, added tests (rbistolfi)
+* Removed obsolete custom code-block directive
+* New function messages(msgid, lang=current_lang) available for templates
+* Fixed teasers (Issue #398)
+* Smarter guessing of the default post format (Issue #400)
+* Make headings not overlap navbar in site theme.
+* Added dummy codeblock fallback for docutils < 0.9
+* Detect dependency on included files in rest compiler
+* Use gallery path from config
+* Don't fail in corrupted images
+* Don't assume filenames are ASCII
+* Don't crash if sidebar_links is not set for a language.
+* All RSS feed links for tags pointed to the DEFAULT_LANG one in some themes.
+* Nikola.link and Nikola.path are now locale aware and lang is optional.
+* Make docutils a soft requirement
+* Normalize paths on task names (Issue #406)
+* Updated translations (all 100%!)
+* Planetoid requires only 3 runs now ;-)
+* Blogger import: imports will not result in an TypeError because str.join expects all it's arguments to be of type str
+
New in 5.4.2
============
@@ -15,7 +112,7 @@ Features
* Twitter Card / Open Graph support.
* Smart math support
-* New soundcould directive
+* New soundcloud directive
* Custom "read more" links
* Better time display, timezone support
* Better doit integration (Issue #151)
diff --git a/README.md b/README.md
index 943128f..edb7a7d 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ In goes content, out comes a website, ready to deploy.
Why Static Websites?
--------------------
-Static websites are safer, use fewer resources, and avoid vendor and platform lockin.
+Static websites are safer, use fewer resources, and avoid vendor and platform lock-in.
You can read more about this in the [Nikola Handbook.](http://nikola.ralsina.com.ar/handbook.html#why-static)
What Can Nikola Do?
@@ -16,15 +16,16 @@ What Can Nikola Do?
It has many features, but here are some of the nicer ones:
-* Blogs, with tags, feeds, archives, comments, etc.
-* [Theming](http://nikola.ralsina.com.ar/theming.html)
-* Fast builds, thanks to [doit](http://pydoit.org)
-* Flexible
-* Small codebase (programmers can understand all of Nikola in a couple of hours)
-* [reStructuredText](http://nikola.ralsina.com.ar/quickstart.html) or [Markdown](http://daringfireball.net/projects/markdown) as input languages.
-* Easy [image galleries](http://nikola.ralsina.com.ar/galleries/demo/) (just drop files in a folder!)
+* `Blogs, with tags, feeds, archives, comments, etc. <some-sites-using-nikola.html>`_
+* `Themable <theming.html>`_
+* Fast builds, thanks to `doit <http://python-doit.sf.net>`_
+* Flexible, extensible via plugins
+* Small codebase (programmers can understand all of Nikola core in a day)
+* `reStructuredText <quickstart.html>`_ [`Cheatsheet <http://ubuntuone.com/1M7C5fdbLkggF7ptoUvTq8>`_] or Markdown as input language (also Wiki, BBCode, Textile, and HTML)
+* Easy `image galleries </galleries/demo/>`_ (just drop files in a folder!)
* Syntax highlighting for almost any programming language or markup
-* Multilingual sites
+* Multilingual sites, `translated to 11 languages <https://www.transifex.com/projects/p/nikola/>`_.
* Doesn't reinvent wheels, leverages existing tools.
+* Python 2 and 3 compatible.
For more information, see http://nikola.ralsina.com.ar
diff --git a/docs/creating-a-theme.txt b/docs/creating-a-theme.txt
index 21bf796..11428bd 100644
--- a/docs/creating-a-theme.txt
+++ b/docs/creating-a-theme.txt
@@ -15,7 +15,7 @@ Starting The Theme
First, we create a testing site, and copy the orphan theme from nikola's sources into the right place::
- $ nikola init monospace-site --demo
+ $ nikola init --demo monospace-site
A new site with some sample data has been created at monospace-site.
See README.txt in that folder for more information.
@@ -180,7 +180,7 @@ the posts themselves is different, and that was not described in ``base.tmpl`` a
there is a placeholder called content: ``<%block name="content"></%block>``
That's because ``base.tmpl`` defines the *base* layout. The layout of more specific pages, like "the page that shows
-a lis of posts" is defined in the other templates. Specifically, this is defined in ``index.tmpl``:
+a list of posts" is defined in the other templates. Specifically, this is defined in ``index.tmpl``:
.. code-block:: mako
@@ -211,12 +211,13 @@ box, add links for the posts tags, move the date there, etc.
## -*- coding: utf-8 -*-
<%namespace name="helper" file="index_helper.tmpl"/>
+ <%namespace name="disqus" file="disqus_helper.tmpl"/>
<%inherit file="base.tmpl"/>
<%block name="content">
% for post in posts:
<div class="postbox">
<h1><a href="${post.permalink(lang)}">${post.title(lang)}</a></h1>
- <div class="meta" style="background-color: rgb(234, 234, 234); ">
+ <div class="meta" style="background-color: rgb(234, 234, 234); ">
<span class="authordate">
${messages[lang]["Posted"]}: ${post.date.strftime(date_format)}
</span>
@@ -230,10 +231,12 @@ box, add links for the posts tags, move the date there, etc.
</span>
</div>
${post.text(lang, index_teasers)}
- ${helper.html_disqus_link(post)}
+ ${disqus.html_disqus_link(post.permalink()+"#disqus_thread", post.base_path)}
</div>
% endfor
${helper.html_pager()}
+ ${disqus.html_disqus_script()}
+ </%block>
.. figure:: http://ralsina.com.ar/galleries/random/monospace-4.png
:height: 400px
@@ -246,11 +249,12 @@ Then if we click on the post title, we will see some broken details in the metad
## -*- coding: utf-8 -*-
<%namespace name="helper" file="post_helper.tmpl"/>
+ <%namespace name="disqus" file="disqus_helper.tmpl"/>
<%inherit file="base.tmpl"/>
<%block name="content">
<div class="post">
${helper.html_title()}
- <div class="meta" style="background-color: rgb(234, 234, 234); ">
+ <div class="meta" style="background-color: rgb(234, 234, 234); ">
<span class="authordate">
${messages[lang]["Posted"]}: ${post.date.strftime(date_format)} [<a href="${post.pagenames[lang]+'.txt'}">${messages[lang]["Source"]}</a>]
</span>
@@ -269,7 +273,7 @@ Then if we click on the post title, we will see some broken details in the metad
</div>
${post.text(lang)}
${helper.html_pager(post)}
- ${helper.html_disqus(post)}
+ ${disqus.html_disqus(post.permalink(absolute=True), post.title(lang), post.base_path)}
</div>
</%block>
diff --git a/docs/extending.txt b/docs/extending.txt
index 84a382a..750ec98 100644
--- a/docs/extending.txt
+++ b/docs/extending.txt
@@ -52,7 +52,7 @@ Each and every one of those is a plugin. Let's look at a typical example:
First, the ``command_serve.plugin`` file:
-.. code-block:: init
+.. code-block:: ini
[Core]
Name = serve
@@ -210,7 +210,7 @@ probably want to do a Task plugin, which will make it be part of the
``nikola build`` command. There are the currently available tasks, all
provided by plugins::
- $ nikola build list
+ $ nikola list
build_bundles
copy_assets
diff --git a/docs/internals.txt b/docs/internals.txt
new file mode 100644
index 0000000..7a38f27
--- /dev/null
+++ b/docs/internals.txt
@@ -0,0 +1,131 @@
+Nikola Internals
+================
+
+When trying to guide someone into adding a feature in Nikola, it hit me that
+while the way it's structured makes sense **to me** it is far from obvious.
+
+So, this is a short document explaining what each piece of Nikola does and
+how it all fits together.
+
+Nikola is a Pile of Plugins
+ Most of Nikola is implemented as plugins using `Yapsy <http://yapsy.sourceforge.net/>`_.
+ You can ignore that they are plugins and just think of them as regular python
+ modules and packages with a funny little ``.plugin`` file next to them.
+
+ So, 90% of the time, what you want to do is either write a new plugin or extend
+ an existing one.
+
+ There are several kinds of plugins, all implementing interfaces defined in
+ ``nikola/plugin_categories.py``.
+
+ If your plugin has a dependency, please make sure it doesn't make Nikola
+ throw an exception when the dependency is missing. Try to fail gracefully
+ with an informative message.
+
+Commands are plugins
+ When you use ``nikola foo`` you are using the plugin ``command_foo``. Those are
+ used to extend Nikola's command line. Their interface is defined in the ``Command``
+ class. They take options and arguments and do whatever you want, so go wild.
+
+The ``build`` command is special
+ The ``build`` command triggers a whole lot of things, and is the core of Nikola
+ because it's the one that you use to build sites. So it deserves its own section.
+
+The Build Command
+-----------------
+
+Nikola's goal is similar, deep at heart, to a Makefile. Take sources, compile them
+into something, in this case a website. Instead of a Makefile, Nikola uses
+`doit <http://pydoit.com>`_
+
+Doit has the concept of "tasks". The 1 minute summary of tasks is that they have:
+
+actions
+ What the task **does**. For example, convert a markdown document into HTML.
+
+dependencies
+ If this file changes, then we need to redo the actions. If this confguration
+ option changes, redo it, etc.
+
+targets
+ Files that the action generates. No two actions can have the same targets.
+
+basename:name
+ Each task is identified by either a name or a basename:name pair. Nikola
+
+.. sidebar:: More about tasks
+
+ If you ever want to do your own tasks, you really should read the doit
+ `documentation on tasks <http://pydoit.org/tasks.html>`_
+
+So, what Nikola does, when you use the build command, is to read the
+configuration ``conf.py`` from the current folder, instantiate
+the ``Nikola`` class, and have it generate a whole list of tasks for doit
+to process. Then doit will decide which tasks need doing, and do them, in
+the right order.
+
+The place where the tasks are generated is in ``Nikola.gen_tasks``, which collects tasks
+from all the plugins inheriting ``BaseTask``, massages them a bit, then passes them
+to doit.
+
+So, if you want things to happen on ``build`` you want to create a Task plugin, or extend
+one of the existing ones.
+
+.. sidebar:: Tests
+
+ While Nikola is not a hardcore TDD project, we like tests. So, please add them if you can.
+ You can do doctests, you can do unit tests, you can do integration tests. There is support
+ for all of them.
+
+Post and Stories
+----------------
+
+Nikola has a concept of posts and stories. Both are more or less the same thing, except
+posts are added into RSS feeds and stories are not. All of them are in a list called
+"the timeline" formed by objects of class ``Post``
+
+When you are creating a task that needs the list of posts and/or stories (for example,
+the RSS creation plugin), your plugin should call ``self.site.scan_posts()`` to ensure
+the timeline is created and available in ``self.site.timeline``. You should not modify
+the timeline, because it will cause consistency issues.
+
+.. sidebar:: scan_posts
+
+ The scan_posts function is what reads your site and creates the timeline.
+
+ I am considering moving scan_posts off the core and into its own plugin
+ so it can be replaced (for example, by a version that reads a database
+ instead of scanning a folder tree).
+
+Your plugin can use the timeline to generate "stuff" (technical term). For example,
+Nikola comes with plugins that use the timeline to create a website (surprised?).
+
+The workflow included with nikola is as follows:
+
+#. The post is assigned a compiler based on its extension and the ``post_compilers`` option.
+#. The compiler is applied to the post data and a "HTML fragment" is produced. That
+ fragment is stored in a cache (the ``render_posts`` plugin).
+#. The configured theme has templates (and a template engine), which are applied to the post's
+ HTML fragment and metadata (the ``render_pages`` plugin).
+#. The original sources for the post are copied to some accessible place (the ``render_sources`` plugin)
+#. If the post is tagged, some pages and RSS feeds for each tag are updated (the ``render_tags`` plugin)
+#. If the post is new, it's included in the blog's RSS feed (the ``render_rss`` plugin)
+#. The post is added in the right place in the index pages for the blog (the ``task_indexes`` plugin)
+
+You can add whatever you want to that list: just create a plugin for it.
+
+You can also expand Nikola's capabilities at several points:
+
+compilers
+ Nikola supports a variety of markups. If you want to add another one, you need to create
+ a ``Compiler`` plugin.
+
+templates
+ Nikola's themes can use Jinja2 or Mako templates. If you prefer another template system,
+ you have to create a ``TemplateSystem`` plugin.
+
+themes
+ To change how the generated site looks, you can create custom themes.
+
+And of course, you can also replace or extend each of the existing plugins.
+
diff --git a/docs/manual.txt b/docs/manual.txt
index b2290c2..4833eae 100644
--- a/docs/manual.txt
+++ b/docs/manual.txt
@@ -1,8 +1,7 @@
The Nikola Handbook
===================
-:Version: 5.4.2
-:Author: Roberto Alsina <ralsina@netmanagers.com.ar>
+:Version: 5.4.4
.. class:: alert alert-info pull-right
@@ -17,7 +16,7 @@ After you have Nikola installed:
Create a empty site:
``nikola init mysite``
-You can create a site with demo files in it with ``nikola init mysite --demo``
+You can create a site with demo files in it with ``nikola init --demo mysite``
The rest of these commands have to be executed inside the new ``mysite`` folder.
@@ -84,7 +83,7 @@ Getting Help
------------
* Feel free to contact me at ralsina@netmanagers.com.ar for questions about Nikola.
-* You can file bugs at `the issue tracker <http://code.google.com/p/nikola-generator/issues/list>`__
+* You can file bugs at `the issue tracker <https://github.com/ralsina/nikola-site/issues>`__
* You can discuss Nikola at the `nikola-discuss google group <http://groups.google.com/group/nikola-discuss>`_
* You can subscribe to `the Nikola Blog <http://nikola.ralsina.com.ar/blog>`_
* You can follow `Nikola on Twitter <https://twitter.com/#!/nikolagenerator>`_
@@ -197,10 +196,10 @@ Longer version:
#. Get `Nikola <http://nikola.ralsina.com.ar/>`_
#. Install dependencies. To do that, either:
- #. ``pip install -r requirements.txt`` or...
+ #. ``pip install -r requirements.txt`` and ``pip install .`` or...
#. Install your distribution's packages for all the things
mentioned below, if they exist, or...
- #. Get all of these manually (but why?, use requirements.txt):
+ #. Get all of these manually (but why?, use pip):
#. Get python, if you don't have it.
#. Get `doit <http://pydoit.org>`_
@@ -276,9 +275,9 @@ all the pages again, unless you changed something that the page requires. So, if
the text of a post, or its title, that post page, and all index pages where it is mentioned,
will be recreated. If you change the post page template, then all the post pages will be rebuilt.
-Nikola is mostly a series of doit *tasks*, and you can see them by doing ``nikola build list``::
+Nikola is mostly a series of doit *tasks*, and you can see them by doing ``nikola list``::
- $ nikola build list
+ $ nikola list
Scanning posts . . done!
build_bundles
copy_assets
@@ -398,6 +397,24 @@ source for the content, and ``description`` is mostly useful for SEO.
You can add your own metadata fields in the same manner, if you use a theme that
supports them (for example: ``.. author: John Doe``)
+.. sidebar:: Other Metadata Fields
+
+ Nikola will also use other metadata fields:
+
+ nocomments
+ Set to "True" to disable comments. Example::
+
+ .. nocomments: True
+
+ template
+ Will change the template used to render this page/post specific page. Example::
+
+ .. template: story.tmpl
+
+ password
+ The post will be encrypted and invisible until the reader enters the password.
+ Also, the post's sourcecode will not be available.
+
.. note:: The Two-File Format
@@ -410,11 +427,11 @@ supports them (for example: ``.. author: John Doe``)
2012/09/15 19:52:05
If you are writing a multilingual site, you can also create a per-language
-post file (for example: ``how-to-make-money.txt.es``). This one can have two
-lines of metadata:
+post file (for example: ``how-to-make-money.txt.es``). This one can replace
+metadata of the default language, for example:
-1) The translated title for the post or page
-2) A translated version of the pagename
+* The translated title for the post or page
+* A translated version of the pagename
You can edit these files with your favourite text editor, and once you are happy
with the contents, generate the pages as explained in `Getting Started`_
@@ -470,10 +487,10 @@ one in the list if all of them have it set to False.
The ``new_post`` command supports some options::
- $ nikola help new_post
+ $ nikola help new_post
Purpose: Create a new blog post or site page.
Usage: nikola new_post [options] [path]
-
+
Options:
-p, --page Create a page instead of a blog post.
-t ARG, --title=ARG Title for the page/post.
@@ -520,6 +537,12 @@ If you add a "draft" tag to a post, then it will not be shown in indexes and fee
It *will* be compiled, and if you deploy it it *will* be made available, so use
with care.
+Retired Posts
+~~~~~~~~~~~~~
+
+If you add a "retired" tag to a post, then it will not be shown in indexes and feeds.
+It *will* be compiled, and if you deploy it it *will* be made available, so it will
+not generate 404s for people who had linked to it.
Creating a Page
---------------
@@ -572,7 +595,7 @@ You surely want to edit these options::
# Data about this site
BLOG_TITLE = "Demo Site"
- BLOG_URL = "http://nikola.ralsina.com.ar"
+ SITE_URL = "http://nikola.ralsina.com.ar"
BLOG_EMAIL = "joe@demo.site"
BLOG_DESCRIPTION = "This is a demo site for Nikola."
@@ -718,6 +741,10 @@ Disqus is a good option because:
3) It's free.
4) It's damn nice.
+You can disable comments for a post by adding a "nocomments" metadata field to it::
+
+ .. nocomments: True
+
.. admonition:: Important
In some cases, when you run the test site, you won't see the comments.
@@ -801,7 +828,7 @@ different ones, or about other webservers, please share!
AddType text/css .css
#. Optionally you can greate static compressed copies and save some CPU on your server
- with the GZIP_FILES option in Nikola.
+ with the GZIP_FILES option in Nikola.
#. The webassets Nikola plugin can drastically decrease the number of CSS and JS files your site fetches.
@@ -844,8 +871,8 @@ the embedded player will be set to the native height and width of the video.
You can override this if you wish::
.. vimeo:: 20241459
- height=240
- width=320
+ :height: 240
+ :width: 320
Soundcloud
~~~~~~~~~~
@@ -860,22 +887,19 @@ The ID is 78131362 and you can embed the audio with this::
.. soundcloud:: 78131362
-code-block
-~~~~~~~~~~
-
-This is a somewhat complicated directive to display code nicely. You can just
-embed code like this::
-
- .. code-block:: python
+Code
+~~~~
- print "Hello World!"
+The ``code`` directive has been included in docutils since version 0.9 and now
+replaces Nikola's ``code-block`` directive. To ease the transition, two aliases
+for ``code`` directive are provided: ``code-block`` and ``sourcecode``::
-Or you can include the code from a file::
+ .. code:: python
+ :number-lines:
- .. code-block:: python
- :include: /foo/bar/baz.py
+ print("Our virtues and our failings are inseparable")
-listing
+Listing
~~~~~~~
To use this, you have to put your source code files inside ``listings`` or whatever your
@@ -886,27 +910,22 @@ To use this, you have to put your source code files inside ``listings`` or whate
Will include the source code from ``foo.py``, highlight its syntax in python mode,
and also create a ``listings/foo.py.html`` page and the listing will have a title linking to it.
-Advanced Code Options
-~~~~~~~~~~~~~~~~~~~~~
-
-Both code-block and listing support a number of options, including these:
+Listings support a few extra options so that you can display a fragment instead of the whole
+file in a document:
start-at
- A string, the diplayed code will start when it finds this
+ Takes a string, and starts displaying the code at the first line that matches it.
+start-before
+ Takes a string, and starts displaying the code right before the first line that matches it.
end-at
- A string, the diplayed code will end when it finds this
-start-after
- A string, the diplayed code will start in the line after this
+ Takes a string, and stops displaying the code at the first line that matches it.
end-before
- A string, the diplayed code will end in the line before this
-linenos
- Display line numbers
-linenos_offset
- Use the original file's line numbers (warning: broken)
-tab-width
- Size of the tabs (default 4)
-
-gist
+ Takes a string, and stops displaying the code right before the first line that matches it.
+
+If you set start-at and start-before, start-at wins. If you set end-at and end-before, end-at wins.
+If you make it so your listing ends before it starts, it's frowned upon and nothing will be shown.
+
+Gist
~~~~
You can easily embed GitHub gists with this directive, like this::
@@ -925,8 +944,6 @@ Slideshows
To create an image slideshow, you can use the ``slides`` directive. For example::
.. slides::
- :preload:
- :play: 350
/galleries/demo/tesla_conducts_lg.jpg
/galleries/demo/tesla_lightning2_lg.jpg
@@ -934,13 +951,6 @@ To create an image slideshow, you can use the ``slides`` directive. For example:
/galleries/demo/tesla_lightning1_lg.jpg
/galleries/demo/tesla_tower1_lg.jpg
-This is based on `slidejs <http://slidesjs.com/>`_ and it supports
-`the options described there <http://slidesjs.com/#options>`_ with one minor tweak to make them
-fit in docutils convention: If the option takes a boolean value, you just have to add it or not. For example,
-to enable preloading, just use the ``:preload:`` option.
-
-If the option takes any other kind of argument, just use it after the option, like ``play`` in the
-above example.
Importing Your Wordpress Site Into Nikola
-----------------------------------------
@@ -1012,7 +1022,7 @@ corresponding lines in your ``conf.py``.
An example configuration that uses the Twitter nickname of the website
and the authors Twitter user ID is found below.
-.. code-block:: Python
+.. code-block:: python
TWITTER_CARD = {
'use_twitter_cards': True, # enable Twitter Cards / Open Graph
@@ -1022,6 +1032,103 @@ and the authors Twitter user ID is found below.
'creator:id': 654321, # Same as creator, but the Twitter user's ID.
}
+
+Extra Plugins
+-------------
+
+These are plugins that may not be widely used or that are a bit too radical or
+experimental for the general public.
+
+To enable them for your site please look for `ENABLED_EXTRAS` in your ``conf.py``.
+
+Planetoid
+~~~~~~~~~
+
+This plugin converts Nikola into the equivalent of `Planet <http://www.planetplanet.org/>`_
+a feed aggregator. It requires `PeeWee <https://github.com/coleifer/peewee>`_ and
+`Feedparser <http://code.google.com/p/feedparser/>`_ to work.
+
+It has a configuration option: PLANETOID_REFRESH which is the number of minutes
+before retrying a feed (defaults to 60).
+
+You need to create a ``feeds`` file containing the data of which feeds you want to
+aggregate. The format is very simple::
+
+ # Roberto Alsina
+ http://feeds2.feedburner.com/PostsInLateralOpinionAboutPython
+ Roberto Alsina
+
+#. Lines that start with ``#`` are comments and ignored.
+#. Lines that start with http are feed URLs.
+#. URL lines have to be followed by the "real name" of the feed.
+
+After all that is in place, just run ``nikola build`` and you'll get
+a planet.
+If you run ``nikola build`` for the first time you need to actually issue
+the command three times until the planet is build.
+
+There is a special theme for the planets called `site-planetoid`. To use
+this set `THEME` in your ``conf.py`` to ``'site-planetoid'``.
+This is special in the case that it redirects users to the original URL of the post
+when they try to open a post.
+
+Local Search
+~~~~~~~~~~~~
+
+If you don't want to depend on google or duckduckgo to implement search for you,
+or just want it to wok even if you are offline, enable this plugin and the
+search will be performed client side.
+
+This plugin implements a Tipue-based local search for your site.
+
+To use it, copy task_localsearch.plugin and task_localsearch
+into a plugins/ folder in your nikola site.
+
+After you build your site, you will have several new files in assets/css and assets/js
+and a tipue_search.html that you can use as a basis for using this in your site.
+
+For more information about how to customize it and use it, please refer to the tipue
+docs at http://www.tipue.com/search/
+
+Tipue is under an MIT license (see MIT-LICENSE.txt)
+
+Here's a set of example settings for conf.py that should work nicely with the "site" theme::
+
+ SEARCH_FORM = """
+ <span class="navbar-form pull-left">
+ <input type="text" id="tipue_search_input">
+ </span>"""
+
+ ANALYTICS = """
+ <script type="text/javascript" src="/assets/js/tipuesearch_set.js"></script>
+ <script type="text/javascript" src="/assets/js/tipuesearch.js"></script>
+ <script type="text/javascript">
+ $(document).ready(function() {
+ $('#tipue_search_input').tipuesearch({
+ 'mode': 'json',
+ 'contentLocation': '/assets/js/tipuesearch_content.json',
+ 'showUrl': false
+ });
+ });
+ </script>
+ """
+
+ EXTRA_HEAD_DATA = """
+ <link rel="stylesheet" type="text/css" href="/assets/css/tipuesearch.css">
+ <div id="tipue_search_content" style="margin-left: auto; margin-right: auto; padding: 20px;"></div>
+ """
+
+The <div> in EXTRA_HEAD_DATA is a hack but it will migrate into the <body> of the
+documents thanks to magic, and will hold the search results after the user searches.
+
+Mustache
+~~~~~~~~
+
+This task gives you a ``mustache.html`` file which lets you access your whole
+blog without reloading the page, using client-side templates. Makes it much
+faster and modern ;-)
+
+
License
-------
diff --git a/docs/theming.txt b/docs/theming.txt
index 6c0c0e4..d767cae 100644
--- a/docs/theming.txt
+++ b/docs/theming.txt
@@ -44,8 +44,8 @@ messages
And these optional files:
parent
- A text file that, on its first line, contains the name of the **parent theme**.
- Any resources missing on this theme, will be looked up in the parent theme
+ A text file that, on its first line, contains the name of the **parent theme**.
+ Any resources missing on this theme, will be looked up in the parent theme
(and then in the grandparent, etc).
The ``parent`` is so you don't have to create a full theme each time: just create an
@@ -77,7 +77,7 @@ Templates
In templates there is a number of files whose name ends in ``.tmpl``. Those are the
theme's page templates. They are done using the `Mako <http://makotemplates.org>`_
-or `Jinja2 <http://jinja.pocoo.org>`_ template languages. If you want to do a theme, you
+or `Jinja2 <http://jinja.pocoo.org>`_ template languages. If you want to do a theme, you
should learn one first. What engine is used by the theme is declared in the ``engine`` file.
The rest of this document explains Mako templates, but Jinja2 is fairly similar.
@@ -132,7 +132,7 @@ base.tmpl
The included themes use at least these:
* ``rss_link`` a link to custom RSS feed, although it may be empty)
- * ``blog_url`` the URL for your site
+ * ``site_url`` the URL for your site
* ``blog_title`` the name of your site
* ``content_footer`` things like copyright notices, disclaimers, etc.
* ``license`` a larger license badge
@@ -195,47 +195,8 @@ you would need to maintain the inheritance as it is, or not require whatever dat
Messages and Translations
-------------------------
-When you modify templates, you may want to add text in them (for example: "About Me").
-Instead of adding the text directly, which makes it impossible to translate to other
-languages, add it like this::
-
- ${messages[lang]["About Me"]}
-
-Then, in ``messages/en.py`` add it along the other strings::
-
- MESSAGES = [
- u"Posts for year %s",
- u"Archive",
- u"Posts about %s:",
- u"Tags",
- u"Also available in: ",
- u"More posts about",
- u"Posted:",
- u"Original site",
- u"Read in english",
- u"About Me",
- ]
-
-Then, when I want to use your theme in spanish, all I have to do is add a line in ``messages/es.py``::
-
- MESSAGES = {
- u"LANGUAGE": u"Español",
- u"Posts for year %s": u"Posts del año %s",
- u"Archive": u"Archivo",
- u"Posts about %s:": u"Posts sobre %s",
- u"Tags": u"Tags",
- u"Also available in: ": u"También disponible en: ",
- u"More posts about": u"Más posts sobre",
- u"Posted:": u"Publicado:",
- u"Original site": u"Sitio original",
- u"Read in english": u"Leer en español",
- u"About Me": u"Acerca del autor",
- }
-
-And voilá, your theme works in spanish. Don't remove strings from these files even if it seems
-your theme is not using them. Some are used internally in Nikola to generate titles and
-similar things.
-
-To create a new translation, just copy one of the existing ones, translate the right side of
-every string to your language, save it and send it to me, I will add it to Nikola!
+The included themes are translated into a variety of languages. You can add your own translation
+at https://www.transifex.com/projects/p/nikola/
+If you want to create a theme that has new strings, and you want those strings to be translatable,
+then your theme will need a custom ``messages`` folder.
diff --git a/extra_plugins/README.txt b/extra_plugins/README.txt
deleted file mode 100644
index 2a04614..0000000
--- a/extra_plugins/README.txt
+++ /dev/null
@@ -1,62 +0,0 @@
-Extra Plugins
-=============
-
-These are plugins that may not be widely used or that are a bit too radical or
-experimental for the general public.
-
-To enable them for you, create a ``plugins/`` folder in your site, and copy
-both the ``.plugin`` file and the matching ``.py`` file or folder.
-
-Planetoid
----------
-
-This plugin converts Nikola into the equivalent of `Planet <http://www.planetplanet.org/>`_
-a feed aggregator. It requires `PeeWee <https://github.com/coleifer/peewee>`_ and
-`Feedparser <http://code.google.com/p/feedparser/>`_ to work.
-
-It has a configuration option: PLANETOID_REFRESH which is the number of minutes
-before retrying a feed (defaults to 60).
-
-You need to create a ``feeds`` file containing the data of which feeds you want to
-aggregate. The format is very simple::
-
- # Roberto Alsina
- http://feeds2.feedburner.com/PostsInLateralOpinionAboutPython
- Roberto Alsina
-
-#. Lines that start with ``#`` are comments and ignored.
-#. Lines that start with http are feed URLs.
-#. URL lines have to be followed by the "real name" of the feed.
-
-FIXME: explain the planetoid theme stuff
-
-After all that is in place, just run ``nikola build`` and you'll get
-a planet.
-
-Local Search
-------------
-
-If you don't want to depend on google or duckduckgo to implement search for you,
-or just want it to wok even if you are offline, enable this plugin and the
-search will be performed client side.
-
-This plugin implements a Tipue-based local search for your site.
-
-To use it, copy task_localsearch.plugin and task_localsearch
-into a plugins/ folder in your nikola site.
-
-After you build your site, you will have several new files in assets/css and assets/js
-and a search.html that you can use as a basis for using this in your site.
-
-For more information about how to customize it and use it, please refer to the tipue
-docs at http://www.tipue.com/search/
-
-Tipue is under an MIT license (see MIT-LICENSE.txt)
-
-
-Mustache
---------
-
-This task gives you a ``mustache.html`` file which lets you access your whole
-blog without reloading the page, using client-side templates. Makes it much
-faster and modern ;-)
diff --git a/extra_plugins/command_planetoid/index.tmpl b/extra_plugins/command_planetoid/index.tmpl
deleted file mode 100644
index da9ff5d..0000000
--- a/extra_plugins/command_planetoid/index.tmpl
+++ /dev/null
@@ -1,26 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="base.tmpl"/>
-<%block name="content">
- % for post in posts:
- <div style="border: 2px solid darkgrey; margin-bottom: 12px; border-radius: 4px; padding:12px; overflow: auto;">
- <a href="${post.link}"><h1>${post.title(lang)}</a>
- <small>&nbsp;&nbsp;
- Publicado: ${post.date}
- </small></h1>
- ${post.text(lang)}
- </div>
- % endfor
- <div>
-<ul class="pager">
- %if prevlink:
- <li class="previous">
- <a href="${prevlink}">&larr; Posts posteriores</a>
- </li>
- %endif
- %if nextlink:
- <li class="next">
- <a href="${nextlink}">Posts anteriores &rarr;</a>
- </li>
- %endif
-</ul>
-</%block>
diff --git a/extra_plugins/command_planetoid/post.tmpl b/extra_plugins/command_planetoid/post.tmpl
deleted file mode 100644
index 2c4c220..0000000
--- a/extra_plugins/command_planetoid/post.tmpl
+++ /dev/null
@@ -1,10 +0,0 @@
-## -*- coding: utf-8 -*-
-<html>
-<body>
-<script type="text/javascript">
-<!--
-window.location = "${post.link}"
-//-->
-</script>
-Redirecting you to <a href="${post.link}">the original location.</a>
-</body> \ No newline at end of file
diff --git a/extra_plugins/task_localsearch/files/assets/css/tipuesearch.css b/extra_plugins/task_localsearch/files/assets/css/tipuesearch.css
deleted file mode 100755
index 144c97d..0000000
--- a/extra_plugins/task_localsearch/files/assets/css/tipuesearch.css
+++ /dev/null
@@ -1,182 +0,0 @@
-
-/*
-Tipue Search 2.0
-Copyright (c) 2012 Tipue
-Tipue Search is released under the MIT License
-http://www.tipue.com/search
-*/
-
-
-em
-{
- font: inherit;
- font-weight: 400;
-}
-#tipue_search_input
-{
- font: 13px/1.5 'open sans', sans-serif;
- color: #333;
- padding: 7px;
- margin: 0;
- width: 160px;
- border: 1px solid #d3d3d3;
- border-radius: 3px;
- -moz-appearance: none;
- -webkit-appearance: none;
- outline: none;
-}
-#tipue_search_input:focus
-{
- border-color: #c3c3c3;
- box-shadow: 0 0 3px rgba(0,0,0,.2);
-}
-#tipue_search_button
-{
- width: 60px;
- height: 33px;
- margin-top: 1px;
- border: 1px solid #dcdcdc;
- border-radius: 3px;
- background: #f1f1f1 url('search.gif') no-repeat center;
- outline: none;
-}
-#tipue_search_button:hover
-{
- border: 1px solid #c3c3c3;
- -moz-box-shadow: 1px 1px 2px #e3e3e3;
- -webkit-box-shadow: 1px 1px 2px #e3e3e3;
- box-shadow: 1px 1px 2px #e3e3e3;
-}
-
-#tipue_search_content
-{
- clear: left;
- width: 650px;
- padding: 25px 0 13px 0;
- margin: 0;
-}
-#tipue_search_loading
-{
- padding-top: 60px;
- background: #fff url('loader.gif') no-repeat left;
-}
-
-#tipue_search_warning_head
-{
- font: 14px/1.5 'open sans', sans-serif;
- color: #333;
-}
-#tipue_search_warning
-{
- font: 13px/1.5 'open sans', sans-serif;
- color: #333;
- font-weight: 300;
- margin: 13px 0;
-}
-#tipue_search_warning a
-{
- color: #36c;
- text-decoration: none;
-}
-#tipue_search_warning a:hover
-{
- padding-bottom: 1px;
- border-bottom: 1px solid #ccc;
-}
-
-#tipue_search_results_count
-{
- font: 13px/1.5 'open sans', sans-serif;
- color: #333;
- font-weight: 300;
-}
-
-#tipue_search_content_title
-{
- font: 16px/1.5 'open sans', sans-serif;
- color: #333;
- margin-top: 27px;
-}
-#tipue_search_content_title a
-{
- color: #36c;
- text-decoration: none;
-}
-#tipue_search_content_title a:hover
-{
- padding-bottom: 1px;
- border-bottom: 1px solid #ccc;
-}
-#tipue_search_content_text
-{
- font: 13px/1.5 'open sans', sans-serif;
- color: #333;
- font-weight: 300;
- line-height: 21px;
- padding: 9px 0;
-}
-#tipue_search_content_loc
-{
- font: 13px/1.5 'open sans', sans-serif;
- color: #333;
- font-weight: 300;
-}
-#tipue_search_content_loc a
-{
- color: #777;
- text-decoration: none;
-}
-#tipue_search_content_loc a:hover
-{
- padding-bottom: 1px;
- border-bottom: 1px solid #ccc;
-}
-
-#tipue_search_foot
-{
- margin: 43px 0 31px 0;
-}
-#tipue_search_foot_boxes
-{
- padding: 0;
- margin: 0;
- font: 12px/1 'open sans', sans-serif;
-}
-#tipue_search_foot_boxes li
-{
- list-style: none;
- margin: 0;
- padding: 0;
- display: inline;
-}
-#tipue_search_foot_boxes li a
-{
- padding: 7px 10px 8px 10px;
- background-color: #f5f5f5;
- background: -webkit-linear-gradient(top, #f7f7f7, #f1f1f1);
- background: -moz-linear-gradient(top, #f7f7f7, #f1f1f1);
- background: -ms-linear-gradient(top, #f7f7f7, #f1f1f1);
- background: -o-linear-gradient(top, #f7f7f7, #f1f1f1);
- background: linear-gradient(top, #f7f7f7, #f1f1f1);
- border: 1px solid #dcdcdc;
- border-radius: 3px;
- color: #333;
- margin-right: 7px;
- text-decoration: none;
- text-align: center;
-}
-#tipue_search_foot_boxes li.current
-{
- padding: 7px 10px 8px 10px;
- background: #fff;
- border: 1px solid #dcdcdc;
- border-radius: 3px;
- color: #333;
- margin-right: 7px;
- text-align: center;
-}
-#tipue_search_foot_boxes li a:hover
-{
- border: 1px solid #c3c3c3;
- box-shadow: 1px 1px 2px #e3e3e3;
-}
diff --git a/install_requirements.py b/install_requirements.py
deleted file mode 100644
index bea8813..0000000
--- a/install_requirements.py
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
-Helper tool to install the right requirements using pip based on the
-running Python version. It requires ``pip`` to be installed and found
-in the $PATH.
-
-:author: Niko Wenselowski
-"""
-from __future__ import unicode_literals, print_function
-
-import sys
-import os
-
-
-def get_requirements_file_path():
- """Returns the absolute path to the correct requirements file."""
- directory = os.path.dirname(__file__)
-
- if sys.version_info[0] == 3:
- requirements_file = 'requirements-3.txt'
- else:
- requirements_file = 'requirements.txt'
-
- return os.path.join(directory, requirements_file)
-
-
-def main():
- requirements_file = get_requirements_file_path()
- print('Installing requirements from %s' % requirements_file)
- os.system('pip install -r %s --use-mirrors' % requirements_file)
-
-
-if __name__ == '__main__':
- main()
diff --git a/nikola/conf.py.in b/nikola/conf.py.in
index 7d75295..c2bf8ff 100644
--- a/nikola/conf.py.in
+++ b/nikola/conf.py.in
@@ -17,7 +17,7 @@ BLOG_TITLE = "${BLOG_TITLE}"
SITE_URL = "${SITE_URL}"
# This is the URL where nikola's output will be deployed.
# If not set, defaults to SITE_URL
-# BASE_URL = "${SITE_URL}
+# BASE_URL = "${SITE_URL}"
BLOG_EMAIL = "${BLOG_EMAIL}"
BLOG_DESCRIPTION = "${BLOG_DESCRIPTION}"
@@ -47,7 +47,7 @@ DEFAULT_LANG = "${DEFAULT_LANG}"
# The format is {"translationcode" : "path/to/translation" }
# the path will be used as a prefix for the generated pages location
TRANSLATIONS = {
- "${DEFAULT_LANG}": "",
+ DEFAULT_LANG: "",
# Example for another language:
# "es": "./es",
}
@@ -58,6 +58,7 @@ SIDEBAR_LINKS = {
DEFAULT_LANG: (
('/archive.html', 'Archives'),
('/categories/index.html', 'Tags'),
+ ('/rss.xml', 'RSS'),
),
}
@@ -109,6 +110,12 @@ post_compilers = ${POST_COMPILERS}
# Set to False for two-file posts, with separate metadata.
# ONE_FILE_POSTS = True
+# If this is set to True, then posts that are not translated to a language
+# LANG will not be visible at all in the pages in that language.
+# If set to False, the DEFAULT_LANG version will be displayed for
+# untranslated posts.
+# HIDE_UNTRANSLATED_POSTS = False
+
# Paths for different autogenerated bits. These are combined with the
# translation paths.
@@ -124,11 +131,16 @@ post_compilers = ${POST_COMPILERS}
# Final location is output / TRANSLATION[lang] / INDEX_PATH / index-*.html
# INDEX_PATH = ""
+
+# Create per-month archives instead of per-year
+# CREATE_MONTHLY_ARCHIVE = False
# Final locations for the archives are:
# output / TRANSLATION[lang] / ARCHIVE_PATH / ARCHIVE_FILENAME
# output / TRANSLATION[lang] / ARCHIVE_PATH / YEAR / index.html
+# output / TRANSLATION[lang] / ARCHIVE_PATH / YEAR / MONTH / index.html
# ARCHIVE_PATH = ""
# ARCHIVE_FILENAME = "archive.html"
+
# Final locations are:
# output / TRANSLATION[lang] / RSS_PATH / rss.xml
# RSS_PATH = ""
@@ -208,11 +220,17 @@ post_compilers = ${POST_COMPILERS}
# INDEXES_TITLE = "" # If this is empty, the default is BLOG_TITLE
# INDEXES_PAGES = "" # If this is empty, the default is 'old posts page %d' translated
-# Name of the theme to use. Themes are located in themes/theme_name
+# Name of the theme to use.
# THEME = 'site'
+# Color scheme to be used for code blocks. If your theme provide "assets/css/code.css" this
+# is ignored.
+# Can be any of autumn borland bw colorful default emacs friendly fruity manni monokai
+# murphy native pastie perldoc rrt tango trac vim vs
+# CODE_COLOR_SCHEME = default
+
# If you use 'site-reveal' theme you can select several subthemes
-# THEME_REVEAL_CONGIF_SUBTHEME = 'sky' # You can also use: beige/serif/simple/night/default
+# THEME_REVEAL_CONGIF_SUBTHEME = 'sky' # You can also use: beige/serif/simple/night/default
# Again, if you use 'site-reveal' theme you can select several transitions between the slides
# THEME_REVEAL_CONGIF_TRANSITION = 'cube' # You can also use: page/concave/linear/none/default
@@ -261,6 +279,11 @@ CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL,
# Enable comments on picture gallery pages?
# COMMENTS_IN_GALLERIES = False
+# If a link ends in /index.html, drop the index.html part.
+# http://mysite/foo/bar/index.html => http://mysite/foo/bar/
+# Default = False
+# STRIP_INDEX_HTML = False
+
# Do you want a add a Mathjax config file?
# MATHJAX_CONFIG = ""
@@ -328,6 +351,9 @@ CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL,
# external resources.
# USE_CDN = False
+# Extra things you want in the pages HEAD tag. This will be added right
+# before </HEAD>
+# EXTRA_HEAD_DATA = ""
# Google analytics or whatever else you use. Added to the bottom of <body>
# in the default template (base.tmpl).
# ANALYTICS = ""
@@ -379,6 +405,15 @@ CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL,
# Plugins you don't want to use. Be careful :-)
# DISABLED_PLUGINS = ["render_galleries"]
+# Experimental plugins - use at your own risk.
+# They probably need some manual adjustments - please see their respective readme.
+# ENABLED_EXTRAS = [
+# 'planetoid',
+# 'ipynb',
+# 'localsearch',
+# 'mustache',
+# ]
+
# Put in global_context things you want available on all your templates.
# It can be anything, data, functions, modules, etc.
diff --git a/nikola/data/themes/default/assets/css/code.css b/nikola/data/themes/default/assets/css/code.css
deleted file mode 100644
index b1d7ace..0000000
--- a/nikola/data/themes/default/assets/css/code.css
+++ /dev/null
@@ -1,62 +0,0 @@
-pre { word-break: pre; white-space: pre; word-wrap: pre; overflow: auto; max-width: 100%;}
-td.linenos { vertical-align: top; width: 4em;}
-div.code > pre, .code
-{ background: #f8f8f8; white-space: pre;}
-.code .c { color: #008800; font-style: italic } /* Comment */
-.code .err { border: 1px solid #FF0000 } /* Error */
-.code .k { color: #AA22FF; font-weight: bold } /* Keyword */
-.code .o { color: #666666 } /* Operator */
-.code .cm { color: #008800; font-style: italic } /* Comment.Multiline */
-.code .cp { color: #008800 } /* Comment.Preproc */
-.code .c1 { color: #008800; font-style: italic } /* Comment.Single */
-.code .cs { color: #008800; font-weight: bold } /* Comment.Special */
-.code .gd { color: #A00000 } /* Generic.Deleted */
-.code .ge { font-style: italic } /* Generic.Emph */
-.code .gr { color: #FF0000 } /* Generic.Error */
-.code .gh { color: #000080; font-weight: bold } /* Generic.Heading */
-.code .gi { color: #00A000 } /* Generic.Inserted */
-.code .go { color: #808080 } /* Generic.Output */
-.code .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
-.code .gs { font-weight: bold } /* Generic.Strong */
-.code .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
-.code .gt { color: #0040D0 } /* Generic.Traceback */
-.code .kc { color: #AA22FF; font-weight: bold } /* Keyword.Constant */
-.code .kd { color: #AA22FF; font-weight: bold } /* Keyword.Declaration */
-.code .kp { color: #AA22FF } /* Keyword.Pseudo */
-.code .kr { color: #AA22FF; font-weight: bold } /* Keyword.Reserved */
-.code .kt { color: #AA22FF; font-weight: bold } /* Keyword.Type */
-.code .m { color: #666666 } /* Literal.Number */
-.code .s { color: #BB4444 } /* Literal.String */
-.code .na { color: #BB4444 } /* Name.Attribute */
-.code .nb { color: #AA22FF } /* Name.Builtin */
-.code .nc { color: #0000FF } /* Name.Class */
-.code .no { color: #880000 } /* Name.Constant */
-.code .nd { color: #AA22FF } /* Name.Decorator */
-.code .ni { color: #999999; font-weight: bold } /* Name.Entity */
-.code .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
-.code .nf { color: #00A000 } /* Name.Function */
-.code .nl { color: #A0A000 } /* Name.Label */
-.code .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
-.code .nt { color: #008000; font-weight: bold } /* Name.Tag */
-.code .nv { color: #B8860B } /* Name.Variable */
-.code .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
-.code .mf { color: #666666 } /* Literal.Number.Float */
-.code .mh { color: #666666 } /* Literal.Number.Hex */
-.code .mi { color: #666666 } /* Literal.Number.Integer */
-.code .mo { color: #666666 } /* Literal.Number.Oct */
-.code .sb { color: #BB4444 } /* Literal.String.Backtick */
-.code .sc { color: #BB4444 } /* Literal.String.Char */
-.code .sd { color: #BB4444; font-style: italic } /* Literal.String.Doc */
-.code .s2 { color: #BB4444 } /* Literal.String.Double */
-.code .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
-.code .sh { color: #BB4444 } /* Literal.String.Heredoc */
-.code .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
-.code .sx { color: #008000 } /* Literal.String.Other */
-.code .sr { color: #BB6688 } /* Literal.String.Regex */
-.code .s1 { color: #BB4444 } /* Literal.String.Single */
-.code .ss { color: #B8860B } /* Literal.String.Symbol */
-.code .bp { color: #AA22FF } /* Name.Builtin.Pseudo */
-.code .vc { color: #B8860B } /* Name.Variable.Class */
-.code .vg { color: #B8860B } /* Name.Variable.Global */
-.code .vi { color: #B8860B } /* Name.Variable.Instance */
-.code .il { color: #666666 } /* Literal.Number.Integer.Long */
diff --git a/nikola/data/themes/default/assets/css/slides.css b/nikola/data/themes/default/assets/css/slides.css
deleted file mode 100644
index 272c83e..0000000
--- a/nikola/data/themes/default/assets/css/slides.css
+++ /dev/null
@@ -1,11 +0,0 @@
-.slides_container {
- display: block;
- margin-left: auto;
- margin-right: auto;
- max-width: 80%;
- width: 400px;
- height: 300px;
-}
-.slide-current {
- font-weight: bold;
-}
diff --git a/nikola/data/themes/default/assets/css/theme.css b/nikola/data/themes/default/assets/css/theme.css
index 0523ce9..08a71f3 100644
--- a/nikola/data/themes/default/assets/css/theme.css
+++ b/nikola/data/themes/default/assets/css/theme.css
@@ -60,3 +60,14 @@ blockquote p, blockquote {
font-weight: 300;
line-height: 1.25;
}
+
+ul.bricks > li {
+ display: inline;
+ background-color: lightblue;
+ padding: 8px;
+ border-radius: 5px;
+ line-height: 3;
+ white-space:nowrap;
+ margin: 3px;
+}
+
diff --git a/nikola/data/themes/default/assets/js/slides.jquery.js b/nikola/data/themes/default/assets/js/slides.jquery.js
deleted file mode 100755
index f2e09c8..0000000
--- a/nikola/data/themes/default/assets/js/slides.jquery.js
+++ /dev/null
@@ -1,555 +0,0 @@
-/*
-* Slides, A Slideshow Plugin for jQuery
-* Intructions: http://slidesjs.com
-* By: Nathan Searles, http://nathansearles.com
-* Version: 1.1.9
-* Updated: September 5th, 2011
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-(function($){
- $.fn.slides = function( option ) {
- // override defaults with specified option
- option = $.extend( {}, $.fn.slides.option, option );
-
- return this.each(function(){
- // wrap slides in control container, make sure slides are block level
- $('.' + option.container, $(this)).children().wrapAll('<div class="slides_control"/>');
-
- var elem = $(this),
- control = $('.slides_control',elem),
- total = control.children().size(),
- width = control.children().outerWidth(),
- height = control.children().outerHeight(),
- start = option.start - 1,
- effect = option.effect.indexOf(',') < 0 ? option.effect : option.effect.replace(' ', '').split(',')[0],
- paginationEffect = option.effect.indexOf(',') < 0 ? effect : option.effect.replace(' ', '').split(',')[1],
- next = 0, prev = 0, number = 0, current = 0, loaded, active, clicked, position, direction, imageParent, pauseTimeout, playInterval;
-
- // is there only one slide?
- if (total < 2) {
- // Fade in .slides_container
- $('.' + option.container, $(this)).fadeIn(option.fadeSpeed, option.fadeEasing, function(){
- // let the script know everything is loaded
- loaded = true;
- // call the loaded funciton
- option.slidesLoaded();
- });
- // Hide the next/previous buttons
- $('.' + option.next + ', .' + option.prev).fadeOut(0);
- return false;
- }
-
- // animate slides
- function animate(direction, effect, clicked) {
- if (!active && loaded) {
- active = true;
- // start of animation
- option.animationStart(current + 1);
- switch(direction) {
- case 'next':
- // change current slide to previous
- prev = current;
- // get next from current + 1
- next = current + 1;
- // if last slide, set next to first slide
- next = total === next ? 0 : next;
- // set position of next slide to right of previous
- position = width*2;
- // distance to slide based on width of slides
- direction = -width*2;
- // store new current slide
- current = next;
- break;
- case 'prev':
- // change current slide to previous
- prev = current;
- // get next from current - 1
- next = current - 1;
- // if first slide, set next to last slide
- next = next === -1 ? total-1 : next;
- // set position of next slide to left of previous
- position = 0;
- // distance to slide based on width of slides
- direction = 0;
- // store new current slide
- current = next;
- break;
- case 'pagination':
- // get next from pagination item clicked, convert to number
- next = parseInt(clicked,10);
- // get previous from pagination item with class of current
- prev = $('.' + option.paginationClass + ' li.'+ option.currentClass +' a', elem).attr('href').match('[^#/]+$');
- // if next is greater then previous set position of next slide to right of previous
- if (next > prev) {
- position = width*2;
- direction = -width*2;
- } else {
- // if next is less then previous set position of next slide to left of previous
- position = 0;
- direction = 0;
- }
- // store new current slide
- current = next;
- break;
- }
-
- // fade animation
- if (effect === 'fade') {
- // fade animation with crossfade
- if (option.crossfade) {
- // put hidden next above current
- control.children(':eq('+ next +')', elem).css({
- zIndex: 10
- // fade in next
- }).fadeIn(option.fadeSpeed, option.fadeEasing, function(){
- if (option.autoHeight) {
- // animate container to height of next
- control.animate({
- height: control.children(':eq('+ next +')', elem).outerHeight()
- }, option.autoHeightSpeed, function(){
- // hide previous
- control.children(':eq('+ prev +')', elem).css({
- display: 'none',
- zIndex: 0
- });
- // reset z index
- control.children(':eq('+ next +')', elem).css({
- zIndex: 0
- });
- // end of animation
- option.animationComplete(next + 1);
- active = false;
- });
- } else {
- // hide previous
- control.children(':eq('+ prev +')', elem).css({
- display: 'none',
- zIndex: 0
- });
- // reset zindex
- control.children(':eq('+ next +')', elem).css({
- zIndex: 0
- });
- // end of animation
- option.animationComplete(next + 1);
- active = false;
- }
- });
- } else {
- // fade animation with no crossfade
- control.children(':eq('+ prev +')', elem).fadeOut(option.fadeSpeed, option.fadeEasing, function(){
- // animate to new height
- if (option.autoHeight) {
- control.animate({
- // animate container to height of next
- height: control.children(':eq('+ next +')', elem).outerHeight()
- }, option.autoHeightSpeed,
- // fade in next slide
- function(){
- control.children(':eq('+ next +')', elem).fadeIn(option.fadeSpeed, option.fadeEasing);
- });
- } else {
- // if fixed height
- control.children(':eq('+ next +')', elem).fadeIn(option.fadeSpeed, option.fadeEasing, function(){
- // fix font rendering in ie, lame
- if($.browser.msie) {
- $(this).get(0).style.removeAttribute('filter');
- }
- });
- }
- // end of animation
- option.animationComplete(next + 1);
- active = false;
- });
- }
- // slide animation
- } else {
- // move next slide to right of previous
- control.children(':eq('+ next +')').css({
- left: position,
- display: 'block'
- });
- // animate to new height
- if (option.autoHeight) {
- control.animate({
- left: direction,
- height: control.children(':eq('+ next +')').outerHeight()
- },option.slideSpeed, option.slideEasing, function(){
- control.css({
- left: -width
- });
- control.children(':eq('+ next +')').css({
- left: width,
- zIndex: 5
- });
- // reset previous slide
- control.children(':eq('+ prev +')').css({
- left: width,
- display: 'none',
- zIndex: 0
- });
- // end of animation
- option.animationComplete(next + 1);
- active = false;
- });
- // if fixed height
- } else {
- // animate control
- control.animate({
- left: direction
- },option.slideSpeed, option.slideEasing, function(){
- // after animation reset control position
- control.css({
- left: -width
- });
- // reset and show next
- control.children(':eq('+ next +')').css({
- left: width,
- zIndex: 5
- });
- // reset previous slide
- control.children(':eq('+ prev +')').css({
- left: width,
- display: 'none',
- zIndex: 0
- });
- // end of animation
- option.animationComplete(next + 1);
- active = false;
- });
- }
- }
- // set current state for pagination
- if (option.pagination) {
- // remove current class from all
- $('.'+ option.paginationClass +' li.' + option.currentClass, elem).removeClass(option.currentClass);
- // add current class to next
- $('.' + option.paginationClass + ' li:eq('+ next +')', elem).addClass(option.currentClass);
- }
- }
- } // end animate function
-
- function stop() {
- // clear interval from stored id
- clearInterval(elem.data('interval'));
- }
-
- function pause() {
- if (option.pause) {
- // clear timeout and interval
- clearTimeout(elem.data('pause'));
- clearInterval(elem.data('interval'));
- // pause slide show for option.pause amount
- pauseTimeout = setTimeout(function() {
- // clear pause timeout
- clearTimeout(elem.data('pause'));
- // start play interval after pause
- playInterval = setInterval( function(){
- animate("next", effect);
- },option.play);
- // store play interval
- elem.data('interval',playInterval);
- },option.pause);
- // store pause interval
- elem.data('pause',pauseTimeout);
- } else {
- // if no pause, just stop
- stop();
- }
- }
-
- // 2 or more slides required
- if (total < 2) {
- return;
- }
-
- // error corection for start slide
- if (start < 0) {
- start = 0;
- }
-
- if (start > total) {
- start = total - 1;
- }
-
- // change current based on start option number
- if (option.start) {
- current = start;
- }
-
- // randomizes slide order
- if (option.randomize) {
- control.randomize();
- }
-
- // make sure overflow is hidden, width is set
- $('.' + option.container, elem).css({
- overflow: 'hidden',
- // fix for ie
- position: 'relative'
- });
-
- // set css for slides
- control.children().css({
- position: 'absolute',
- top: 0,
- left: control.children().outerWidth(),
- zIndex: 0,
- display: 'none'
- });
-
- // set css for control div
- control.css({
- position: 'relative',
- // size of control 3 x slide width
- width: (width * 3),
- // set height to slide height
- height: height,
- // center control to slide
- left: -width
- });
-
- // show slides
- $('.' + option.container, elem).css({
- display: 'block'
- });
-
- // if autoHeight true, get and set height of first slide
- if (option.autoHeight) {
- control.children().css({
- height: 'auto'
- });
- control.animate({
- height: control.children(':eq('+ start +')').outerHeight()
- },option.autoHeightSpeed);
- }
-
- // checks if image is loaded
- if (option.preload && control.find('img:eq(' + start + ')').length) {
- // adds preload image
- $('.' + option.container, elem).css({
- background: 'url(' + option.preloadImage + ') no-repeat 50% 50%'
- });
-
- // gets image src, with cache buster
- var img = control.find('img:eq(' + start + ')').attr('src') + '?' + (new Date()).getTime();
-
- // check if the image has a parent
- if ($('img', elem).parent().attr('class') != 'slides_control') {
- // If image has parent, get tag name
- imageParent = control.children(':eq(0)')[0].tagName.toLowerCase();
- } else {
- // Image doesn't have parent, use image tag name
- imageParent = control.find('img:eq(' + start + ')');
- }
-
- // checks if image is loaded
- control.find('img:eq(' + start + ')').attr('src', img).load(function() {
- // once image is fully loaded, fade in
- control.find(imageParent + ':eq(' + start + ')').fadeIn(option.fadeSpeed, option.fadeEasing, function(){
- $(this).css({
- zIndex: 5
- });
- // removes preload image
- $('.' + option.container, elem).css({
- background: ''
- });
- // let the script know everything is loaded
- loaded = true;
- // call the loaded funciton
- option.slidesLoaded();
- });
- });
- } else {
- // if no preloader fade in start slide
- control.children(':eq(' + start + ')').fadeIn(option.fadeSpeed, option.fadeEasing, function(){
- // let the script know everything is loaded
- loaded = true;
- // call the loaded funciton
- option.slidesLoaded();
- });
- }
-
- // click slide for next
- if (option.bigTarget) {
- // set cursor to pointer
- control.children().css({
- cursor: 'pointer'
- });
- // click handler
- control.children().click(function(){
- // animate to next on slide click
- animate('next', effect);
- return false;
- });
- }
-
- // pause on mouseover
- if (option.hoverPause && option.play) {
- control.bind('mouseover',function(){
- // on mouse over stop
- stop();
- });
- control.bind('mouseleave',function(){
- // on mouse leave start pause timeout
- pause();
- });
- }
-
- // generate next/prev buttons
- if (option.generateNextPrev) {
- $('.' + option.container, elem).after('<a href="#" class="'+ option.prev +'">Prev</a>');
- $('.' + option.prev, elem).after('<a href="#" class="'+ option.next +'">Next</a>');
- }
-
- // next button
- $('.' + option.next ,elem).click(function(e){
- e.preventDefault();
- if (option.play) {
- pause();
- }
- animate('next', effect);
- });
-
- // previous button
- $('.' + option.prev, elem).click(function(e){
- e.preventDefault();
- if (option.play) {
- pause();
- }
- animate('prev', effect);
- });
-
- // generate pagination
- if (option.generatePagination) {
- // create unordered list
- if (option.prependPagination) {
- elem.prepend('<ul class='+ option.paginationClass +'></ul>');
- } else {
- elem.append('<ul class='+ option.paginationClass +'></ul>');
- }
- // for each slide create a list item and link
- control.children().each(function(){
- $('.' + option.paginationClass, elem).append('<li><a href="#'+ number +'">'+ (number+1) +'</a></li>');
- number++;
- });
- } else {
- // if pagination exists, add href w/ value of item number to links
- $('.' + option.paginationClass + ' li a', elem).each(function(){
- $(this).attr('href', '#' + number);
- number++;
- });
- }
-
- // add current class to start slide pagination
- $('.' + option.paginationClass + ' li:eq('+ start +')', elem).addClass(option.currentClass);
-
- // click handling
- $('.' + option.paginationClass + ' li a', elem ).click(function(){
- // pause slideshow
- if (option.play) {
- pause();
- }
- // get clicked, pass to animate function
- clicked = $(this).attr('href').match('[^#/]+$');
- // if current slide equals clicked, don't do anything
- if (current != clicked) {
- animate('pagination', paginationEffect, clicked);
- }
- return false;
- });
-
- // click handling
- $('a.link', elem).click(function(){
- // pause slideshow
- if (option.play) {
- pause();
- }
- // get clicked, pass to animate function
- clicked = $(this).attr('href').match('[^#/]+$') - 1;
- // if current slide equals clicked, don't do anything
- if (current != clicked) {
- animate('pagination', paginationEffect, clicked);
- }
- return false;
- });
-
- if (option.play) {
- // set interval
- playInterval = setInterval(function() {
- animate('next', effect);
- }, option.play);
- // store interval id
- elem.data('interval',playInterval);
- }
- });
- };
-
- // default options
- $.fn.slides.option = {
- preload: false, // boolean, Set true to preload images in an image based slideshow
- preloadImage: '/img/loading.gif', // string, Name and location of loading image for preloader. Default is "/img/loading.gif"
- container: 'slides_container', // string, Class name for slides container. Default is "slides_container"
- generateNextPrev: false, // boolean, Auto generate next/prev buttons
- next: 'next', // string, Class name for next button
- prev: 'prev', // string, Class name for previous button
- pagination: true, // boolean, If you're not using pagination you can set to false, but don't have to
- generatePagination: true, // boolean, Auto generate pagination
- prependPagination: false, // boolean, prepend pagination
- paginationClass: 'pagination', // string, Class name for pagination
- currentClass: 'current', // string, Class name for current class
- fadeSpeed: 350, // number, Set the speed of the fading animation in milliseconds
- fadeEasing: '', // string, must load jQuery's easing plugin before http://gsgd.co.uk/sandbox/jquery/easing/
- slideSpeed: 350, // number, Set the speed of the sliding animation in milliseconds
- slideEasing: '', // string, must load jQuery's easing plugin before http://gsgd.co.uk/sandbox/jquery/easing/
- start: 1, // number, Set the speed of the sliding animation in milliseconds
- effect: 'slide', // string, '[next/prev], [pagination]', e.g. 'slide, fade' or simply 'fade' for both
- crossfade: false, // boolean, Crossfade images in a image based slideshow
- randomize: false, // boolean, Set to true to randomize slides
- play: 0, // number, Autoplay slideshow, a positive number will set to true and be the time between slide animation in milliseconds
- pause: 0, // number, Pause slideshow on click of next/prev or pagination. A positive number will set to true and be the time of pause in milliseconds
- hoverPause: false, // boolean, Set to true and hovering over slideshow will pause it
- autoHeight: false, // boolean, Set to true to auto adjust height
- autoHeightSpeed: 350, // number, Set auto height animation time in milliseconds
- bigTarget: false, // boolean, Set to true and the whole slide will link to next slide on click
- animationStart: function(){}, // Function called at the start of animation
- animationComplete: function(){}, // Function called at the completion of animation
- slidesLoaded: function() {} // Function is called when slides is fully loaded
- };
-
- // Randomize slide order on load
- $.fn.randomize = function(callback) {
- function randomizeOrder() { return(Math.round(Math.random())-0.5); }
- return($(this).each(function() {
- var $this = $(this);
- var $children = $this.children();
- var childCount = $children.length;
- if (childCount > 1) {
- $children.hide();
- var indices = [];
- for (i=0;i<childCount;i++) { indices[indices.length] = i; }
- indices = indices.sort(randomizeOrder);
- $.each(indices,function(j,k) {
- var $child = $children.eq(k);
- var $clone = $child.clone(true);
- $clone.show().appendTo($this);
- if (callback !== undefined) {
- callback($child, $clone);
- }
- $child.remove();
- });
- }
- }));
- };
-})(jQuery); \ No newline at end of file
diff --git a/nikola/data/themes/default/bundles b/nikola/data/themes/default/bundles
index 35af9c0..2b81ea8 100644
--- a/nikola/data/themes/default/bundles
+++ b/nikola/data/themes/default/bundles
@@ -1,4 +1,4 @@
-assets/css/all-nocdn.css=bootstrap.css,bootstrap-responsive.css,rst.css,code.css,colorbox.css,slides.css,theme.css,custom.css
-assets/css/all.css=rst.css,code.css,colorbox.css,slides.css,theme.css,custom.css
-assets/js/all-nocdn.js=jquery-1.7.2.min.js,bootstrap.min.js,jquery.colorbox-min.js,slides.min.jquery.js
-assets/js/all.js=jquery.colorbox-min.js,slides.min.jquery.js
+assets/css/all-nocdn.css=bootstrap.css,bootstrap-responsive.css,rst.css,code.css,colorbox.css,theme.css,custom.css
+assets/css/all.css=rst.css,code.css,colorbox.css,theme.css,custom.css
+assets/js/all-nocdn.js=jquery-1.7.2.min.js,bootstrap.min.js,jquery.colorbox-min.js
+assets/js/all.js=jquery.colorbox-min.js
diff --git a/nikola/data/themes/default/messages/messages_ca.py b/nikola/data/themes/default/messages/messages_ca.py
index 8e7186f..a709f1b 100644
--- a/nikola/data/themes/default/messages/messages_ca.py
+++ b/nikola/data/themes/default/messages/messages_ca.py
@@ -2,21 +2,22 @@
from __future__ import unicode_literals
MESSAGES = {
- "LANGUAGE": "Català",
- "Posts for year %s": "Entrades de l'any %s",
- "Archive": "Arxiu",
- "Posts about %s": "Entrades sobre %s",
- "Tags": "Etiquetes",
"Also available in": "També disponibles en",
+ "Archive": "Arxiu",
+ "LANGUAGE": "Català",
"More posts about": "Més entrades sobre",
- "Posted": "Publicat",
- "Original site": "Lloc original",
- "Read in English": "Llegeix-ho en català",
- "Older posts": "Entrades anteriors",
"Newer posts": "Entrades posteriors",
- "Previous post": "Entrada anterior",
"Next post": "Entrada següent",
- "old posts page %d": "entrades antigues pàgina %d",
+ "Older posts": "Entrades anteriors",
+ "Original site": "Lloc original",
+ "Posted": "Publicat",
+ "Posts about %s": "Entrades sobre %s",
+ "Posts for year %s": "Entrades de l'any %s",
+ "Posts for {month} {year}": "",
+ "Previous post": "Entrada anterior",
+ "Read in English": "Llegeix-ho en català",
"Read more": "Llegeix-ne més",
"Source": "Codi",
+ "Tags": "Etiquetes",
+ "old posts page %d": "entrades antigues pàgina %d",
}
diff --git a/nikola/data/themes/default/messages/messages_de.py b/nikola/data/themes/default/messages/messages_de.py
index 5da3b2b..57c784f 100644
--- a/nikola/data/themes/default/messages/messages_de.py
+++ b/nikola/data/themes/default/messages/messages_de.py
@@ -2,21 +2,22 @@
from __future__ import unicode_literals
MESSAGES = {
- "LANGUAGE": "Deutsch",
- "Posts for year %s": "Einträge aus dem Jahr %s",
- "Archive": "Archiv",
- "Posts about %s": "Einträge über %s",
- "Tags": "Tags",
"Also available in": "Auch verfügbar in",
+ "Archive": "Archiv",
+ "LANGUAGE": "Deutsch",
"More posts about": "Weitere Einträge über",
- "Posted": "Veröffentlicht",
- "Original site": "Original-Seite",
- "Read in English": "Auf Deutsch lesen",
- "Older posts": "Ältere Einträge",
"Newer posts": "Neuere Einträge",
- "Previous post": "Vorheriger Eintrag",
"Next post": "Nächster Eintrag",
- "Source": "Source",
+ "Older posts": "Ältere Einträge",
+ "Original site": "Original-Seite",
+ "Posted": "Veröffentlicht",
+ "Posts about %s": "Einträge über %s",
+ "Posts for year %s": "Einträge aus dem Jahr %s",
+ "Posts for {month} {year}": "Einträge für {month} {year}",
+ "Previous post": "Vorheriger Eintrag",
+ "Read in English": "Auf Deutsch lesen",
"Read more": "Weiterlesen",
- "old posts page %d": "Vorherige Einträge %d"
+ "Source": "Source",
+ "Tags": "Tags",
+ "old posts page %d": "Vorherige Einträge %d",
}
diff --git a/nikola/data/themes/default/messages/messages_el.py b/nikola/data/themes/default/messages/messages_el.py
new file mode 100644
index 0000000..a00f88f
--- /dev/null
+++ b/nikola/data/themes/default/messages/messages_el.py
@@ -0,0 +1,23 @@
+# -*- encoding:utf-8 -*-
+from __future__ import unicode_literals
+
+MESSAGES = {
+ "Also available in": "Διαθέσιμο και στα",
+ "Archive": "Αρχείο",
+ "LANGUAGE": "Ελληνικά",
+ "More posts about": "Περισσότερες αναρτήσεις για",
+ "Newer posts": "Νεότερες αναρτήσεις",
+ "Next post": "Επόμενη ανάρτηση",
+ "Older posts": "Παλαιότερες αναρτήσεις",
+ "Original site": "Ιστοσελίδα αρχικής ανάρτησης",
+ "Posted": "Αναρτήθηκε",
+ "Posts about %s": "Αναρτήσεις για %s",
+ "Posts for year %s": "Αναρτήσεις για το έτος %s",
+ "Posts for {month} {year}": "",
+ "Previous post": "Προηγούμενη ανάρτηση",
+ "Read in English": "Διαβάστε στα Ελληνικά",
+ "Read more": "Διαβάστε περισσότερα",
+ "Source": "Πηγαίος κώδικας",
+ "Tags": "Ετικέτες",
+ "old posts page %d": "σελίδα παλαιότερων αναρτήσεων %d",
+}
diff --git a/nikola/data/themes/default/messages/messages_en.py b/nikola/data/themes/default/messages/messages_en.py
index 9fc77ef..982b654 100644
--- a/nikola/data/themes/default/messages/messages_en.py
+++ b/nikola/data/themes/default/messages/messages_en.py
@@ -2,21 +2,22 @@
from __future__ import unicode_literals
MESSAGES = {
- "LANGUAGE": "English",
- "Posts for year %s": "Posts for year %s",
- "Archive": "Archive",
- "Posts about %s": "Posts about %s",
- "Tags": "Tags",
"Also available in": "Also available in",
+ "Archive": "Archive",
+ "LANGUAGE": "English",
"More posts about": "More posts about",
- "Posted": "Posted",
- "Original site": "Original site",
- "Read in English": "Read in English",
"Newer posts": "Newer posts",
+ "Next post": "Next post",
"Older posts": "Older posts",
+ "Original site": "Original site",
+ "Posted": "Posted",
+ "Posts about %s": "Posts about %s",
+ "Posts for year %s": "Posts for year %s",
+ "Posts for {month} {year}": "Posts for {month} {year}",
"Previous post": "Previous post",
- "Next post": "Next post",
- "old posts page %d": "old posts page %d",
+ "Read in English": "Read in English",
"Read more": "Read more",
"Source": "Source",
+ "Tags": "Tags",
+ "old posts page %d": "old posts page %d",
}
diff --git a/nikola/data/themes/default/messages/messages_es.py b/nikola/data/themes/default/messages/messages_es.py
index f17f058..4b73d47 100644
--- a/nikola/data/themes/default/messages/messages_es.py
+++ b/nikola/data/themes/default/messages/messages_es.py
@@ -2,21 +2,22 @@
from __future__ import unicode_literals
MESSAGES = {
- "LANGUAGE": "Español",
- "Posts for year %s": "Posts del año %s",
- "Archive": "Archivo",
- "Posts about %s": "Posts sobre %s",
- "Tags": "Tags",
"Also available in": "También disponible en",
+ "Archive": "Archivo",
+ "LANGUAGE": "Español",
"More posts about": "Más posts sobre",
- "Posted": "Publicado",
- "Original site": "Sitio original",
- "Read in English": "Leer en español",
- "Older posts": "Posts anteriores",
"Newer posts": "Posts posteriores",
- "Previous post": "Post anterior",
"Next post": "Siguiente post",
- "old posts page %d": "posts antiguos página %d",
+ "Older posts": "Posts anteriores",
+ "Original site": "Sitio original",
+ "Posted": "Publicado",
+ "Posts about %s": "Posts sobre %s",
+ "Posts for year %s": "Posts del año %s",
+ "Posts for {month} {year}": "Posts de {month} {year}",
+ "Previous post": "Post anterior",
+ "Read in English": "Leer en español",
"Read more": "Leer más",
"Source": "Código",
+ "Tags": "Tags",
+ "old posts page %d": "posts antiguos página %d",
}
diff --git a/nikola/data/themes/default/messages/messages_fr.py b/nikola/data/themes/default/messages/messages_fr.py
index 74eecb8..94e9f46 100644
--- a/nikola/data/themes/default/messages/messages_fr.py
+++ b/nikola/data/themes/default/messages/messages_fr.py
@@ -2,20 +2,22 @@
from __future__ import unicode_literals
MESSAGES = {
- "LANGUAGE": "Français",
- "Posts for year %s": "Billets de l'année %s",
- "Archive": "Archives",
- "Posts about %s": "Billets sur %s",
- "Tags": "Étiquettes",
"Also available in": "Disponible aussi en",
+ "Archive": "Archives",
+ "LANGUAGE": "Français",
"More posts about": "Plus de billets sur",
- "Posted": "Publié",
- "Original site": "Site d'origine",
- "Read in English": "Lire en français",
"Newer posts": "Billets récents",
+ "Next post": "Billet suivant",
"Older posts": "Anciens billets",
- "Previous post": "Previous post",
- "Next post": "Next post",
- "Read more": "Read more",
+ "Original site": "Site d'origine",
+ "Posted": "Publié",
+ "Posts about %s": "Billets sur %s",
+ "Posts for year %s": "Billets de l'année %s",
+ "Posts for {month} {year}": "Billets de {month} {year}",
+ "Previous post": "Billet précédent",
+ "Read in English": "Lire en français",
+ "Read more": "Lire la suite",
"Source": "Source",
+ "Tags": "Étiquettes",
+ "old posts page %d": "anciens billets page %d",
}
diff --git a/nikola/data/themes/default/messages/messages_it.py b/nikola/data/themes/default/messages/messages_it.py
index 42f4709..c41a20c 100644
--- a/nikola/data/themes/default/messages/messages_it.py
+++ b/nikola/data/themes/default/messages/messages_it.py
@@ -1,23 +1,23 @@
-# vim: set fileencoding=utf-8 :
+# -*- encoding:utf-8 -*-
from __future__ import unicode_literals
MESSAGES = {
- "LANGUAGE": "Italiano",
- "Posts for year %s": "Articoli per l'anno %s",
- "Archive": "Archivio",
- "Posts about %s": "Articoli su %s",
- "Tags": "Tags",
"Also available in": "Anche disponibile in",
+ "Archive": "Archivio",
+ "LANGUAGE": "Italiano",
"More posts about": "Altri articoli s",
- "Posted": "Pubblicato",
- "Original site": "Sito originale",
- "Read in English": "Leggi in italiano",
"Newer posts": "Articoli recenti",
- "Older posts": "Articoli più vecchi",
+ "Next post": "Articolo successivo",
"Older posts": "Articoli vecchi",
+ "Original site": "Sito originale",
+ "Posted": "Pubblicato",
+ "Posts about %s": "Articoli su %s",
+ "Posts for year %s": "Articoli per l'anno %s",
+ "Posts for {month} {year}": "",
"Previous post": "Articolo precedente",
- "Next post": "Articolo successivo",
- "old posts page %d": "pagina dei vecchi articoli %d",
+ "Read in English": "Leggi in italiano",
"Read more": "Espandi",
"Source": "Source",
+ "Tags": "Tags",
+ "old posts page %d": "pagina dei vecchi articoli %d",
}
diff --git a/nikola/data/themes/default/messages/messages_ja.py b/nikola/data/themes/default/messages/messages_ja.py
new file mode 100644
index 0000000..8dd1521
--- /dev/null
+++ b/nikola/data/themes/default/messages/messages_ja.py
@@ -0,0 +1,23 @@
+# -*- encoding:utf-8 -*-
+from __future__ import unicode_literals
+
+MESSAGES = {
+ "Also available in": "他の言語で読む",
+ "Archive": "過去の記事",
+ "LANGUAGE": "日本語",
+ "More posts about": "タグ",
+ "Newer posts": "新しい記事",
+ "Next post": "次の記事",
+ "Older posts": "過去の記事",
+ "Original site": "元のサイト",
+ "Posted": "投稿日時",
+ "Posts about %s": "%sについての記事",
+ "Posts for year %s": "%s年の記事",
+ "Posts for {month} {year}": "{year}年{month}月の記事",
+ "Previous post": "前の記事",
+ "Read in English": "日本語で読む",
+ "Read more": "続きを読む",
+ "Source": "ソース",
+ "Tags": "タグ",
+ "old posts page %d": "前の記事 %dページ目",
+}
diff --git a/nikola/data/themes/default/messages/messages_pl.py b/nikola/data/themes/default/messages/messages_pl.py
index 7172ebc..876b5f2 100644
--- a/nikola/data/themes/default/messages/messages_pl.py
+++ b/nikola/data/themes/default/messages/messages_pl.py
@@ -2,21 +2,22 @@
from __future__ import unicode_literals
MESSAGES = {
- "LANGUAGE": "polski",
- "Posts for year %s": "Posty z roku %s",
- "Archive": "Archiwum",
- "Posts about %s": "Posty o %s",
- "Tags": "Tags",
"Also available in": "Również dostępny w",
+ "Archive": "Archiwum",
+ "LANGUAGE": "polski",
"More posts about": "Więcej postów o",
- "Posted": "Opublikowany",
- "Original site": "Oryginalna strona",
- "Read in English": "Czytaj po polsku",
- "Older posts": "Starsze posty",
"Newer posts": "Nowsze posty",
- "Previous post": "Poprzedni post",
"Next post": "Następny post",
- "Source": "Źródło",
+ "Older posts": "Starsze posty",
+ "Original site": "Oryginalna strona",
+ "Posted": "Opublikowany",
+ "Posts about %s": "Posty o %s",
+ "Posts for year %s": "Posty z roku %s",
+ "Posts for {month} {year}": "Posty z {month} {year}",
+ "Previous post": "Poprzedni post",
+ "Read in English": "Czytaj po polsku",
"Read more": "Czytaj więcej",
- "old posts page %d": "stare posty, strona %d"
+ "Source": "Źródło",
+ "Tags": "Tags",
+ "old posts page %d": "stare posty, strona %d",
}
diff --git a/nikola/data/themes/default/messages/messages_pt-br.py b/nikola/data/themes/default/messages/messages_pt_br.py
index 183c577..12dafb5 100644
--- a/nikola/data/themes/default/messages/messages_pt-br.py
+++ b/nikola/data/themes/default/messages/messages_pt_br.py
@@ -2,21 +2,22 @@
from __future__ import unicode_literals
MESSAGES = {
- "LANGUAGE": "Português",
- "Posts for year %s": "Posts do ano %s",
- "Archive": "Arquivo",
- "Posts about %s": "Posts sobre %s",
- "Tags": "Tags",
"Also available in": "Também disponível em",
+ "Archive": "Arquivo",
+ "LANGUAGE": "Português",
"More posts about": "Mais posts sobre",
- "Posted": "Publicado",
- "Original site": "Site original",
- "Read in English": "Ler em português",
"Newer posts": "Posts mais recentes",
+ "Next post": "Próximo post",
"Older posts": "Posts mais antigos",
+ "Original site": "Site original",
+ "Posted": "Publicado",
+ "Posts about %s": "Posts sobre %s",
+ "Posts for year %s": "Posts do ano %s",
+ "Posts for {month} {year}": "Posts de {month} {year}",
"Previous post": "Post anterior",
- "Next post": "Próximo post",
- "old posts page %d": "Posts antigos página %d",
+ "Read in English": "Ler em português",
"Read more": "Leia mais",
"Source": "Código",
+ "Tags": "Tags",
+ "old posts page %d": "Posts antigos página %d",
}
diff --git a/nikola/data/themes/default/messages/messages_ru.py b/nikola/data/themes/default/messages/messages_ru.py
index 8a209ec..1d7e41c 100644
--- a/nikola/data/themes/default/messages/messages_ru.py
+++ b/nikola/data/themes/default/messages/messages_ru.py
@@ -2,21 +2,22 @@
from __future__ import unicode_literals
MESSAGES = {
- "LANGUAGE": "Русский",
- "Posts for year %s": "Записи за %s год",
- "Archive": "Архив",
- "Posts about %s": "Записи с тэгом %s:",
- "Tags": "Тэги",
"Also available in": "Также доступно в",
+ "Archive": "Архив",
+ "LANGUAGE": "Русский",
"More posts about": "Больше записей о",
- "Posted": "Опубликовано",
- "Original site": "Оригинальный сайт",
- "Read in English": "Прочесть по-русски",
- "Older posts": "Старые записи",
"Newer posts": "Новые записи",
- "Previous post": "Предыдущая запись",
"Next post": "Следующая запись",
- "old posts page %d": "страница со старыми записями %d",
+ "Older posts": "Старые записи",
+ "Original site": "Оригинальный сайт",
+ "Posted": "Опубликовано",
+ "Posts about %s": "Записи с тэгом %s:",
+ "Posts for year %s": "Записи за %s год",
+ "Posts for {month} {year}": "",
+ "Previous post": "Предыдущая запись",
+ "Read in English": "Прочесть по-русски",
"Read more": "Продолжить чтение",
"Source": "Source",
+ "Tags": "Тэги",
+ "old posts page %d": "страница со старыми записями %d",
}
diff --git a/nikola/data/themes/default/messages/messages_zh-cn.py b/nikola/data/themes/default/messages/messages_zh_cn.py
index 2f4b64e..aa69a96 100644
--- a/nikola/data/themes/default/messages/messages_zh-cn.py
+++ b/nikola/data/themes/default/messages/messages_zh_cn.py
@@ -1,22 +1,23 @@
-# -*- encoding:utf-8 -*-
-from __future__ import unicode_literals
-
-MESSAGES = {
- "LANGUAGE": "简体中文",
- "Posts for year %s": "%s年文章",
- "Archive": "文章存档",
- "Posts about %s": "文章分类:%s",
- "Tags": "标签",
- "Also available in": "其他语言版本",
- "More posts about": "更多相关文章:",
- "Posted": "发表于",
- "Original site": "原文地址",
- "Read in English": "中文版",
- "Older posts": "旧一篇",
- "Newer posts": "新一篇",
- "Previous post": "前一篇",
- "Next post": "后一篇",
- "old posts page %d": "旧文章页 %d",
- "Read more": "更多",
- "Source": "源代码",
-}
+# -*- encoding:utf-8 -*-
+from __future__ import unicode_literals
+
+MESSAGES = {
+ "Also available in": "其他语言版本",
+ "Archive": "文章存档",
+ "LANGUAGE": "简体中文",
+ "More posts about": "更多相关文章:",
+ "Newer posts": "新一篇",
+ "Next post": "后一篇",
+ "Older posts": "旧一篇",
+ "Original site": "原文地址",
+ "Posted": "发表于",
+ "Posts about %s": "文章分类:%s",
+ "Posts for year %s": "%s年文章",
+ "Posts for {month} {year}": "{year}年{month}月文章",
+ "Previous post": "前一篇",
+ "Read in English": "中文版",
+ "Read more": "更多",
+ "Source": "源代码",
+ "Tags": "标签",
+ "old posts page %d": "旧文章页 %d",
+}
diff --git a/nikola/data/themes/default/templates/base.tmpl b/nikola/data/themes/default/templates/base.tmpl
index c0935a2..72a4077 100644
--- a/nikola/data/themes/default/templates/base.tmpl
+++ b/nikola/data/themes/default/templates/base.tmpl
@@ -1,11 +1,13 @@
## -*- coding: utf-8 -*-
<%namespace file="base_helper.tmpl" import="*"/>
+${set_locale(lang)}
<!DOCTYPE html>
<html lang="${lang}">
<head>
${html_head()}
<%block name="extra_head">
</%block>
+ ${extra_head_data}
</head>
<body>
%if add_this_buttons:
@@ -22,7 +24,7 @@
<%block name="belowtitle">
%if len(translations) > 1:
<small>
- ${(messages[lang][u"Also available in"])}:&nbsp;
+ ${(messages("Also available in"))}:&nbsp;
${html_translations()}
</small>
%endif
@@ -52,7 +54,7 @@
</div>
</div>
</div>
- ${analytics}
${late_load_js()}
+ ${analytics}
<script type="text/javascript">jQuery("a.image-reference").colorbox({rel:"gal",maxWidth:"80%",maxHeight:"80%",scalePhotos:true});</script>
</body>
diff --git a/nikola/data/themes/default/templates/base_helper.tmpl b/nikola/data/themes/default/templates/base_helper.tmpl
index eb22905..a833c51 100644
--- a/nikola/data/themes/default/templates/base_helper.tmpl
+++ b/nikola/data/themes/default/templates/base_helper.tmpl
@@ -22,7 +22,6 @@
<link href="/assets/css/rst.css" rel="stylesheet" type="text/css">
<link href="/assets/css/code.css" rel="stylesheet" type="text/css">
<link href="/assets/css/colorbox.css" rel="stylesheet" type="text/css"/>
- <link href="/assets/css/slides.css" rel="stylesheet" type="text/css"/>
<link href="/assets/css/theme.css" rel="stylesheet" type="text/css"/>
%if has_custom_css:
<link href="/assets/css/custom.css" rel="stylesheet" type="text/css">
@@ -34,9 +33,13 @@
%if rss_link:
${rss_link}
%else:
- %for language in translations:
- <link rel="alternate" type="application/rss+xml" title="RSS (${language})" href="${_link('rss', None, language)}">
- %endfor
+ %if len(translations) > 1:
+ %for language in translations:
+ <link rel="alternate" type="application/rss+xml" title="RSS (${language})" href="${_link('rss', None, language)}">
+ %endfor
+ %else:
+ <link rel="alternate" type="application/rss+xml" title="RSS" href="${_link('rss', None)}">
+ %endif
%endif
%if favicons:
%for name, file, size in favicons:
@@ -63,7 +66,6 @@
<script src="/assets/js/bootstrap.min.js" type="text/javascript"></script>
%endif
<script src="/assets/js/jquery.colorbox-min.js" type="text/javascript"></script>
- <script src="/assets/js/slides.min.jquery.js" type="text/javascript"></script>
%endif
</%def>
@@ -98,7 +100,7 @@
<%def name="html_translations()">
%for langname in translations.keys():
%if langname != lang:
- <a href="${_link("index", None, langname)}">${messages[langname]["LANGUAGE"]}</a>
+ <a href="${_link("index", None, langname)}">${messages("LANGUAGE", langname)}</a>
%endif
%endfor
</%def>
diff --git a/nikola/data/themes/default/templates/disqus_helper.tmpl b/nikola/data/themes/default/templates/disqus_helper.tmpl
index 674e20e..4c60f85 100644
--- a/nikola/data/themes/default/templates/disqus_helper.tmpl
+++ b/nikola/data/themes/default/templates/disqus_helper.tmpl
@@ -1,6 +1,9 @@
## -*- coding: utf-8 -*-
<%!
import json
+ translations = {
+ 'es': 'es_ES',
+ }
%>
<%def name="html_disqus(url, title, identifier)">
%if disqus_forum:
@@ -12,8 +15,8 @@
%endif
var disqus_title=${json.dumps(title)};
var disqus_identifier="${identifier}";
- var disqus_config = function () {
- this.language = "${lang}";
+ var disqus_config = function () {
+ this.language = "${translations.get(lang, lang)}";
};
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
diff --git a/nikola/data/themes/default/templates/index.tmpl b/nikola/data/themes/default/templates/index.tmpl
index 4f66867..b49e764 100644
--- a/nikola/data/themes/default/templates/index.tmpl
+++ b/nikola/data/themes/default/templates/index.tmpl
@@ -5,13 +5,15 @@
<%block name="content">
% for post in posts:
<div class="postbox">
- <h1><a href="${post.permalink(lang)}">${post.title(lang)}</a>
+ <h1><a href="${post.permalink()}">${post.title()}</a>
<small>&nbsp;&nbsp;
- ${messages[lang]["Posted"]}: <time class="published" datetime="${post.date.isoformat()}">${post.date.strftime(date_format)}</time>
+ ${messages("Posted")}: <time class="published" datetime="${post.date.isoformat()}">${post.formatted_date(date_format)}</time>
</small></h1>
<hr>
- ${post.text(lang, index_teasers)}
- ${disqus.html_disqus_link(post.permalink()+"#disqus_thread", post.base_path)}
+ ${post.text(teaser_only=index_teasers)}
+ % if not post.meta('nocomments'):
+ ${disqus.html_disqus_link(post.permalink()+"#disqus_thread", post.base_path)}
+ % endif
</div>
% endfor
${helper.html_pager()}
diff --git a/nikola/data/themes/default/templates/index_helper.tmpl b/nikola/data/themes/default/templates/index_helper.tmpl
index 151b4d2..7859972 100644
--- a/nikola/data/themes/default/templates/index_helper.tmpl
+++ b/nikola/data/themes/default/templates/index_helper.tmpl
@@ -4,11 +4,11 @@
<ul class="pager">
%if prevlink:
<li class="previous">
- <a href="${prevlink}">&larr; ${messages[lang]["Newer posts"]}</a>
+ <a href="${prevlink}">&larr; ${messages("Newer posts")}</a>
%endif
%if nextlink:
<li class="next">
- <a href="${nextlink}">${messages[lang]["Older posts"]} &rarr;</a>
+ <a href="${nextlink}">${messages("Older posts")} &rarr;</a>
%endif
</ul>
</div>
diff --git a/nikola/data/themes/default/templates/list_post.tmpl b/nikola/data/themes/default/templates/list_post.tmpl
index 1a1cdee..f0e159d 100644
--- a/nikola/data/themes/default/templates/list_post.tmpl
+++ b/nikola/data/themes/default/templates/list_post.tmpl
@@ -6,7 +6,7 @@
<h1>${title}</h1>
<ul class="unstyled">
% for post in posts:
- <li><a href="${post.permalink(lang)}">[${post.date.strftime(date_format)}] ${post.title(lang)}</a>
+ <li><a href="${post.permalink()}">[${post.formatted_date(date_format)}] ${post.title()}</a>
% endfor
</ul>
</div>
diff --git a/nikola/data/themes/default/templates/post.tmpl b/nikola/data/themes/default/templates/post.tmpl
index 22d8a58..f9e24d2 100644
--- a/nikola/data/themes/default/templates/post.tmpl
+++ b/nikola/data/themes/default/templates/post.tmpl
@@ -10,20 +10,24 @@ ${helper.twitter_card_information(post)}
${helper.html_title()}
<hr>
<small>
- ${messages[lang]["Posted"]}: <time class="published" datetime="${post.date.isoformat()}">${post.date.strftime(date_format)}</time>
+ ${messages("Posted")}: <time class="published" datetime="${post.date.isoformat()}">${post.formatted_date(date_format)}</time>
${helper.html_translations(post)}
${helper.html_tags(post)}
</small>
<hr>
- ${post.text(lang)}
+ ${post.text()}
${helper.html_pager(post)}
- ${disqus.html_disqus(post.permalink(absolute=True), post.title(lang), post.base_path)}
+ % if not post.meta('nocomments'):
+ ${disqus.html_disqus(post.permalink(absolute=True), post.title(), post.base_path)}
+ % endif
${helper.mathjax_script(post)}
</div>
</%block>
<%block name="sourcelink">
+% if not post.meta('password'):
<li>
- <a href="${post.pagenames[lang]+post.source_ext()}" id="sourcelink">${messages[lang]["Source"]}</a>
+ <a href="${post.meta('slug')+post.source_ext()}" id="sourcelink">${messages("Source")}</a>
</li>
+% endif
</%block>
diff --git a/nikola/data/themes/default/templates/post_helper.tmpl b/nikola/data/themes/default/templates/post_helper.tmpl
index 911a831..cce0ecf 100644
--- a/nikola/data/themes/default/templates/post_helper.tmpl
+++ b/nikola/data/themes/default/templates/post_helper.tmpl
@@ -2,7 +2,7 @@
<%def name="html_title()">
<h1>${title}</h1>
% if link:
- <p><a href='${link}'>${messages[lang]["Original site"]}</a></p>
+ <p><a href='${link}'>${messages("Original site")}</a></p>
% endif
</%def>
@@ -12,7 +12,7 @@
%for langname in translations.keys():
%if langname != lang and post.is_translation_available(langname):
&nbsp;&nbsp;|&nbsp;&nbsp;
- <a href="${post.permalink(langname)}">${messages[langname]["Read in English"]}</a>
+ <a href="${post.permalink(langname)}">${messages("Read in English", langname)}</a>
%endif
%endfor
%endif
@@ -21,9 +21,9 @@
<%def name="html_tags(post)">
%if post.tags:
- &nbsp;&nbsp;|&nbsp;&nbsp;${messages[lang]["More posts about"]}
+ &nbsp;&nbsp;|&nbsp;&nbsp;${messages("More posts about")}
%for tag in post.tags:
- <a class="tag" href="${_link('tag', tag, lang)}"><span class="badge badge-info">${tag}</span></a>
+ <a class="tag" href="${_link('tag', tag)}"><span class="badge badge-info">${tag}</span></a>
%endfor
%endif
</%def>
@@ -32,19 +32,21 @@
<ul class="pager">
%if post.prev_post:
<li class="previous">
- <a href="${post.prev_post.permalink(lang)}">&larr; ${messages[lang]["Previous post"]}</a>
+ <a href="${post.prev_post.permalink()}">&larr; ${messages("Previous post")}</a>
+ </li>
%endif
%if post.next_post:
<li class="next">
- <a href="${post.next_post.permalink(lang)}">${messages[lang]["Next post"]} &rarr;</a>
+ <a href="${post.next_post.permalink()}">${messages("Next post")} &rarr;</a>
+ </li>
%endif
</ul>
</%def>
<%def name="twitter_card_information(post)">
%if twitter_card and twitter_card['use_twitter_cards']:
- <meta name="twitter:card" content="${twitter_card.get('card', 'summary')}">
- <meta name="og:url" content="${post.permalink(lang, absolute=True)}">
+ <meta name="twitter:card" content="${twitter_card.get('card', 'summary')|h}">
+ <meta name="og:url" content="${post.permalink(absolute=True)}">
%if 'site:id' in twitter_card:
<meta name="twitter:site:id" content="${twitter_card['site:id']}">
%elif 'site' in twitter_card:
@@ -55,11 +57,11 @@
%elif 'creator' in twitter_card:
<meta name="twitter:creator" content="${twitter_card['creator']}">
%endif
- <meta name="og:title" content="${post.title(lang)[:70]}">
- %if post.description(lang):
- <meta name="og:description" content="${post.description(lang)[:200]}">
+ <meta name="og:title" content="${post.title()[:70]|h}">
+ %if post.description():
+ <meta name="og:description" content="${post.description()[:200]|h}">
%else:
- <meta name="og:description" content="${post.text(lang, strip_html=True)[:200]}">
+ <meta name="og:description" content="${post.text(strip_html=True)[:200]|h}">
%endif
%endif
</%def>
diff --git a/nikola/data/themes/default/templates/story.tmpl b/nikola/data/themes/default/templates/story.tmpl
index d5c2f44..c1c06d8 100644
--- a/nikola/data/themes/default/templates/story.tmpl
+++ b/nikola/data/themes/default/templates/story.tmpl
@@ -1,12 +1,16 @@
## -*- coding: utf-8 -*-
<%inherit file="post.tmpl"/>
+<%namespace name="helper" file="post_helper.tmpl"/>
<%namespace name="disqus" file="disqus_helper.tmpl"/>
+<%block name="extra_head">
+${helper.twitter_card_information(post)}
+</%block>
<%block name="content">
%if title:
<h1>${title}</h1>
%endif
- ${post.text(lang)}
-%if enable_comments:
- ${disqus.html_disqus(post.permalink(absolute=True), post.title(lang), post.base_path)}
+ ${post.text()}
+%if enable_comments and not post.meta('nocomments'):
+ ${disqus.html_disqus(post.permalink(absolute=True), post.title(), post.base_path)}
%endif
</%block>
diff --git a/nikola/data/themes/default/templates/tag.tmpl b/nikola/data/themes/default/templates/tag.tmpl
index 7c89ad1..7fb43c0 100644
--- a/nikola/data/themes/default/templates/tag.tmpl
+++ b/nikola/data/themes/default/templates/tag.tmpl
@@ -1,7 +1,32 @@
## -*- coding: utf-8 -*-
<%inherit file="list_post.tmpl"/>
<%block name="extra_head">
+ %if len(translations) > 1:
%for language in translations:
- <link rel="alternate" type="application/rss+xml" type="application/rss+xml" title="RSS for tag ${tag} (${language})" href="${_link("tag_rss", tag, lang)}">
+ <link rel="alternate" type="application/rss+xml" type="application/rss+xml" title="RSS for tag ${tag} (${language})" href="${_link("tag_rss", tag, language)}">
%endfor
+ %else:
+ <link rel="alternate" type="application/rss+xml" type="application/rss+xml" title="RSS for tag ${tag}" href="${_link("tag_rss", tag)}">
+ %endif
+</%block>
+
+<%block name="content">
+ <!--Body content-->
+ <div class="postbox">
+ <h1>${title}</h1>
+ %if len(translations) > 1:
+ %for language in translations:
+ <a href="${_link("tag_rss", tag, language)}">RSS (${language})</a>&nbsp;
+ %endfor
+ %else:
+ <a href="${_link("tag_rss", tag)}">RSS</a>
+ %endif
+ <br>
+ <ul class="unstyled">
+ % for post in posts:
+ <li><a href="${post.permalink()}">[${post.formatted_date(date_format)}] ${post.title()}</a>
+ % endfor
+ </ul>
+ </div>
+ <!--End of body content-->
</%block>
diff --git a/nikola/data/themes/default/templates/tags.tmpl b/nikola/data/themes/default/templates/tags.tmpl
index 369a3d5..5727dc5 100644
--- a/nikola/data/themes/default/templates/tags.tmpl
+++ b/nikola/data/themes/default/templates/tags.tmpl
@@ -1,14 +1,12 @@
## -*- coding: utf-8 -*-
<%inherit file="base.tmpl"/>
<%block name="content">
- <div class="postbox">
- <!--Body content-->
- <h1>${title}</h1>
- <ul class="unstyled">
- % for text, link in items:
- <li><a class="tag" href="${link}"><span class="badge badge-info">${text}</span></a>
- % endfor
- </ul>
- <!--End of body content-->
- </div>
+ <!--Body content-->
+ <h1>${title}</h1>
+ <ul class="unstyled bricks">
+ % for text, link in items:
+ <li><a class="reference" href="${link}">${text}</a></li>
+ % endfor
+ </ul>
+ <!--End of body content-->
</%block>
diff --git a/nikola/data/themes/jinja-default/templates/base.tmpl b/nikola/data/themes/jinja-default/templates/base.tmpl
index 97cddff..c104b20 100644
--- a/nikola/data/themes/jinja-default/templates/base.tmpl
+++ b/nikola/data/themes/jinja-default/templates/base.tmpl
@@ -1,4 +1,5 @@
<!DOCTYPE html>
+{{set_locale(lang)}}
<html lang="{{lang}}">
<head>
<meta charset="utf-8">
@@ -23,7 +24,6 @@
<link href="/assets/css/rst.css" rel="stylesheet" type="text/css">
<link href="/assets/css/code.css" rel="stylesheet" type="text/css">
<link href="/assets/css/colorbox.css" rel="stylesheet" type="text/css"/>
- <link href="/assets/css/slides.css" rel="stylesheet" type="text/css"/>
<link href="/assets/css/theme.css" rel="stylesheet" type="text/css"/>
{% if has_custom_css %}
<link href="/assets/css/custom.css" rel="stylesheet" type="text/css">
@@ -42,6 +42,7 @@
{% endif %}
{% block extra_head %}
{% endblock %}
+ {{extra_head_data}}
</head>
<body>
{% if add_this_buttons %}
@@ -58,10 +59,10 @@
{% block belowtitle%}
{% if translations|length > 1 %}
<small>
- {{ messages[lang]["Also available in"] }}:&nbsp;
+ {{ messages("Also available in") }}:&nbsp;
{% for langname in translations.keys() %}
{% if langname != lang %}
- <a href="{{_link("index", None, langname)}}">{{messages[langname]["LANGUAGE"]}}</a>
+ <a href="{{_link("index", None, langname)}}">{{messages("LANGUAGE", langname)}}</a>
{% endif %}
{% endfor %}
</small>
@@ -106,7 +107,6 @@
</div>
</div>
</div>
- {{analytics}}
<!-- late load javascript -->
{% if use_bundles %}
{% if use_cdn %}
@@ -125,7 +125,7 @@
<script src="/assets/js/bootstrap.min.js" type="text/javascript"></script>
{% endif %}
<script src="/assets/js/jquery.colorbox-min.js" type="text/javascript"></script>
- <script src="/assets/js/slides.min.jquery.js" type="text/javascript"></script>
{% endif %}
+ {{analytics}}
<script type="text/javascript">jQuery("a.image-reference").colorbox({rel:"gal",maxWidth:"80%",maxHeight:"80%",scalePhotos:true});</script>
</body>
diff --git a/nikola/data/themes/jinja-default/templates/index.tmpl b/nikola/data/themes/jinja-default/templates/index.tmpl
index ab0392c..7d1aa00 100644
--- a/nikola/data/themes/jinja-default/templates/index.tmpl
+++ b/nikola/data/themes/jinja-default/templates/index.tmpl
@@ -2,14 +2,14 @@
{% block content %}
{% for post in posts %}
<div class="postbox">
- <h1><a href="{{post.permalink(lang)}}">{{post.title(lang)}}</a>
+ <h1><a href="{{post.permalink()}}">{{post.title()}}</a>
<small>&nbsp;&nbsp;
- {{messages[lang]["Posted"]}}: {{post.date.strftime(date_format)}}
+ {{messages("Posted")}}: {{post.formatted_date(date_format)}}
</small></h1>
<hr>
- {{post.text(lang, index_teasers)}}
+ {{post.text(teaser_only=index_teasers)}}
<p>
- {% if disqus_forum %}
+ {% if disqus_forum and not post.meta('nocomments')%}
<a href="{{post.permalink()}}#disqus_thread" data-disqus-identifier="{{post.base_path}}">Comments</a>
{% endif %}
</div>
@@ -18,11 +18,11 @@
<ul class="pager">
{%if prevlink %}
<li class="previous">
- <a href="{{prevlink}}">&larr; {{messages[lang]["Newer posts"]}}</a>
+ <a href="{{prevlink}}">&larr; {{messages("Newer posts")}}</a>
{% endif %}
{% if nextlink %}
<li class="next">
- <a href="{{nextlink}}">{{messages[lang]["Older posts"]}} &rarr;</a>
+ <a href="{{nextlink}}">{{messages("Older posts")}} &rarr;</a>
{% endif %}
</ul>
diff --git a/nikola/data/themes/jinja-default/templates/list_post.tmpl b/nikola/data/themes/jinja-default/templates/list_post.tmpl
index 7723214..b4ac59e 100644
--- a/nikola/data/themes/jinja-default/templates/list_post.tmpl
+++ b/nikola/data/themes/jinja-default/templates/list_post.tmpl
@@ -5,7 +5,7 @@
<h1>{{title}}</h1>
<ul class="unstyled">
{% for post in posts %}
- <li><a href="{{post.permalink(lang)}}">[{{post.date.strftime(date_format)}}] {{post.title(lang)}}</a>
+ <li><a href="{{post.permalink()}}">[{{post.formatted_date(date_format)}}] {{post.title()}}</a>
{% endfor %}
</ul>
</div>
diff --git a/nikola/data/themes/jinja-default/templates/post.tmpl b/nikola/data/themes/jinja-default/templates/post.tmpl
index d14e973..ab96682 100644
--- a/nikola/data/themes/jinja-default/templates/post.tmpl
+++ b/nikola/data/themes/jinja-default/templates/post.tmpl
@@ -3,49 +3,50 @@
<div class="postbox">
<h1><a href='{{permalink}}'>{{title}}</a></h1>
{% if link %}
- <p><a href='{{link}}'>{{messages[lang]["Original site"]}}</a></p>
+ <p><a href='{{link}}'>{{messages("Original site")}}</a></p>
{% endif %}
<hr>
<small>
- {{messages[lang]["Posted"]}}: {{post.date.strftime(date_format)}}&nbsp;&nbsp;|&nbsp;&nbsp;
+ {{messages("Posted")}}: {{post.formatted_date(date_format)}}&nbsp;&nbsp;|&nbsp;&nbsp;
{% if translations|length > 1 %}
{% for langname in translations.keys() %}
{% if langname != lang and post.is_translation_available(langname) %}
- <a href="{{post.permalink(langname)}}">{{messages[langname]["Read in English"]}}</a>
+ <a href="{{post.permalink(langname)}}">{{messages("Read in English", langname)}}</a>
&nbsp;&nbsp;|&nbsp;&nbsp;
{% endif %}
{% endfor %}
{% endif %}
-
- <a href="{{post.pagenames[lang]+".txt"}}" id="sourcelink">{{messages[lang]["Source"]}}</a>
+ {% if not post.meta('password')
+ <a href="{{post.meta('slug')+".txt"}}" id="sourcelink">{{messages("Source")}}</a>
+ {% endif %}
{% if post.tags %}
- &nbsp;&nbsp;|&nbsp;&nbsp;{{messages[lang]["More posts about"]}}
+ &nbsp;&nbsp;|&nbsp;&nbsp;{{messages("More posts about")}}
{% for tag in post.tags %}
- <a href="{{_link("tag", tag, lang)}}"><span class="badge badge-info">{{tag}}</span></a>
+ <a href="{{_link("tag", tag)}}"><span class="badge badge-info">{{tag}}</span></a>
{% endfor %}
{% endif %}
</small>
<hr>
- {{post.text(lang)}}
+ {{post.text()}}
<ul class="pager">
{%if post.prev_post %}
<li class="previous">
- <a href="{{rel_link(permalink, post.prev_post.permalink(lang))}}">&larr; {{messages[lang]["Previous post"]}}</a>
+ <a href="{{rel_link(permalink, post.prev_post.permalink())}}">&larr; {{messages("Previous post")}}</a>
{% endif %}
{%if post.next_post %}
<li class="next">
- <a href="{{rel_link(permalink, post.next_post.permalink(lang))}}">{{messages[lang]["Next post"]}} &rarr;</a>
+ <a href="{{rel_link(permalink, post.next_post.permalink())}}">{{messages("Next post")}} &rarr;</a>
{% endif %}
</ul>
- {% if disqus_forum %}
+ {% if disqus_forum and not post.meta('nocomments')%}
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_shortname ="{{disqus_forum}}";
var disqus_url="{{post.permalink(absolute=True)}}";
- var disqus_title={{post.title(lang)|tojson }};
+ var disqus_title={{post.title()|tojson }};
var disqus_identifier="{{post.base_path}}";
- var disqus_config = function () {
+ var disqus_config = function () {
this.language = "{{lang}}";
};
(function() {
diff --git a/nikola/data/themes/jinja-default/templates/story.tmpl b/nikola/data/themes/jinja-default/templates/story.tmpl
index ccaac91..a4ad375 100644
--- a/nikola/data/themes/jinja-default/templates/story.tmpl
+++ b/nikola/data/themes/jinja-default/templates/story.tmpl
@@ -3,8 +3,23 @@
{% if title %}
<h1>{{title}}</h1>
{% endif %}
- {{post.text(lang)}}
-{%if enable_comments %}
- {{disqus.html_disqus(post.permalink(absolute=True), post.title(lang), post.base_path)}}
+ {{post.text()}}
+{%if enable_comments and disqus_forum and not post.meta('nocomments')%}
+ <div id="disqus_thread"></div>
+ <script type="text/javascript">
+ var disqus_shortname ="{{disqus_forum}}";
+ var disqus_url="{{post.permalink(absolute=True)}}";
+ var disqus_title={{post.title()|tojson }};
+ var disqus_identifier="{{post.base_path}}";
+ var disqus_config = function () {
+ this.language = "{{lang}}";
+ };
+ (function() {
+ var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
+ dsq.src = 'http://' + 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="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
{%endif%}
{% endblock %}
diff --git a/nikola/data/themes/jinja-default/templates/tag.tmpl b/nikola/data/themes/jinja-default/templates/tag.tmpl
index 42720fd..77db27d 100644
--- a/nikola/data/themes/jinja-default/templates/tag.tmpl
+++ b/nikola/data/themes/jinja-default/templates/tag.tmpl
@@ -1,6 +1,6 @@
{% extends "list_post.tmpl"%}
{%block extra_head %}
{% for language in translations %}
- <link rel="alternate" type="application/rss+xml" type="application/rss+xml" title="RSS for tag {{tag}} ({{language}})" href="{{_link("tag_rss", tag, lang)}}">
+ <link rel="alternate" type="application/rss+xml" type="application/rss+xml" title="RSS for tag {{tag}} ({{language}})" href="{{_link("tag_rss", tag, language)}}">
{% endfor %}
{% endblock %}
diff --git a/nikola/data/themes/jinja-default/templates/tags.tmpl b/nikola/data/themes/jinja-default/templates/tags.tmpl
index 3eae88d..0fa9d0f 100644
--- a/nikola/data/themes/jinja-default/templates/tags.tmpl
+++ b/nikola/data/themes/jinja-default/templates/tags.tmpl
@@ -3,9 +3,9 @@
<div class="postbox">
<!--Body content-->
<h1>{{title}}</h1>
- <ul class="unstyled">
+ <ul class="unstyled bricks">
{% for text, link in items %}
- <li><a href="{{link}}"><span class="badge badge-info">{{text}}</span></a>
+ <li><a href="{{link}}">{{text}}</a></li>
{% endfor %}
</ul>
<!--End of body content-->
diff --git a/nikola/data/themes/monospace/assets/css/code.css b/nikola/data/themes/monospace/assets/css/code.css
deleted file mode 100644
index b1d7ace..0000000
--- a/nikola/data/themes/monospace/assets/css/code.css
+++ /dev/null
@@ -1,62 +0,0 @@
-pre { word-break: pre; white-space: pre; word-wrap: pre; overflow: auto; max-width: 100%;}
-td.linenos { vertical-align: top; width: 4em;}
-div.code > pre, .code
-{ background: #f8f8f8; white-space: pre;}
-.code .c { color: #008800; font-style: italic } /* Comment */
-.code .err { border: 1px solid #FF0000 } /* Error */
-.code .k { color: #AA22FF; font-weight: bold } /* Keyword */
-.code .o { color: #666666 } /* Operator */
-.code .cm { color: #008800; font-style: italic } /* Comment.Multiline */
-.code .cp { color: #008800 } /* Comment.Preproc */
-.code .c1 { color: #008800; font-style: italic } /* Comment.Single */
-.code .cs { color: #008800; font-weight: bold } /* Comment.Special */
-.code .gd { color: #A00000 } /* Generic.Deleted */
-.code .ge { font-style: italic } /* Generic.Emph */
-.code .gr { color: #FF0000 } /* Generic.Error */
-.code .gh { color: #000080; font-weight: bold } /* Generic.Heading */
-.code .gi { color: #00A000 } /* Generic.Inserted */
-.code .go { color: #808080 } /* Generic.Output */
-.code .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
-.code .gs { font-weight: bold } /* Generic.Strong */
-.code .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
-.code .gt { color: #0040D0 } /* Generic.Traceback */
-.code .kc { color: #AA22FF; font-weight: bold } /* Keyword.Constant */
-.code .kd { color: #AA22FF; font-weight: bold } /* Keyword.Declaration */
-.code .kp { color: #AA22FF } /* Keyword.Pseudo */
-.code .kr { color: #AA22FF; font-weight: bold } /* Keyword.Reserved */
-.code .kt { color: #AA22FF; font-weight: bold } /* Keyword.Type */
-.code .m { color: #666666 } /* Literal.Number */
-.code .s { color: #BB4444 } /* Literal.String */
-.code .na { color: #BB4444 } /* Name.Attribute */
-.code .nb { color: #AA22FF } /* Name.Builtin */
-.code .nc { color: #0000FF } /* Name.Class */
-.code .no { color: #880000 } /* Name.Constant */
-.code .nd { color: #AA22FF } /* Name.Decorator */
-.code .ni { color: #999999; font-weight: bold } /* Name.Entity */
-.code .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
-.code .nf { color: #00A000 } /* Name.Function */
-.code .nl { color: #A0A000 } /* Name.Label */
-.code .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
-.code .nt { color: #008000; font-weight: bold } /* Name.Tag */
-.code .nv { color: #B8860B } /* Name.Variable */
-.code .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
-.code .mf { color: #666666 } /* Literal.Number.Float */
-.code .mh { color: #666666 } /* Literal.Number.Hex */
-.code .mi { color: #666666 } /* Literal.Number.Integer */
-.code .mo { color: #666666 } /* Literal.Number.Oct */
-.code .sb { color: #BB4444 } /* Literal.String.Backtick */
-.code .sc { color: #BB4444 } /* Literal.String.Char */
-.code .sd { color: #BB4444; font-style: italic } /* Literal.String.Doc */
-.code .s2 { color: #BB4444 } /* Literal.String.Double */
-.code .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
-.code .sh { color: #BB4444 } /* Literal.String.Heredoc */
-.code .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
-.code .sx { color: #008000 } /* Literal.String.Other */
-.code .sr { color: #BB6688 } /* Literal.String.Regex */
-.code .s1 { color: #BB4444 } /* Literal.String.Single */
-.code .ss { color: #B8860B } /* Literal.String.Symbol */
-.code .bp { color: #AA22FF } /* Name.Builtin.Pseudo */
-.code .vc { color: #B8860B } /* Name.Variable.Class */
-.code .vg { color: #B8860B } /* Name.Variable.Global */
-.code .vi { color: #B8860B } /* Name.Variable.Instance */
-.code .il { color: #666666 } /* Literal.Number.Integer.Long */
diff --git a/nikola/data/themes/monospace/bundles b/nikola/data/themes/monospace/bundles
index aa35d9c..4760181 100644
--- a/nikola/data/themes/monospace/bundles
+++ b/nikola/data/themes/monospace/bundles
@@ -1 +1,2 @@
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/monospace/templates/base.tmpl b/nikola/data/themes/monospace/templates/base.tmpl
index 9eecbd4..806795d 100644
--- a/nikola/data/themes/monospace/templates/base.tmpl
+++ b/nikola/data/themes/monospace/templates/base.tmpl
@@ -1,11 +1,13 @@
## -*- coding: utf-8 -*-
<%namespace file="base_helper.tmpl" import="*"/>
+${set_locale(lang)}
<!DOCTYPE html>
<html lang="${lang}">
<head>
${html_head()}
<%block name="extra_head">
</%block>
+ ${extra_head_data}
</head>
<body class="home blog">
%if add_this_buttons:
@@ -23,7 +25,7 @@
<%block name="belowtitle">
%if len(translations) > 1:
<small>
- ${(messages[lang][u"Also available in"])}:&nbsp;
+ ${(messages("Also available in"))}:&nbsp;
${html_translations()}
</small>
%endif
@@ -38,6 +40,6 @@
<div id="footer">
${content_footer}
</div>
- </div>
+ </div>
${analytics}
</body>
diff --git a/nikola/data/themes/monospace/templates/base_helper.tmpl b/nikola/data/themes/monospace/templates/base_helper.tmpl
index aba8dff..4f3e45b 100644
--- a/nikola/data/themes/monospace/templates/base_helper.tmpl
+++ b/nikola/data/themes/monospace/templates/base_helper.tmpl
@@ -4,27 +4,29 @@
<meta name="description" content="${description}" >
<meta name="author" content="${blog_author}">
<title>${title} | ${blog_title}</title>
- <!-- Le styles -->
+ ${mathjax_config}
%if use_bundles:
- <link href="/assets/css/all.css" rel="stylesheet" type="text/css">
- <script src="/assets/js/all.js" type="text/javascript"></script>
+ %if use_cdn:
+ <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.0/css/bootstrap-combined.min.css" rel="stylesheet">
+ <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:
- <link href="/assets/css/bootstrap.css" rel="stylesheet" type="text/css">
+ %if use_cdn:
+ <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.0/css/bootstrap-combined.min.css" rel="stylesheet">
+ %else:
+ <link href="/assets/css/bootstrap.min.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/bootstrap-responsive.min.css" rel="stylesheet" type="text/css">
+ %endif
<link href="/assets/css/rst.css" rel="stylesheet" type="text/css">
<link href="/assets/css/code.css" rel="stylesheet" type="text/css">
<link href="/assets/css/colorbox.css" rel="stylesheet" type="text/css"/>
- <link href="/assets/css/slides.css" rel="stylesheet" type="text/css"/>
<link href="/assets/css/theme.css" rel="stylesheet" type="text/css"/>
%if has_custom_css:
<link href="/assets/css/custom.css" rel="stylesheet" type="text/css">
%endif
- <link href="/assets/css/bootstrap-responsive.css" rel="stylesheet" type="text/css">
- <script src="/assets/js/jquery-1.7.2.min.js" type="text/javascript"></script>
- <script src="/assets/js/jquery.colorbox-min.js" type="text/javascript"></script>
- <script src="/assets/js/slides.min.jquery.js" type="text/javascript"></script>
- <script src="/assets/js/bootstrap.min.js" type="text/javascript"></script>
%endif
- <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
<![endif]-->
@@ -32,7 +34,7 @@
${rss_link}
%else:
%for language in translations:
- <link rel="alternate" type="application/rss+xml" title="RSS (${language})" href="${_link('rss', None, lang)}">
+ <link rel="alternate" type="application/rss+xml" title="RSS (${language})" href="${_link('rss', None, language)}">
%endfor
%endif
%if favicons:
@@ -48,10 +50,10 @@
<!-- Social buttons -->
<div id="addthisbox" class="addthis_toolbox addthis_peekaboo_style addthis_default_style addthis_label_style addthis_32x32_style">
<a class="addthis_button_more">Share</a>
- <ul><li><a class="addthis_button_facebook"></a></li>
- <li><a class="addthis_button_google_plusone_share"></a></li>
- <li><a class="addthis_button_linkedin"></a></li>
- <li><a class="addthis_button_twitter"></a></li>
+ <ul><li><a class="addthis_button_facebook"></a>
+ <li><a class="addthis_button_google_plusone_share"></a>
+ <li><a class="addthis_button_linkedin"></a>
+ <li><a class="addthis_button_twitter"></a>
</ul>
</div>
<script type="text/javascript" src="http://s7.addthis.com/js/300/addthis_widget.js#pubid=ra-4f7088a56bb93798"></script>
@@ -74,7 +76,7 @@
<%def name="html_translations()">
%for langname in translations.keys():
%if langname != lang:
- <a href="${_link("index", None, langname)}">${messages[langname]["LANGUAGE"]}</a>
+ <a href="${_link("index", None, langname)}">${messages("LANGUAGE", langname)}</a>
%endif
%endfor
</%def>
diff --git a/nikola/data/themes/monospace/templates/disqus_helper.tmpl b/nikola/data/themes/monospace/templates/disqus_helper.tmpl
index 674e20e..4c60f85 100644
--- a/nikola/data/themes/monospace/templates/disqus_helper.tmpl
+++ b/nikola/data/themes/monospace/templates/disqus_helper.tmpl
@@ -1,6 +1,9 @@
## -*- coding: utf-8 -*-
<%!
import json
+ translations = {
+ 'es': 'es_ES',
+ }
%>
<%def name="html_disqus(url, title, identifier)">
%if disqus_forum:
@@ -12,8 +15,8 @@
%endif
var disqus_title=${json.dumps(title)};
var disqus_identifier="${identifier}";
- var disqus_config = function () {
- this.language = "${lang}";
+ var disqus_config = function () {
+ this.language = "${translations.get(lang, lang)}";
};
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
diff --git a/nikola/data/themes/monospace/templates/index.tmpl b/nikola/data/themes/monospace/templates/index.tmpl
index ee57d26..4a0c630 100644
--- a/nikola/data/themes/monospace/templates/index.tmpl
+++ b/nikola/data/themes/monospace/templates/index.tmpl
@@ -5,22 +5,24 @@
<%block name="content">
% for post in posts:
<div class="postbox">
- <h1><a href="${post.permalink(lang)}">${post.title(lang)}</a></h1>
+ <h1><a href="${post.permalink()}">${post.title()}</a></h1>
<div class="meta" style="background-color: rgb(234, 234, 234); ">
<span class="authordate">
- ${messages[lang]["Posted"]}: ${post.date.strftime(date_format)}
+ ${messages("Posted")}: ${post.formatted_date(date_format)}
</span>
<br>
<span class="tags">Tags:&nbsp;
%if post.tags:
%for tag in post.tags:
- <a class="tag" href="${_link('tag', tag, lang)}"><span class="badge badge-info">${tag}</span></a>
+ <a class="tag" href="${_link('tag', tag)}"><span class="badge badge-info">${tag}</span></a>
%endfor
%endif
</span>
</div>
- ${post.text(lang, index_teasers)}
- ${disqus.html_disqus_link(post.permalink()+"#disqus_thread", post.base_path)}
+ ${post.text(teaser_only=index_teasers)}
+ % if not post.meta('nocomments'):
+ ${disqus.html_disqus_link(post.permalink()+"#disqus_thread", post.base_path)}
+ % endif
</div>
% endfor
${helper.html_pager()}
diff --git a/nikola/data/themes/monospace/templates/index_helper.tmpl b/nikola/data/themes/monospace/templates/index_helper.tmpl
index 114a730..1bb700c 100644
--- a/nikola/data/themes/monospace/templates/index_helper.tmpl
+++ b/nikola/data/themes/monospace/templates/index_helper.tmpl
@@ -4,12 +4,12 @@
<ul class="pager">
%if prevlink:
<li class="previous">
- <a href="${prevlink}">&larr; ${messages[lang]["Newer posts"]}</a>
+ <a href="${prevlink}">&larr; ${messages("Newer posts")}</a>
</li>
%endif
%if nextlink:
<li class="next">
- <a href="${nextlink}">${messages[lang]["Older posts"]} &rarr;</a>
+ <a href="${nextlink}">${messages("Older posts")} &rarr;</a>
</li>
%endif
</ul>
diff --git a/nikola/data/themes/monospace/templates/list_post.tmpl b/nikola/data/themes/monospace/templates/list_post.tmpl
index 1a1cdee..f0e159d 100644
--- a/nikola/data/themes/monospace/templates/list_post.tmpl
+++ b/nikola/data/themes/monospace/templates/list_post.tmpl
@@ -6,7 +6,7 @@
<h1>${title}</h1>
<ul class="unstyled">
% for post in posts:
- <li><a href="${post.permalink(lang)}">[${post.date.strftime(date_format)}] ${post.title(lang)}</a>
+ <li><a href="${post.permalink()}">[${post.formatted_date(date_format)}] ${post.title()}</a>
% endfor
</ul>
</div>
diff --git a/nikola/data/themes/monospace/templates/post.tmpl b/nikola/data/themes/monospace/templates/post.tmpl
index 2ba27f1..0ec360d 100644
--- a/nikola/data/themes/monospace/templates/post.tmpl
+++ b/nikola/data/themes/monospace/templates/post.tmpl
@@ -7,13 +7,16 @@
${helper.html_title()}
<div class="meta" style="background-color: rgb(234, 234, 234); ">
<span class="authordate">
- ${messages[lang]["Posted"]}: ${post.date.strftime(date_format)} [<a href="${post.pagenames[lang]+'.txt'}" id="sourcelink">${messages[lang]["Source"]}</a>]
+ ${messages("Posted")}: ${post.formatted_date(date_format)}
+ % if not post.meta('password'):
+ [<a href="${post.meta('slug')+'.txt'}" id="sourcelink">${messages("Source")}</a>]
+ % endif
</span>
<br>
%if post.tags:
- <span class="tags">${messages[lang]["Tags"]}:&nbsp;
+ <span class="tags">${messages("Tags")}:&nbsp;
%for tag in post.tags:
- <a class="tag" href="${_link('tag', tag, lang)}"><span class="badge badge-info">${tag}</span></a>
+ <a class="tag" href="${_link('tag', tag)}"><span class="badge badge-info">${tag}</span></a>
%endfor
</span>
<br>
@@ -22,8 +25,10 @@
${helper.html_translations(post)}
</span>
</div>
- ${post.text(lang)}
+ ${post.text()}
${helper.html_pager(post)}
- ${disqus.html_disqus(post.permalink(absolute=True), post.title(lang), post.base_path)}
+ % if not post.meta('nocomments'):
+ ${disqus.html_disqus(post.permalink(absolute=True), post.title(), post.base_path)}
+ % endif
</div>
</%block>
diff --git a/nikola/data/themes/monospace/templates/post_helper.tmpl b/nikola/data/themes/monospace/templates/post_helper.tmpl
index 8651c65..cce0ecf 100644
--- a/nikola/data/themes/monospace/templates/post_helper.tmpl
+++ b/nikola/data/themes/monospace/templates/post_helper.tmpl
@@ -2,7 +2,7 @@
<%def name="html_title()">
<h1>${title}</h1>
% if link:
- <p><a href='${link}'>${messages[lang]["Original site"]}</a></p>
+ <p><a href='${link}'>${messages("Original site")}</a></p>
% endif
</%def>
@@ -12,7 +12,7 @@
%for langname in translations.keys():
%if langname != lang and post.is_translation_available(langname):
&nbsp;&nbsp;|&nbsp;&nbsp;
- <a href="${post.permalink(langname)}">${messages[langname]["Read in English"]}</a>
+ <a href="${post.permalink(langname)}">${messages("Read in English", langname)}</a>
%endif
%endfor
%endif
@@ -21,25 +21,53 @@
<%def name="html_tags(post)">
%if post.tags:
- &nbsp;&nbsp;|&nbsp;&nbsp;${messages[lang]["More posts about"]}
+ &nbsp;&nbsp;|&nbsp;&nbsp;${messages("More posts about")}
%for tag in post.tags:
- <a class="tag" href="${_link('tag', tag, lang)}"><span class="badge badge-info">${tag}</span></a>
+ <a class="tag" href="${_link('tag', tag)}"><span class="badge badge-info">${tag}</span></a>
%endfor
%endif
</%def>
-
<%def name="html_pager(post)">
<ul class="pager">
%if post.prev_post:
<li class="previous">
- <a href="${post.prev_post.permalink(lang)}">&larr; ${messages[lang]["Previous post"]}</a>
+ <a href="${post.prev_post.permalink()}">&larr; ${messages("Previous post")}</a>
</li>
%endif
%if post.next_post:
<li class="next">
- <a href="${post.next_post.permalink(lang)}">${messages[lang]["Next post"]} &rarr;</a>
+ <a href="${post.next_post.permalink()}">${messages("Next post")} &rarr;</a>
</li>
%endif
</ul>
</%def>
+
+<%def name="twitter_card_information(post)">
+ %if twitter_card and twitter_card['use_twitter_cards']:
+ <meta name="twitter:card" content="${twitter_card.get('card', 'summary')|h}">
+ <meta name="og:url" content="${post.permalink(absolute=True)}">
+ %if 'site:id' in twitter_card:
+ <meta name="twitter:site:id" content="${twitter_card['site:id']}">
+ %elif 'site' in twitter_card:
+ <meta name="twitter:site" content="${twitter_card['site']}">
+ %endif
+ %if 'creator:id' in twitter_card:
+ <meta name="twitter:creator:id" content="${twitter_card['creator:id']}">
+ %elif 'creator' in twitter_card:
+ <meta name="twitter:creator" content="${twitter_card['creator']}">
+ %endif
+ <meta name="og:title" content="${post.title()[:70]|h}">
+ %if post.description():
+ <meta name="og:description" content="${post.description()[:200]|h}">
+ %else:
+ <meta name="og:description" content="${post.text(strip_html=True)[:200]|h}">
+ %endif
+ %endif
+</%def>
+
+<%def name="mathjax_script(post)">
+ %if post.is_mathjax:
+ <script src="/assets/js/mathjax.js" type="text/javascript"></script>
+ %endif
+</%def>
diff --git a/nikola/data/themes/monospace/templates/story.tmpl b/nikola/data/themes/monospace/templates/story.tmpl
index 30d263b..21d0e2f 100644
--- a/nikola/data/themes/monospace/templates/story.tmpl
+++ b/nikola/data/themes/monospace/templates/story.tmpl
@@ -1,11 +1,15 @@
## -*- coding: utf-8 -*-
<%inherit file="post.tmpl"/>
+<%namespace name="helper" file="post_helper.tmpl"/>
+<%block name="extra_head">
+${helper.twitter_card_information(post)}
+</%block>
<%block name="content">
%if title:
<h1>${title}</h1>
%endif
- ${post.text(lang)}
-%if enable_comments:
- ${disqus.html_disqus(post.permalink(absolute=True), post.title(lang), post.base_path)}
+ ${post.text()}
+%if enable_comments and not post.meta('nocomments'):
+ ${disqus.html_disqus(post.permalink(absolute=True), post.title(), post.base_path)}
%endif
</%block>
diff --git a/nikola/data/themes/monospace/templates/tag.tmpl b/nikola/data/themes/monospace/templates/tag.tmpl
index 7c89ad1..97aafeb 100644
--- a/nikola/data/themes/monospace/templates/tag.tmpl
+++ b/nikola/data/themes/monospace/templates/tag.tmpl
@@ -2,6 +2,6 @@
<%inherit file="list_post.tmpl"/>
<%block name="extra_head">
%for language in translations:
- <link rel="alternate" type="application/rss+xml" type="application/rss+xml" title="RSS for tag ${tag} (${language})" href="${_link("tag_rss", tag, lang)}">
+ <link rel="alternate" type="application/rss+xml" type="application/rss+xml" title="RSS for tag ${tag} (${language})" href="${_link("tag_rss", tag, language)}">
%endfor
</%block>
diff --git a/nikola/data/themes/orphan/assets/css/code.css b/nikola/data/themes/orphan/assets/css/code.css
deleted file mode 120000
index 6b2b872..0000000
--- a/nikola/data/themes/orphan/assets/css/code.css
+++ /dev/null
@@ -1 +0,0 @@
-../../../default/assets/css/code.css \ No newline at end of file
diff --git a/nikola/data/themes/orphan/templates/base.tmpl b/nikola/data/themes/orphan/templates/base.tmpl
index 39e2b9d..2a62b58 100644
--- a/nikola/data/themes/orphan/templates/base.tmpl
+++ b/nikola/data/themes/orphan/templates/base.tmpl
@@ -1,11 +1,13 @@
## -*- coding: utf-8 -*-
<%namespace file="base_helper.tmpl" import="*"/>
+${set_locale(lang)}
<!DOCTYPE html>
<html lang="${lang}">
<head>
${html_head()}
<%block name="extra_head">
</%block>
+ ${extra_head_data}
</head>
<body>
%if add_this_buttons:
@@ -17,7 +19,7 @@
<%block name="belowtitle">
%if len(translations) > 1:
<small>
- ${(messages[lang][u"Also available in"])}:&nbsp;
+ ${(messages("Also available in"))}:&nbsp;
${html_translations()}
</small>
%endif
diff --git a/nikola/data/themes/orphan/templates/base_helper.tmpl b/nikola/data/themes/orphan/templates/base_helper.tmpl
index aba8dff..4f3e45b 100644
--- a/nikola/data/themes/orphan/templates/base_helper.tmpl
+++ b/nikola/data/themes/orphan/templates/base_helper.tmpl
@@ -4,27 +4,29 @@
<meta name="description" content="${description}" >
<meta name="author" content="${blog_author}">
<title>${title} | ${blog_title}</title>
- <!-- Le styles -->
+ ${mathjax_config}
%if use_bundles:
- <link href="/assets/css/all.css" rel="stylesheet" type="text/css">
- <script src="/assets/js/all.js" type="text/javascript"></script>
+ %if use_cdn:
+ <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.0/css/bootstrap-combined.min.css" rel="stylesheet">
+ <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:
- <link href="/assets/css/bootstrap.css" rel="stylesheet" type="text/css">
+ %if use_cdn:
+ <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.0/css/bootstrap-combined.min.css" rel="stylesheet">
+ %else:
+ <link href="/assets/css/bootstrap.min.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/bootstrap-responsive.min.css" rel="stylesheet" type="text/css">
+ %endif
<link href="/assets/css/rst.css" rel="stylesheet" type="text/css">
<link href="/assets/css/code.css" rel="stylesheet" type="text/css">
<link href="/assets/css/colorbox.css" rel="stylesheet" type="text/css"/>
- <link href="/assets/css/slides.css" rel="stylesheet" type="text/css"/>
<link href="/assets/css/theme.css" rel="stylesheet" type="text/css"/>
%if has_custom_css:
<link href="/assets/css/custom.css" rel="stylesheet" type="text/css">
%endif
- <link href="/assets/css/bootstrap-responsive.css" rel="stylesheet" type="text/css">
- <script src="/assets/js/jquery-1.7.2.min.js" type="text/javascript"></script>
- <script src="/assets/js/jquery.colorbox-min.js" type="text/javascript"></script>
- <script src="/assets/js/slides.min.jquery.js" type="text/javascript"></script>
- <script src="/assets/js/bootstrap.min.js" type="text/javascript"></script>
%endif
- <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
<![endif]-->
@@ -32,7 +34,7 @@
${rss_link}
%else:
%for language in translations:
- <link rel="alternate" type="application/rss+xml" title="RSS (${language})" href="${_link('rss', None, lang)}">
+ <link rel="alternate" type="application/rss+xml" title="RSS (${language})" href="${_link('rss', None, language)}">
%endfor
%endif
%if favicons:
@@ -48,10 +50,10 @@
<!-- Social buttons -->
<div id="addthisbox" class="addthis_toolbox addthis_peekaboo_style addthis_default_style addthis_label_style addthis_32x32_style">
<a class="addthis_button_more">Share</a>
- <ul><li><a class="addthis_button_facebook"></a></li>
- <li><a class="addthis_button_google_plusone_share"></a></li>
- <li><a class="addthis_button_linkedin"></a></li>
- <li><a class="addthis_button_twitter"></a></li>
+ <ul><li><a class="addthis_button_facebook"></a>
+ <li><a class="addthis_button_google_plusone_share"></a>
+ <li><a class="addthis_button_linkedin"></a>
+ <li><a class="addthis_button_twitter"></a>
</ul>
</div>
<script type="text/javascript" src="http://s7.addthis.com/js/300/addthis_widget.js#pubid=ra-4f7088a56bb93798"></script>
@@ -74,7 +76,7 @@
<%def name="html_translations()">
%for langname in translations.keys():
%if langname != lang:
- <a href="${_link("index", None, langname)}">${messages[langname]["LANGUAGE"]}</a>
+ <a href="${_link("index", None, langname)}">${messages("LANGUAGE", langname)}</a>
%endif
%endfor
</%def>
diff --git a/nikola/data/themes/orphan/templates/disqus_helper.tmpl b/nikola/data/themes/orphan/templates/disqus_helper.tmpl
index 674e20e..4c60f85 100644
--- a/nikola/data/themes/orphan/templates/disqus_helper.tmpl
+++ b/nikola/data/themes/orphan/templates/disqus_helper.tmpl
@@ -1,6 +1,9 @@
## -*- coding: utf-8 -*-
<%!
import json
+ translations = {
+ 'es': 'es_ES',
+ }
%>
<%def name="html_disqus(url, title, identifier)">
%if disqus_forum:
@@ -12,8 +15,8 @@
%endif
var disqus_title=${json.dumps(title)};
var disqus_identifier="${identifier}";
- var disqus_config = function () {
- this.language = "${lang}";
+ var disqus_config = function () {
+ this.language = "${translations.get(lang, lang)}";
};
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
diff --git a/nikola/data/themes/orphan/templates/index.tmpl b/nikola/data/themes/orphan/templates/index.tmpl
index 1a436e2..59d391a 100644
--- a/nikola/data/themes/orphan/templates/index.tmpl
+++ b/nikola/data/themes/orphan/templates/index.tmpl
@@ -5,13 +5,15 @@
<%block name="content">
% for post in posts:
<div class="postbox">
- <h1><a href="${post.permalink(lang)}">${post.title(lang)}</a>
+ <h1><a href="${post.permalink()}">${post.title()}</a>
<small>&nbsp;&nbsp;
- ${messages[lang]["Posted"]}: ${post.date.strftime(date_format)}
+ ${messages("Posted")}: ${post.formatted_date(date_format)}
</small></h1>
<hr>
- ${post.text(lang, index_teasers)}
- ${disqus.html_disqus_link(post.permalink()+"#disqus_thread", post.base_path)}
+ ${post.text(teaser_only=index_teasers)}
+ % if not post.meta('nocomments'):
+ ${disqus.html_disqus_link(post.permalink()+"#disqus_thread", post.base_path)}
+ % endif
</div>
% endfor
${helper.html_pager()}
diff --git a/nikola/data/themes/orphan/templates/index_helper.tmpl b/nikola/data/themes/orphan/templates/index_helper.tmpl
index 114a730..1bb700c 100644
--- a/nikola/data/themes/orphan/templates/index_helper.tmpl
+++ b/nikola/data/themes/orphan/templates/index_helper.tmpl
@@ -4,12 +4,12 @@
<ul class="pager">
%if prevlink:
<li class="previous">
- <a href="${prevlink}">&larr; ${messages[lang]["Newer posts"]}</a>
+ <a href="${prevlink}">&larr; ${messages("Newer posts")}</a>
</li>
%endif
%if nextlink:
<li class="next">
- <a href="${nextlink}">${messages[lang]["Older posts"]} &rarr;</a>
+ <a href="${nextlink}">${messages("Older posts")} &rarr;</a>
</li>
%endif
</ul>
diff --git a/nikola/data/themes/orphan/templates/list_post.tmpl b/nikola/data/themes/orphan/templates/list_post.tmpl
index 1a1cdee..f0e159d 100644
--- a/nikola/data/themes/orphan/templates/list_post.tmpl
+++ b/nikola/data/themes/orphan/templates/list_post.tmpl
@@ -6,7 +6,7 @@
<h1>${title}</h1>
<ul class="unstyled">
% for post in posts:
- <li><a href="${post.permalink(lang)}">[${post.date.strftime(date_format)}] ${post.title(lang)}</a>
+ <li><a href="${post.permalink()}">[${post.formatted_date(date_format)}] ${post.title()}</a>
% endfor
</ul>
</div>
diff --git a/nikola/data/themes/orphan/templates/post.tmpl b/nikola/data/themes/orphan/templates/post.tmpl
index 672d4f6..6f6529d 100644
--- a/nikola/data/themes/orphan/templates/post.tmpl
+++ b/nikola/data/themes/orphan/templates/post.tmpl
@@ -7,15 +7,19 @@
${helper.html_title()}
<hr>
<small>
- ${messages[lang]["Posted"]}: ${post.date.strftime(date_format)}
+ ${messages("Posted")}: ${post.formatted_date(date_format)}
${helper.html_translations(post)}
&nbsp;&nbsp;|&nbsp;&nbsp;
- <a href="${post.pagenames[lang]+'.txt'}" id="sourcelink">${messages[lang]["Source"]}</a>
+ % if not post.meta('password'):
+ <a href="${post.meta('slug')+'.txt'}" id="sourcelink">${messages("Source")}</a>
+ % endif
${helper.html_tags(post)}
</small>
<hr>
- ${post.text(lang)}
+ ${post.text()}
${helper.html_pager(post)}
- ${disqus.html_disqus(post.permalink(absolute=True), post.title(lang), post.base_path)}
+ % if not post.meta('nocomments'):
+ ${disqus.html_disqus(post.permalink(absolute=True), post.title(), post.base_path)}
+ % endif
</div>
</%block>
diff --git a/nikola/data/themes/orphan/templates/post_helper.tmpl b/nikola/data/themes/orphan/templates/post_helper.tmpl
index a3dc75f..cce0ecf 100644
--- a/nikola/data/themes/orphan/templates/post_helper.tmpl
+++ b/nikola/data/themes/orphan/templates/post_helper.tmpl
@@ -2,7 +2,7 @@
<%def name="html_title()">
<h1>${title}</h1>
% if link:
- <p><a href='${link}'>${messages[lang]["Original site"]}</a></p>
+ <p><a href='${link}'>${messages("Original site")}</a></p>
% endif
</%def>
@@ -10,9 +10,9 @@
<%def name="html_translations(post)">
%if len(translations) > 1:
%for langname in translations.keys():
- %if langname != lang and post.is_translation_available(langname):
+ %if langname != lang and post.is_translation_available(langname):
&nbsp;&nbsp;|&nbsp;&nbsp;
- <a href="${post.permalink(langname)}">${messages[langname]["Read in English"]}</a>
+ <a href="${post.permalink(langname)}">${messages("Read in English", langname)}</a>
%endif
%endfor
%endif
@@ -21,25 +21,53 @@
<%def name="html_tags(post)">
%if post.tags:
- &nbsp;&nbsp;|&nbsp;&nbsp;${messages[lang]["More posts about"]}
+ &nbsp;&nbsp;|&nbsp;&nbsp;${messages("More posts about")}
%for tag in post.tags:
- <a class="tag" href="${_link('tag', tag, lang)}"><span class="badge badge-info">${tag}</span></a>
+ <a class="tag" href="${_link('tag', tag)}"><span class="badge badge-info">${tag}</span></a>
%endfor
%endif
</%def>
-
<%def name="html_pager(post)">
<ul class="pager">
%if post.prev_post:
<li class="previous">
- <a href="${post.prev_post.permalink(lang)}">&larr; ${messages[lang]["Previous post"]}</a>
+ <a href="${post.prev_post.permalink()}">&larr; ${messages("Previous post")}</a>
</li>
%endif
%if post.next_post:
<li class="next">
- <a href="${post.next_post.permalink(lang)}">${messages[lang]["Next post"]} &rarr;</a>
+ <a href="${post.next_post.permalink()}">${messages("Next post")} &rarr;</a>
</li>
%endif
</ul>
</%def>
+
+<%def name="twitter_card_information(post)">
+ %if twitter_card and twitter_card['use_twitter_cards']:
+ <meta name="twitter:card" content="${twitter_card.get('card', 'summary')|h}">
+ <meta name="og:url" content="${post.permalink(absolute=True)}">
+ %if 'site:id' in twitter_card:
+ <meta name="twitter:site:id" content="${twitter_card['site:id']}">
+ %elif 'site' in twitter_card:
+ <meta name="twitter:site" content="${twitter_card['site']}">
+ %endif
+ %if 'creator:id' in twitter_card:
+ <meta name="twitter:creator:id" content="${twitter_card['creator:id']}">
+ %elif 'creator' in twitter_card:
+ <meta name="twitter:creator" content="${twitter_card['creator']}">
+ %endif
+ <meta name="og:title" content="${post.title()[:70]|h}">
+ %if post.description():
+ <meta name="og:description" content="${post.description()[:200]|h}">
+ %else:
+ <meta name="og:description" content="${post.text(strip_html=True)[:200]|h}">
+ %endif
+ %endif
+</%def>
+
+<%def name="mathjax_script(post)">
+ %if post.is_mathjax:
+ <script src="/assets/js/mathjax.js" type="text/javascript"></script>
+ %endif
+</%def>
diff --git a/nikola/data/themes/orphan/templates/story.tmpl b/nikola/data/themes/orphan/templates/story.tmpl
index 30d263b..21d0e2f 100644
--- a/nikola/data/themes/orphan/templates/story.tmpl
+++ b/nikola/data/themes/orphan/templates/story.tmpl
@@ -1,11 +1,15 @@
## -*- coding: utf-8 -*-
<%inherit file="post.tmpl"/>
+<%namespace name="helper" file="post_helper.tmpl"/>
+<%block name="extra_head">
+${helper.twitter_card_information(post)}
+</%block>
<%block name="content">
%if title:
<h1>${title}</h1>
%endif
- ${post.text(lang)}
-%if enable_comments:
- ${disqus.html_disqus(post.permalink(absolute=True), post.title(lang), post.base_path)}
+ ${post.text()}
+%if enable_comments and not post.meta('nocomments'):
+ ${disqus.html_disqus(post.permalink(absolute=True), post.title(), post.base_path)}
%endif
</%block>
diff --git a/nikola/data/themes/orphan/templates/tag.tmpl b/nikola/data/themes/orphan/templates/tag.tmpl
index 7c89ad1..97aafeb 100644
--- a/nikola/data/themes/orphan/templates/tag.tmpl
+++ b/nikola/data/themes/orphan/templates/tag.tmpl
@@ -2,6 +2,6 @@
<%inherit file="list_post.tmpl"/>
<%block name="extra_head">
%for language in translations:
- <link rel="alternate" type="application/rss+xml" type="application/rss+xml" title="RSS for tag ${tag} (${language})" href="${_link("tag_rss", tag, lang)}">
+ <link rel="alternate" type="application/rss+xml" type="application/rss+xml" title="RSS for tag ${tag} (${language})" href="${_link("tag_rss", tag, language)}">
%endfor
</%block>
diff --git a/nikola/data/themes/site-planetoid/README b/nikola/data/themes/site-planetoid/README
new file mode 100644
index 0000000..c148591
--- /dev/null
+++ b/nikola/data/themes/site-planetoid/README
@@ -0,0 +1 @@
+A version of the site theme for the use with the "planetoid" plugin.
diff --git a/nikola/data/themes/site-planetoid/engine b/nikola/data/themes/site-planetoid/engine
new file mode 100644
index 0000000..2951cdd
--- /dev/null
+++ b/nikola/data/themes/site-planetoid/engine
@@ -0,0 +1 @@
+mako
diff --git a/nikola/data/themes/site-planetoid/parent b/nikola/data/themes/site-planetoid/parent
new file mode 100644
index 0000000..1320f90
--- /dev/null
+++ b/nikola/data/themes/site-planetoid/parent
@@ -0,0 +1 @@
+site
diff --git a/nikola/data/themes/site-planetoid/templates/index.tmpl b/nikola/data/themes/site-planetoid/templates/index.tmpl
new file mode 100644
index 0000000..29243e0
--- /dev/null
+++ b/nikola/data/themes/site-planetoid/templates/index.tmpl
@@ -0,0 +1,16 @@
+## -*- coding: utf-8 -*-
+<%namespace name="helper" file="index_helper.tmpl"/>
+<%inherit file="base.tmpl"/>
+<%block name="content">
+ % for post in posts:
+ <div style="border: 2px solid darkgrey; margin-bottom: 12px; border-radius: 4px; padding:12px; overflow: auto;">
+ <a href="${post.meta('link')}"><h1>${post.title(lang)}</a>
+ <small>&nbsp;&nbsp;
+ ${messages("Posted")}: <time class="published" datetime="${post.date.isoformat()}">${post.formatted_date(date_format)}</time>
+ </small></h1>
+ ${post.text(lang)}
+ </div>
+ % endfor
+ ${helper.html_pager()}
+</ul>
+</%block>
diff --git a/nikola/data/themes/site-planetoid/templates/post.tmpl b/nikola/data/themes/site-planetoid/templates/post.tmpl
new file mode 100644
index 0000000..d60de78
--- /dev/null
+++ b/nikola/data/themes/site-planetoid/templates/post.tmpl
@@ -0,0 +1,9 @@
+## -*- coding: utf-8 -*-
+<html>
+<head>
+<meta http-equiv="Refresh" content="0;url=${post.meta('link')}">
+</head>
+<body>
+Redirecting you to <a href="${post.meta('link')}">the original location.</a>
+</body>
+</html>
diff --git a/nikola/data/themes/site-planetoid/templates/story.tmpl b/nikola/data/themes/site-planetoid/templates/story.tmpl
new file mode 100644
index 0000000..7712e71
--- /dev/null
+++ b/nikola/data/themes/site-planetoid/templates/story.tmpl
@@ -0,0 +1,25 @@
+## -*- coding: utf-8 -*-
+<%namespace name="helper" file="post_helper.tmpl"/>
+<%namespace name="disqus" file="disqus_helper.tmpl"/>
+<%inherit file="base.tmpl"/>
+<%block name="extra_head">
+${helper.twitter_card_information(post)}
+</%block>
+
+<%block name="content">
+%if title:
+ <h1>${title}</h1>
+%endif
+ ${post.text()}
+%if enable_comments and not post.meta('nocomments'):
+ ${disqus.html_disqus(post.permalink(absolute=True), post.title(), post.base_path)}
+%endif
+</%block>
+
+<%block name="sourcelink">
+% if not post.meta('password'):
+ <li>
+ <a href="${post.meta('slug')+post.source_ext()}" id="sourcelink">${messages("Source")}</a>
+ </li>
+% endif
+</%block>
diff --git a/nikola/data/themes/site/assets/css/theme.css b/nikola/data/themes/site/assets/css/theme.css
index aa0ee4a..24072ac 100644
--- a/nikola/data/themes/site/assets/css/theme.css
+++ b/nikola/data/themes/site/assets/css/theme.css
@@ -64,3 +64,17 @@ blockquote p, blockquote {
line-height: 1.25;
}
+ul.bricks > li {
+ display: inline;
+ background-color: lightblue;
+ padding: 8px;
+ border-radius: 5px;
+ line-height: 3;
+ white-space:nowrap;
+ margin: 3px;
+}
+
+h1, h2, h3, h4, h5, h6, h7 {
+ margin-top: -40px;
+ padding-top: 40px;
+}
diff --git a/nikola/data/themes/site/templates/base.tmpl b/nikola/data/themes/site/templates/base.tmpl
index 416d04b..4efd0ad 100644
--- a/nikola/data/themes/site/templates/base.tmpl
+++ b/nikola/data/themes/site/templates/base.tmpl
@@ -1,5 +1,6 @@
## -*- coding: utf-8 -*-
<%namespace file="base_helper.tmpl" import="*"/>
+${set_locale(lang)}
<!DOCTYPE html>
<html lang="${lang}">
<head>
@@ -7,20 +8,21 @@
${html_head()}
<%block name="extra_head">
</%block>
+ ${extra_head_data}
</head>
<body>
<!-- Menubar -->
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
-
+
<!-- .btn-navbar is used as the toggle for collapsed navbar content -->
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
- </a>
-
+ </a>
+
<a class="brand" href="${abs_link('/')}">
${blog_title}
</a>
@@ -52,14 +54,14 @@
<div class="span8">
<%block name="content"></%block>
</div>
- </div>
+ </div>
<!--End of body content-->
</div>
<div class="footerbox">
${content_footer}
</div>
${html_social()}
-${analytics}
${late_load_js()}
+${analytics}
<script type="text/javascript">jQuery("a.image-reference").colorbox({rel:"gal",maxWidth:"80%",maxHeight:"80%",scalePhotos:true});</script>
</body>
diff --git a/nikola/main.py b/nikola/main.py
index b390387..8263b7e 100644
--- a/nikola/main.py
+++ b/nikola/main.py
@@ -35,16 +35,13 @@ from doit.cmd_help import Help as DoitHelp
from doit.cmd_run import Run as DoitRun
from .nikola import Nikola
+from .utils import _reload
def main(args):
sys.path.append('')
try:
import conf
- if sys.version_info[0] > 2:
- from imp import reload as _reload
- else:
- _reload = reload # NOQA
_reload(conf)
config = conf.__dict__
except ImportError:
@@ -111,7 +108,7 @@ class DoitNikola(DoitMain):
sub_cmds = self.get_commands()
args = self.process_args(cmd_args)
- if len(args) == 0 or args == ["--help"]:
+ if len(args) == 0 or any(arg in ["--help", '-h'] for arg in args):
cmd_args = ['help']
args = ['help']
diff --git a/nikola/nikola.py b/nikola/nikola.py
index a1506e7..8660a0f 100644
--- a/nikola/nikola.py
+++ b/nikola/nikola.py
@@ -28,12 +28,14 @@ from collections import defaultdict
from copy import copy
import glob
import gzip
+import locale
import os
import sys
try:
from urlparse import urlparse, urlsplit, urljoin
except ImportError:
from urllib.parse import urlparse, urlsplit, urljoin # NOQA
+import warnings
import lxml.html
from yapsy.PluginManager import PluginManager
@@ -67,12 +69,19 @@ class Nikola(object):
Takes a site config as argument on creation.
"""
+ EXTRA_PLUGINS = [
+ 'planetoid',
+ 'ipynb',
+ 'local_search',
+ 'render_mustache',
+ ]
def __init__(self, **config):
"""Setup proper environment for running tasks."""
self.global_data = {}
self.posts_per_year = defaultdict(list)
+ self.posts_per_month = defaultdict(list)
self.posts_per_tag = defaultdict(list)
self.timeline = []
self.pages = []
@@ -83,21 +92,24 @@ class Nikola(object):
self.configured = True
# This is the default config
- # TODO: fill it
self.config = {
'ADD_THIS_BUTTONS': True,
'ANALYTICS': '',
'ARCHIVE_PATH': "",
'ARCHIVE_FILENAME': "archive.html",
'CACHE_FOLDER': 'cache',
+ 'CODE_COLOR_SCHEME': 'default',
'COMMENTS_IN_GALLERIES': False,
'COMMENTS_IN_STORIES': False,
'CONTENT_FOOTER': '',
+ 'CREATE_MONTHLY_ARCHIVE': False,
'DATE_FORMAT': '%Y-%m-%d %H:%M',
'DEFAULT_LANG': "en",
'DEPLOY_COMMANDS': [],
'DISABLED_PLUGINS': (),
'DISQUS_FORUM': 'nikolademo',
+ 'ENABLED_EXTRAS': (),
+ 'EXTRA_HEAD_DATA': '',
'FAVICONS': {},
'FILE_METADATA_REGEXP': None,
'FILES_FOLDERS': {'files': ''},
@@ -105,6 +117,7 @@ class Nikola(object):
'GALLERY_PATH': 'galleries',
'GZIP_FILES': False,
'GZIP_EXTENSIONS': ('.txt', '.htm', '.html', '.css', '.js', '.json'),
+ 'HIDE_UNTRANSLATED_POSTS': False,
'INDEX_DISPLAY_POST_COUNT': 10,
'INDEX_TEASERS': False,
'INDEXES_TITLE': "",
@@ -114,6 +127,7 @@ class Nikola(object):
'LISTINGS_FOLDER': 'listings',
'MAX_IMAGE_SIZE': 1280,
'MATHJAX_CONFIG': '',
+ 'OLD_THEME_SUPPORT': True,
'OUTPUT_FOLDER': 'output',
'post_compilers': {
"rest": ('.txt', '.rst'),
@@ -136,6 +150,7 @@ class Nikola(object):
'SEARCH_FORM': '',
'SLUG_TAG_PATH': True,
'STORY_INDEX': False,
+ 'STRIP_INDEX_HTML': False,
'TAG_PATH': 'categories',
'TAG_PAGES_ARE_INDEXES': False,
'THEME': 'site',
@@ -156,16 +171,19 @@ class Nikola(object):
self.THEMES = utils.get_theme_chain(self.config['THEME'])
self.MESSAGES = utils.load_messages(self.THEMES,
- self.config['TRANSLATIONS'])
+ self.config['TRANSLATIONS'],
+ self.config['DEFAULT_LANG'])
# SITE_URL is required, but if the deprecated BLOG_URL
# is available, use it and warn
if 'SITE_URL' not in self.config:
if 'BLOG_URL' in self.config:
print("WARNING: You should configure SITE_URL instead of BLOG_URL")
- print("See docs at FIXME put URL")
self.config['SITE_URL'] = self.config['BLOG_URL']
+ self.default_lang = self.config['DEFAULT_LANG']
+ self.translations = self.config['TRANSLATIONS']
+
# BASE_URL defaults to SITE_URL
if 'BASE_URL' not in self.config:
self.config['BASE_URL'] = self.config.get('SITE_URL')
@@ -187,23 +205,28 @@ class Nikola(object):
self.commands = {}
# Activate all command plugins
- for pluginInfo in self.plugin_manager.getPluginsOfCategory("Command"):
- if pluginInfo.name in self.config['DISABLED_PLUGINS']:
- self.plugin_manager.removePluginFromCategory(pluginInfo, "Command")
+ for plugin_info in self.plugin_manager.getPluginsOfCategory("Command"):
+ if (plugin_info.name in self.config['DISABLED_PLUGINS']
+ or (plugin_info.name in self.EXTRA_PLUGINS and
+ plugin_info.name not in self.config['ENABLED_EXTRAS'])):
+ self.plugin_manager.removePluginFromCategory(plugin_info, "Command")
continue
- self.plugin_manager.activatePluginByName(pluginInfo.name)
- pluginInfo.plugin_object.set_site(self)
- pluginInfo.plugin_object.short_help = pluginInfo.description
- self.commands[pluginInfo.name] = pluginInfo.plugin_object
+
+ self.plugin_manager.activatePluginByName(plugin_info.name)
+ plugin_info.plugin_object.set_site(self)
+ plugin_info.plugin_object.short_help = plugin_info.description
+ self.commands[plugin_info.name] = plugin_info.plugin_object
# Activate all task plugins
for task_type in ["Task", "LateTask"]:
- for pluginInfo in self.plugin_manager.getPluginsOfCategory(task_type):
- if pluginInfo.name in self.config['DISABLED_PLUGINS']:
- self.plugin_manager.removePluginFromCategory(pluginInfo, task_type)
+ for plugin_info in self.plugin_manager.getPluginsOfCategory(task_type):
+ if (plugin_info.name in self.config['DISABLED_PLUGINS']
+ or (plugin_info.name in self.EXTRA_PLUGINS and
+ plugin_info.name not in self.config['ENABLED_EXTRAS'])):
+ self.plugin_manager.removePluginFromCategory(plugin_info, task_type)
continue
- self.plugin_manager.activatePluginByName(pluginInfo.name)
- pluginInfo.plugin_object.set_site(self)
+ self.plugin_manager.activatePluginByName(plugin_info.name)
+ plugin_info.plugin_object.set_site(self)
# set global_context for template rendering
self.GLOBAL_CONTEXT = {
@@ -211,6 +234,7 @@ class Nikola(object):
self.GLOBAL_CONTEXT['messages'] = self.MESSAGES
self.GLOBAL_CONTEXT['_link'] = self.link
+ self.GLOBAL_CONTEXT['set_locale'] = s_l
self.GLOBAL_CONTEXT['rel_link'] = self.rel_link
self.GLOBAL_CONTEXT['abs_link'] = self.abs_link
self.GLOBAL_CONTEXT['exists'] = self.file_exists
@@ -244,9 +268,14 @@ class Nikola(object):
'CONTENT_FOOTER')
self.GLOBAL_CONTEXT['rss_path'] = self.config.get('RSS_PATH')
self.GLOBAL_CONTEXT['rss_link'] = self.config.get('RSS_LINK')
- self.GLOBAL_CONTEXT['sidebar_links'] = self.config.get('SIDEBAR_LINKS')
+
+ self.GLOBAL_CONTEXT['sidebar_links'] = utils.Functionary(list, self.config['DEFAULT_LANG'])
+ for k, v in self.config.get('SIDEBAR_LINKS', {}).items():
+ self.GLOBAL_CONTEXT['sidebar_links'][k] = v
+
self.GLOBAL_CONTEXT['twitter_card'] = self.config.get(
'TWITTER_CARD', {})
+ self.GLOBAL_CONTEXT['extra_head_data'] = self.config.get('EXTRA_HEAD_DATA')
self.GLOBAL_CONTEXT.update(self.config.get('GLOBAL_CONTEXT', {}))
@@ -273,14 +302,21 @@ class Nikola(object):
self.template_system.set_directories(lookup_dirs,
self.config['CACHE_FOLDER'])
+ # Check consistency of USE_CDN and the current THEME (Issue #386)
+ if self.config['USE_CDN']:
+ bootstrap_path = utils.get_asset_path(os.path.join(
+ 'assets', 'css', 'bootstrap.min.css'), self.THEMES)
+ if bootstrap_path.split(os.sep)[-4] != 'site':
+ warnings.warn('The USE_CDN option may be incompatible with your theme, because it uses a hosted version of bootstrap.')
+
# Load compiler plugins
self.compilers = {}
self.inverse_compilers = {}
- for pluginInfo in self.plugin_manager.getPluginsOfCategory(
+ for plugin_info in self.plugin_manager.getPluginsOfCategory(
"PageCompiler"):
- self.compilers[pluginInfo.name] = \
- pluginInfo.plugin_object.compile_html
+ self.compilers[plugin_info.name] = \
+ plugin_info.plugin_object.compile_html
def get_compiler(self, source_name):
"""Get the correct compiler for a post from `conf.post_compilers`
@@ -324,11 +360,9 @@ class Nikola(object):
data = self.template_system.render_template(
template_name, None, local_context)
- assert isinstance(output_name, bytes)
assert output_name.startswith(
- self.config["OUTPUT_FOLDER"].encode('utf8'))
- url_part = output_name.decode('utf8')[len(self.config["OUTPUT_FOLDER"])
- + 1:]
+ self.config["OUTPUT_FOLDER"])
+ url_part = output_name[len(self.config["OUTPUT_FOLDER"]) + 1:]
# Treat our site as if output/ is "/" and then make all URLs relative,
# making the site "relocatable"
@@ -392,7 +426,20 @@ class Nikola(object):
with open(output_name, "wb+") as post_file:
post_file.write(data)
- def path(self, kind, name, lang, is_link=False):
+ def current_lang(self): # FIXME: this is duplicated, turn into a mixin
+ """Return the currently set locale, if it's one of the
+ available translations, or default_lang."""
+ lang = utils.LocaleBorg().current_lang
+ if lang:
+ if lang in self.translations:
+ return lang
+ lang = lang.split('_')[0]
+ if lang in self.translations:
+ return lang
+ # whatever
+ return self.default_lang
+
+ def path(self, kind, name, lang=None, is_link=False):
"""Build the path to a certain kind of page.
kind is one of:
@@ -417,6 +464,9 @@ class Nikola(object):
(ex: "archive\\index.html")
"""
+ if lang is None:
+ lang = self.current_lang()
+
path = []
if kind == "tag_index":
@@ -465,7 +515,11 @@ class Nikola(object):
path = [_f for _f in [self.config['LISTINGS_FOLDER'], name +
'.html'] if _f]
if is_link:
- return '/' + ('/'.join(path))
+ link = '/' + ('/'.join(path))
+ if self.config['STRIP_INDEX_HTML'] and link.endswith('/index.html'):
+ return link[:-10]
+ else:
+ return link
else:
return os.path.join(*path)
@@ -528,12 +582,14 @@ class Nikola(object):
def add_gzipped_copies(task):
if not self.config['GZIP_FILES']:
return None
+ if task.get('name') is None:
+ return None
gzip_task = {
'file_dep': [],
'targets': [],
'actions': [],
'basename': 'gzip',
- 'name': task.get('name', 'unknown'),
+ 'name': task.get('name') + '.gz',
'clean': True,
}
targets = task.get('targets', [])
@@ -597,7 +653,18 @@ class Nikola(object):
dir_glob = os.path.join(dirpath, os.path.basename(wildcard))
dest_dir = os.path.normpath(os.path.join(destination,
os.path.relpath(dirpath, dirname)))
- for base_path in glob.glob(dir_glob):
+ full_list = glob.glob(dir_glob)
+ # Now let's look for things that are not in default_lang
+ for lang in self.config['TRANSLATIONS'].keys():
+ lang_glob = dir_glob + "." + lang
+ translated_list = glob.glob(lang_glob)
+ for fname in translated_list:
+ orig_name = os.path.splitext(fname)[0]
+ if orig_name in full_list:
+ continue
+ full_list.append(orig_name)
+
+ for base_path in full_list:
post = Post(
base_path,
self.config['CACHE_FOLDER'],
@@ -609,26 +676,32 @@ class Nikola(object):
self.MESSAGES,
template_name,
self.config['FILE_METADATA_REGEXP'],
+ self.config['STRIP_INDEX_HTML'],
tzinfo,
+ self.config['HIDE_UNTRANSLATED_POSTS'],
)
for lang, langpath in list(
self.config['TRANSLATIONS'].items()):
dest = (destination, langpath, dir_glob,
- post.pagenames[lang])
+ post.meta[lang]['slug'])
if dest in targets:
raise Exception('Duplicated output path {0!r} '
'in post {1!r}'.format(
- post.pagenames[lang],
+ post.meta[lang]['slug'],
base_path))
targets.add(dest)
self.global_data[post.post_name] = post
if post.use_in_feeds:
self.posts_per_year[
str(post.date.year)].append(post.post_name)
- for tag in post.tags:
+ self.posts_per_month[
+ '{0}/{1:02d}'.format(post.date.year, post.date.month)].append(post.post_name)
+ for tag in post.alltags:
self.posts_per_tag[tag].append(post.post_name)
else:
self.pages.append(post)
+ if self.config['OLD_THEME_SUPPORT']:
+ post._add_old_metadata()
for name, post in list(self.global_data.items()):
self.timeline.append(post)
self.timeline.sort(key=lambda p: p.date)
@@ -657,7 +730,7 @@ class Nikola(object):
else:
context['enable_comments'] = self.config['COMMENTS_IN_STORIES']
output_name = os.path.join(self.config['OUTPUT_FOLDER'],
- post.destination_path(lang)).encode('utf8')
+ post.destination_path(lang))
deps_dict = copy(context)
deps_dict.pop('post')
if post.prev_post:
@@ -668,9 +741,11 @@ class Nikola(object):
deps_dict['TRANSLATIONS'] = self.config['TRANSLATIONS']
deps_dict['global'] = self.GLOBAL_CONTEXT
deps_dict['comments'] = context['enable_comments']
+ if post:
+ deps_dict['post_translations'] = post.translated_to
task = {
- 'name': output_name,
+ 'name': os.path.normpath(output_name),
'file_dep': deps,
'targets': [output_name],
'actions': [(self.render_template, [post.template_name,
@@ -685,9 +760,6 @@ class Nikola(object):
template_name, filters, extra_context):
"""Renders pages with lists of posts."""
- # This is a name on disk, has to be bytes
- assert isinstance(output_name, bytes)
-
deps = self.template_system.template_deps(template_name)
for post in posts:
deps += post.deps(lang)
@@ -700,11 +772,11 @@ class Nikola(object):
context["nextlink"] = None
context.update(extra_context)
deps_context = copy(context)
- deps_context["posts"] = [(p.titles[lang], p.permalink(lang)) for p in
+ deps_context["posts"] = [(p.meta[lang]['title'], p.permalink(lang)) for p in
posts]
deps_context["global"] = self.GLOBAL_CONTEXT
task = {
- 'name': output_name,
+ 'name': os.path.normpath(output_name),
'targets': [output_name],
'file_dep': deps,
'actions': [(self.render_template, [template_name, output_name,
@@ -714,3 +786,14 @@ class Nikola(object):
}
return utils.apply_filters(task, filters)
+
+
+def s_l(lang):
+ """A set_locale that uses utf8 encoding and returns ''."""
+ utils.LocaleBorg().current_lang = lang
+ try:
+ locale.setlocale(locale.LC_ALL, (lang, "utf8"))
+ except Exception:
+ print("WARNING: could not set locale to {0}."
+ "This may cause some i18n features not to work.".format(lang))
+ return ''
diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py
index cff9b65..c4ca788 100644
--- a/nikola/plugin_categories.py
+++ b/nikola/plugin_categories.py
@@ -145,12 +145,19 @@ class PageCompiler(object):
"""Plugins that compile text files into HTML."""
name = "dummy compiler"
+ default_metadata = {
+ 'title': '',
+ 'slug': '',
+ 'date': '',
+ 'tags': '',
+ 'link': '',
+ 'description': '',
+ }
def compile_html(self, source, dest):
"""Compile the source, save it on dest."""
raise NotImplementedError()
- def create_post(self, path, onefile=False, title="", slug="", date="",
- tags=""):
+ def create_post(self, path, onefile=False, **kw):
"""Create post file with optional metadata."""
raise NotImplementedError()
diff --git a/nikola/plugins/command_check.py b/nikola/plugins/command_check.py
index a396f63..ea82703 100644
--- a/nikola/plugins/command_check.py
+++ b/nikola/plugins/command_check.py
@@ -24,6 +24,7 @@
from __future__ import print_function
import os
+import sys
try:
from urllib import unquote
from urlparse import urlparse
@@ -74,14 +75,17 @@ class CommandCheck(Command):
print(self.help())
return False
if options['links']:
- scan_links(options['find_sources'])
+ failure = scan_links(options['find_sources'])
if options['files']:
- scan_files()
+ failure = scan_files()
+ if failure:
+ sys.exit(1)
existing_targets = set([])
def analize(task, find_sources=False):
+ rv = False
try:
filename = task.split(":")[-1]
d = lxml.html.fromstring(open(filename).read())
@@ -100,6 +104,7 @@ def analize(task, find_sources=False):
if os.path.exists(target_filename):
existing_targets.add(target_filename)
else:
+ rv = True
print("Broken link in {0}: ".format(filename), target)
if find_sources:
print("Possible sources:")
@@ -109,17 +114,21 @@ def analize(task, find_sources=False):
except Exception as exc:
print("Error with:", filename, exc)
+ return rv
def scan_links(find_sources=False):
print("Checking Links:\n===============\n")
+ failure = False
for task in os.popen('nikola list --all', 'r').readlines():
task = task.strip()
if task.split(':')[0] in ('render_tags', 'render_archive',
'render_galleries', 'render_indexes',
- 'render_pages',
+ 'render_pages'
'render_site') and '.html' in task:
- analize(task, find_sources)
+ if analize(task, find_sources):
+ failure = True
+ return failure
def scan_files():
@@ -127,6 +136,7 @@ def scan_files():
task_fnames = set([])
real_fnames = set([])
# First check that all targets are generated in the right places
+ failure = False
for task in os.popen('nikola list --all', 'r').readlines():
task = task.strip()
if 'output' in task and ':' in task:
@@ -144,6 +154,7 @@ def scan_files():
print("\nFiles from unknown origins:\n")
for f in only_on_output:
print(f)
+ failure = True
only_on_input = list(task_fnames - real_fnames)
if only_on_input:
@@ -151,3 +162,5 @@ def scan_files():
print("\nFiles not generated:\n")
for f in only_on_input:
print(f)
+
+ return failure
diff --git a/nikola/plugins/command_console.py b/nikola/plugins/command_console.py
index 4af759f..f4d0295 100644
--- a/nikola/plugins/command_console.py
+++ b/nikola/plugins/command_console.py
@@ -29,35 +29,77 @@ import os
from nikola.plugin_categories import Command
-class Deploy(Command):
+class Console(Command):
"""Start debugging console."""
name = "console"
+ shells = ['ipython', 'bpython', 'plain']
+ doc_purpose = "Start an interactive python console with access to your site and configuration."
- def _execute(self, options, args):
- """Start the console."""
+ def ipython(self):
+ """IPython shell."""
from nikola import Nikola
try:
import conf
+ except ImportError:
+ print("No configuration found, cannot run the console.")
+ else:
+ import IPython
SITE = Nikola(**conf.__dict__)
SITE.scan_posts()
- print("You can now access your configuration as conf and your "
- "site engine as SITE.")
+ IPython.embed(header='Nikola Console (conf = configuration, SITE '
+ '= site engine)')
+
+ def bpython(self):
+ """bpython shell."""
+ from nikola import Nikola
+ try:
+ import conf
except ImportError:
- print("No configuration found.")
- import code
+ print("No configuration found, cannot run the console.")
+ else:
+ import bpython
+ SITE = Nikola(**conf.__dict__)
+ SITE.scan_posts()
+ gl = {'conf': conf, 'SITE': SITE, 'Nikola': Nikola}
+ bpython.embed(banner='Nikola Console (conf = configuration, SITE '
+ '= site engine)', locals_=gl)
+
+ def plain(self):
+ """Plain Python shell."""
+ from nikola import Nikola
try:
- import readline
+ import conf
+ SITE = Nikola(**conf.__dict__)
+ SITE.scan_posts()
+ gl = {'conf': conf, 'SITE': SITE, 'Nikola': Nikola}
except ImportError:
- pass
+ print("No configuration found, cannot run the console.")
else:
- import rlcompleter
- readline.set_completer(rlcompleter.Completer(globals()).complete)
- readline.parse_and_bind("tab:complete")
+ import code
+ try:
+ import readline
+ except ImportError:
+ pass
+ else:
+ import rlcompleter
+ readline.set_completer(rlcompleter.Completer(gl).complete)
+ readline.parse_and_bind("tab:complete")
+
+ pythonrc = os.environ.get("PYTHONSTARTUP")
+ if pythonrc and os.path.isfile(pythonrc):
+ try:
+ execfile(pythonrc) # NOQA
+ except NameError:
+ pass
+
+ code.interact(local=gl, banner='Nikola Console (conf = '
+ 'configuration, SITE = site engine)')
- pythonrc = os.environ.get("PYTHONSTARTUP")
- if pythonrc and os.path.isfile(pythonrc):
+ def _execute(self, options, args):
+ """Start the console."""
+ for shell in self.shells:
try:
- execfile(pythonrc) # NOQA
- except NameError:
+ return getattr(self, shell)()
+ except ImportError:
pass
- code.interact(local=globals())
+ raise ImportError
diff --git a/nikola/plugins/command_deploy.py b/nikola/plugins/command_deploy.py
index ffa86ab..3277567 100644
--- a/nikola/plugins/command_deploy.py
+++ b/nikola/plugins/command_deploy.py
@@ -23,7 +23,12 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function
+from ast import literal_eval
+import codecs
+from datetime import datetime
import os
+import subprocess
+
from nikola.plugin_categories import Command
@@ -37,5 +42,24 @@ class Deploy(Command):
def _execute(self, command, args):
for command in self.site.config['DEPLOY_COMMANDS']:
+
+ # Get last succesful deploy date
+ timestamp_path = os.path.join(self.site.config['CACHE_FOLDER'], 'lastdeploy')
+ try:
+ with open(timestamp_path, 'rb') as inf:
+ last_deploy = literal_eval(inf.read().strip())
+ except Exception:
+ last_deploy = datetime(1970, 1, 1) # NOQA
+
print("==>", command)
- os.system(command)
+ ret = subprocess.check_call(command, shell=True)
+ if ret != 0: # failed deployment
+ raise Exception("Failed deployment")
+ print("Successful deployment")
+ new_deploy = datetime.now()
+ # Store timestamp of successful deployment
+ with codecs.open(timestamp_path, 'wb+', 'utf8') as outf:
+ outf.write(repr(new_deploy))
+ # Here is where we would do things with whatever is
+ # on self.site.timeline and is newer than
+ # last_deploy
diff --git a/nikola/plugins/command_import_blogger.py b/nikola/plugins/command_import_blogger.py
index 35a702e..ecc4676 100644
--- a/nikola/plugins/command_import_blogger.py
+++ b/nikola/plugins/command_import_blogger.py
@@ -73,7 +73,7 @@ class CommandImportBlogger(Command):
]
def _execute(self, options, args):
- """Import a Wordpress blog from an export file into a Nikola site."""
+ """Import a Blogger blog from an export file into a Nikola site."""
# Parse the data
if feedparser is None:
@@ -126,7 +126,7 @@ class CommandImportBlogger(Command):
def generate_base_site(self):
if not os.path.exists(self.output_folder):
- os.system('nikola init --empty ' + self.output_folder)
+ os.system('nikola init ' + self.output_folder)
else:
self.import_into_existing_site = True
print('The folder {0} already exists - assuming that this is a '
@@ -176,9 +176,16 @@ class CommandImportBlogger(Command):
@staticmethod
def write_metadata(filename, title, slug, post_date, description, tags):
+ if not description:
+ description = ""
+
with codecs.open(filename, "w+", "utf8") as fd:
- fd.write('\n'.join((title, slug, post_date, ','.join(tags), '',
- description)))
+ fd.write('{0}\n'.format(title))
+ fd.write('{0}\n'.format(slug))
+ fd.write('{0}\n'.format(post_date))
+ fd.write('{0}\n'.format(','.join(tags)))
+ fd.write('\n')
+ fd.write('{0}\n'.format(description))
def import_item(self, item, out_folder=None):
"""Takes an item from the feed and creates a post file."""
@@ -284,7 +291,7 @@ class CommandImportBlogger(Command):
if not self.import_into_existing_site:
filename = 'conf.py'
else:
- filename = 'conf.py.wordpress_import-{0}'.format(
+ filename = 'conf.py.blogger_import-{0}'.format(
datetime.datetime.now().strftime('%Y%m%d_%H%M%s'))
config_output_path = os.path.join(self.output_folder, filename)
print('Configuration will be written to: ' + config_output_path)
diff --git a/nikola/plugins/command_import_wordpress.py b/nikola/plugins/command_import_wordpress.py
index e7ecca0..b45fe78 100644
--- a/nikola/plugins/command_import_wordpress.py
+++ b/nikola/plugins/command_import_wordpress.py
@@ -90,7 +90,6 @@ class CommandImportWordpress(Command):
def _execute(self, options={}, args=[]):
"""Import a Wordpress blog from an export file into a Nikola site."""
# Parse the data
- print(options, args)
if requests is None:
print('To use the import_wordpress command,'
' you have to install the "requests" package.')
@@ -100,10 +99,16 @@ class CommandImportWordpress(Command):
print(self.help())
return
- options['filename'] = args[0]
+ options['filename'] = args.pop(0)
- if len(args) > 1:
- options['output_folder'] = args[1]
+ if args and ('output_folder' not in args or
+ options['output_folder'] == 'new_site'):
+ options['output_folder'] = args.pop(0)
+
+ if args:
+ print('You specified additional arguments ({0}). Please consider '
+ 'putting these arguments before the filename if you '
+ 'are running into problems.'.format(args))
self.wordpress_export_file = options['filename']
self.squash_newlines = options.get('squash_newlines', False)
@@ -204,8 +209,12 @@ class CommandImportWordpress(Command):
'PUT TITLE HERE')
context['BLOG_DESCRIPTION'] = get_text_tag(
channel, 'description', 'PUT DESCRIPTION HERE')
- context['SITE_URL'] = get_text_tag(channel, 'link', '#')
context['BASE_URL'] = get_text_tag(channel, 'link', '#')
+ if not context['BASE_URL']:
+ base_site_url = channel.find('{{{0}}}author'.format(wordpress_namespace))
+ context['BASE_URL'] = get_text_tag(base_site_url, None, "http://foo.com")
+ context['SITE_URL'] = context['BASE_URL']
+
author = channel.find('{{{0}}}author'.format(wordpress_namespace))
context['BLOG_EMAIL'] = get_text_tag(
author,
@@ -314,7 +323,13 @@ class CommandImportWordpress(Command):
# 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)
- slug = utils.slugify(urlparse(link).path)
+ path = urlparse(link).path
+
+ # In python 2, path is a str. slug requires a unicode
+ # object. Luckily, paths are also ASCII
+ if isinstance(path, utils.bytes_str):
+ path = path.decode('ASCII')
+ slug = utils.slugify(path)
if not slug: # it happens if the post has no "nice" URL
slug = get_text_tag(
item, '{{{0}}}post_name'.format(wordpress_namespace), None)
@@ -334,7 +349,10 @@ class CommandImportWordpress(Command):
item, '{http://purl.org/rss/1.0/modules/content/}encoded', '')
tags = []
- if status != 'publish':
+ if status == 'trash':
+ print('Trashed post "{0}" will not be imported.'.format(title))
+ return
+ elif status != 'publish':
tags.append('draft')
is_draft = True
else:
diff --git a/nikola/plugins/command_install_theme.py b/nikola/plugins/command_install_theme.py
index 04a2cce..2a0a0cc 100644
--- a/nikola/plugins/command_install_theme.py
+++ b/nikola/plugins/command_install_theme.py
@@ -64,6 +64,10 @@ class CommandInstallTheme(Command):
def _execute(self, options, args):
"""Install theme into current site."""
+ if requests is None:
+ print('This command requires the requests package be installed.')
+ return False
+
listing = options['list']
url = options['url']
if args:
diff --git a/nikola/plugins/command_new_post.py b/nikola/plugins/command_new_post.py
index a823da3..933a51a 100644
--- a/nikola/plugins/command_new_post.py
+++ b/nikola/plugins/command_new_post.py
@@ -49,13 +49,31 @@ def filter_post_pages(compiler, is_post, post_compilers, post_pages):
if not filtered:
type_name = "post" if is_post else "page"
- raise Exception("Can't find a way, using your configuration, to create"
+ raise Exception("Can't find a way, using your configuration, to create "
"a {0} in format {1}. You may want to tweak "
"post_compilers or post_pages in conf.py".format(
type_name, compiler))
return filtered[0]
+def get_default_compiler(is_post, post_compilers, post_pages):
+ """Given post_compilers and post_pages, return a reasonable
+ default compiler for this kind of post/page.
+ """
+
+ # First throw away all the post_pages with the wrong is_post
+ filtered = [entry for entry in post_pages if entry[3] == is_post]
+
+ # Get extensions in filtered post_pages until one matches a compiler
+ for entry in filtered:
+ extension = os.path.splitext(entry[0])[-1]
+ for compiler, extensions in post_compilers.items():
+ if extension in extensions:
+ return compiler
+ # No idea, back to default behaviour
+ return 'rest'
+
+
class CommandNewPost(Command):
"""Create a new post."""
@@ -105,7 +123,7 @@ class CommandNewPost(Command):
'short': 'f',
'long': 'format',
'type': str,
- 'default': 'rest',
+ 'default': '',
'help': 'Markup format for post, one of rest, markdown, wiki, '
'bbcode, html, textile, txt2tags',
}
@@ -140,6 +158,12 @@ class CommandNewPost(Command):
post_format = options['post_format']
+ if not post_format: # Issue #400
+ post_format = get_default_compiler(
+ is_post,
+ self.site.config['post_compilers'],
+ self.site.config['post_pages'])
+
if post_format not in compiler_names:
print("ERROR: Unknown post format " + post_format)
return
@@ -160,12 +184,14 @@ class CommandNewPost(Command):
title = sys.stdin.readline()
else:
print("Title:", title)
- if isinstance(title, bytes):
+ if isinstance(title, utils.bytes_str):
title = title.decode(sys.stdin.encoding)
title = title.strip()
if not path:
slug = utils.slugify(title)
else:
+ if isinstance(path, utils.bytes_str):
+ path = path.decode(sys.stdin.encoding)
slug = utils.slugify(os.path.splitext(os.path.basename(path))[0])
date = datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')
data = [title, slug, date, tags]
@@ -186,7 +212,9 @@ class CommandNewPost(Command):
d_name = os.path.dirname(txt_path)
if not os.path.exists(d_name):
os.makedirs(d_name)
- compiler_plugin.create_post(txt_path, onefile, title, slug, date, tags)
+ compiler_plugin.create_post(
+ txt_path, onefile, title=title,
+ slug=slug, date=date, tags=tags)
if not onefile: # write metadata file
with codecs.open(meta_path, "wb+", "utf8") as fd:
diff --git a/extra_plugins/command_planetoid.plugin b/nikola/plugins/command_planetoid.plugin
index 8636d49..8636d49 100644
--- a/extra_plugins/command_planetoid.plugin
+++ b/nikola/plugins/command_planetoid.plugin
diff --git a/extra_plugins/command_planetoid/__init__.py b/nikola/plugins/command_planetoid/__init__.py
index 2428f10..183dd51 100644
--- a/extra_plugins/command_planetoid/__init__.py
+++ b/nikola/plugins/command_planetoid/__init__.py
@@ -29,30 +29,38 @@ import datetime
import hashlib
from optparse import OptionParser
import os
+import sys
from doit.tools import timeout
-import feedparser
-import peewee
-
from nikola.plugin_categories import Command, Task
from nikola.utils import config_changed
+try:
+ import feedparser
+except ImportError:
+ feedparser = None # NOQA
+
+try:
+ import peewee
+except ImportError:
+ peewee = None
-class Feed(peewee.Model):
- name = peewee.CharField()
- url = peewee.CharField(max_length=200)
- last_status = peewee.CharField(null=True)
- etag = peewee.CharField(max_length=200)
- last_modified = peewee.DateTimeField()
+if peewee is not None:
+ class Feed(peewee.Model):
+ name = peewee.CharField()
+ url = peewee.CharField(max_length=200)
+ last_status = peewee.CharField(null=True)
+ etag = peewee.CharField(max_length=200)
+ last_modified = peewee.DateTimeField()
-class Entry(peewee.Model):
- date = peewee.DateTimeField()
- feed = peewee.ForeignKeyField(Feed)
- content = peewee.TextField(max_length=20000)
- link = peewee.CharField(max_length=200)
- title = peewee.CharField(max_length=200)
- guid = peewee.CharField(max_length=200)
+ class Entry(peewee.Model):
+ date = peewee.DateTimeField()
+ feed = peewee.ForeignKeyField(Feed)
+ content = peewee.TextField(max_length=20000)
+ link = peewee.CharField(max_length=200)
+ title = peewee.CharField(max_length=200)
+ guid = peewee.CharField(max_length=200)
class Planetoid(Command, Task):
@@ -65,20 +73,41 @@ class Planetoid(Command, Task):
Entry.create_table(fail_silently=True)
def gen_tasks(self):
- self.init_db()
- for task in self.task_load_feeds():
- yield task
- for task in self.task_update_feeds():
- yield task
- for task in self.task_generate_posts():
- yield task
+ if peewee is None or sys.version_info[0] == 3:
+ if sys.version_info[0] == 3:
+ message = 'Peewee is currently incompatible with Python 3.'
+ else:
+ message = 'You need to install the \"peewee\" module.'
+
+ yield {
+ 'basename': self.name,
+ 'name': '',
+ 'verbosity': 2,
+ 'actions': ['echo "%s"' % message]
+ }
+ else:
+ self.init_db()
+ self.load_feeds()
+ for task in self.task_update_feeds():
+ yield task
+ for task in self.task_generate_posts():
+ yield task
+ yield {
+ 'basename': self.name,
+ 'name': '',
+ 'actions': [],
+ 'file_dep': ['feeds'],
+ 'task_dep': [
+ self.name + "_fetch_feed",
+ self.name + "_generate_posts",
+ ]
+ }
def run(self, *args):
- self.init_db()
parser = OptionParser(usage="nikola %s [options]" % self.name)
(options, args) = parser.parse_args(list(args))
- def task_load_feeds(self):
+ def load_feeds(self):
"Read the feeds file, add it to the database."
feeds = []
feed = name = None
@@ -110,18 +139,9 @@ class Planetoid(Command, Task):
for feed, name in feeds:
f = Feed.select().where(Feed.name == name)
if not list(f):
- yield {
- 'basename': self.name,
- 'name': name.encode('utf8'),
- 'actions': ((add_feed, (name, feed)), ),
- 'file_dep': ['feeds'],
- }
+ add_feed(name, feed)
elif list(f)[0].url != feed:
- yield {
- 'basename': self.name,
- 'name': ('updating_' + name).encode('utf8'),
- 'actions': ((update_feed_url, (list(f)[0], feed)), ),
- }
+ update_feed_url(list(f)[0], feed)
def task_update_feeds(self):
"""Download feed contents, add entries to the database."""
@@ -201,15 +221,23 @@ class Planetoid(Command, Task):
entry.content = content
entry.link = link
entry.save()
+ flag = False
for feed in Feed.select():
+ flag = True
task = {
- 'basename': self.name,
+ 'basename': self.name + "_fetch_feed",
'name': str(feed.url),
'actions': [(update_feed, (feed, ))],
'uptodate': [timeout(datetime.timedelta(minutes=
self.site.config.get('PLANETOID_REFRESH', 60)))],
}
yield task
+ if not flag:
+ yield {
+ 'basename': self.name + "_fetch_feed",
+ 'name': '',
+ 'actions': [],
+ }
def task_generate_posts(self):
"""Generate post files for the blog entries."""
@@ -237,12 +265,23 @@ class Planetoid(Command, Task):
for line in content.splitlines():
fd.write(' %s\n' % line)
+ if not os.path.isdir('posts'):
+ os.mkdir('posts')
+ flag = False
for entry in Entry.select().order_by(Entry.date.desc()):
+ flag = True
entry_id = gen_id(entry)
yield {
- 'basename': self.name,
+ 'basename': self.name + "_generate_posts",
'targets': [os.path.join('posts', entry_id + '.meta'), os.path.join('posts', entry_id + '.txt')],
'name': entry_id,
'actions': [(generate_post, (entry,))],
- 'uptodate': [config_changed({1: entry})]
+ 'uptodate': [config_changed({1: entry})],
+ 'task_dep': [self.name + "_fetch_feed"],
+ }
+ if not flag:
+ yield {
+ 'basename': self.name + "_generate_posts",
+ 'name': '',
+ 'actions': [],
}
diff --git a/nikola/plugins/compile_bbcode.py b/nikola/plugins/compile_bbcode.py
index 26de727..f8022f3 100644
--- a/nikola/plugins/compile_bbcode.py
+++ b/nikola/plugins/compile_bbcode.py
@@ -60,19 +60,17 @@ class CompileTextile(PageCompiler):
output = self.parser.format(data)
out_file.write(output)
- def create_post(self, path, onefile=False, title="", slug="", date="",
- tags=""):
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
d_name = os.path.dirname(path)
if not os.path.isdir(d_name):
os.makedirs(os.path.dirname(path))
with codecs.open(path, "wb+", "utf8") as fd:
if onefile:
fd.write('[note]<!--\n')
- fd.write('.. title: {0}\n'.format(title))
- fd.write('.. slug: {0}\n'.format(slug))
- fd.write('.. date: {0}\n'.format(date))
- fd.write('.. tags: {0}\n'.format(tags))
- fd.write('.. link: \n')
- fd.write('.. description: \n')
+ for k, v in metadata.items():
+ fd.write('.. {0}: {1}\n'.format(k, v))
fd.write('-->[/note]\n\n')
- fd.write("\nWrite your post here.")
+ fd.write("Write your post here.")
diff --git a/nikola/plugins/compile_html.py b/nikola/plugins/compile_html.py
index 6c1c381..7551b33 100644
--- a/nikola/plugins/compile_html.py
+++ b/nikola/plugins/compile_html.py
@@ -43,19 +43,17 @@ class CompileHtml(PageCompiler):
pass
shutil.copyfile(source, dest)
- def create_post(self, path, onefile=False, title="", slug="",
- date="", tags=""):
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
d_name = os.path.dirname(path)
if not os.path.isdir(d_name):
os.makedirs(os.path.dirname(path))
with codecs.open(path, "wb+", "utf8") as fd:
if onefile:
fd.write('<!-- \n')
- fd.write('.. title: {0}\n'.format(title))
- fd.write('.. slug: {0}\n'.format(slug))
- fd.write('.. date: {0}\n'.format(date))
- fd.write('.. tags: {0}\n'.format(tags))
- fd.write('.. link: \n')
- fd.write('.. description: \n')
+ for k, v in metadata.keys():
+ fd.write('.. {0}: {1}\n'.format(k, v))
fd.write('-->\n\n')
fd.write("\n<p>Write your post here.</p>")
diff --git a/extra_plugins/compile_ipynb.plugin b/nikola/plugins/compile_ipynb.plugin
index 51051e0..51051e0 100644
--- a/extra_plugins/compile_ipynb.plugin
+++ b/nikola/plugins/compile_ipynb.plugin
diff --git a/extra_plugins/compile_ipynb/README.txt b/nikola/plugins/compile_ipynb/README.txt
index 2cfd45e..2cfd45e 100644
--- a/extra_plugins/compile_ipynb/README.txt
+++ b/nikola/plugins/compile_ipynb/README.txt
diff --git a/extra_plugins/compile_ipynb/__init__.py b/nikola/plugins/compile_ipynb/__init__.py
index be83a84..d38f6f2 100644
--- a/extra_plugins/compile_ipynb/__init__.py
+++ b/nikola/plugins/compile_ipynb/__init__.py
@@ -60,18 +60,20 @@ class CompileIPynb(PageCompiler):
output = converter.convert()
out_file.write(output)
- def create_post(self, path, onefile=False, title="", slug="", date="",
- tags=""):
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
d_name = os.path.dirname(path)
if not os.path.isdir(d_name):
os.makedirs(os.path.dirname(path))
- meta_path = os.path.join(d_name, slug + ".meta")
+ meta_path = os.path.join(d_name, kw['slug'] + ".meta")
with codecs.open(meta_path, "wb+", "utf8") as fd:
if onefile:
- fd.write('%s\n' % title)
- fd.write('%s\n' % slug)
- fd.write('%s\n' % date)
- fd.write('%s\n' % tags)
+ fd.write('%s\n' % kw['title'])
+ fd.write('%s\n' % kw['slug'])
+ fd.write('%s\n' % kw['date'])
+ fd.write('%s\n' % kw['tags'])
print("Your post's metadata is at: ", meta_path)
with codecs.open(path, "wb+", "utf8") as fd:
fd.write("""{
@@ -95,4 +97,4 @@ class CompileIPynb(PageCompiler):
"metadata": {}
}
]
-}""" % slug)
+}""" % kw['slug'])
diff --git a/nikola/plugins/compile_markdown/__init__.py b/nikola/plugins/compile_markdown/__init__.py
index 7aa03a9..ae700e6 100644
--- a/nikola/plugins/compile_markdown/__init__.py
+++ b/nikola/plugins/compile_markdown/__init__.py
@@ -24,14 +24,28 @@
"""Implementation of compile_html based on markdown."""
+from __future__ import unicode_literals
+
import codecs
import os
-import re
try:
from markdown import markdown
+
+ from nikola.plugins.compile_markdown.mdx_nikola import NikolaExtension
+ nikola_extension = NikolaExtension()
+
+ from nikola.plugins.compile_markdown.mdx_gist import GistExtension
+ gist_extension = GistExtension()
+
+ from nikola.plugins.compile_markdown.mdx_podcast import PodcastExtension
+ podcast_extension = PodcastExtension()
+
except ImportError:
markdown = None # NOQA
+ nikola_extension = None
+ gist_extension = None
+ podcast_extension = None
from nikola.plugin_categories import PageCompiler
@@ -41,6 +55,9 @@ class CompileMarkdown(PageCompiler):
name = "markdown"
+ extensions = ['fenced_code', 'codehilite', gist_extension,
+ nikola_extension, podcast_extension]
+
def compile_html(self, source, dest):
if markdown is None:
raise Exception('To build this site, you need to install the '
@@ -52,30 +69,20 @@ class CompileMarkdown(PageCompiler):
with codecs.open(dest, "w+", "utf8") as out_file:
with codecs.open(source, "r", "utf8") as in_file:
data = in_file.read()
- output = markdown(data, ['fenced_code', 'codehilite'])
- # h1 is reserved for the title so increment all header levels
- for n in reversed(range(1, 9)):
- output = re.sub('<h{0}>'.format(n), '<h{0}>'.format(n + 1),
- output)
- output = re.sub('</h{0}>'.format(n), '</h{0}>'.format(n + 1),
- output)
- # python-markdown's highlighter uses the class 'codehilite' to wrap
- # code, # instead of the standard 'code'. None of the standard
- # pygments stylesheets use this class, so swap it to be 'code'
- output = re.sub(r'(<div[^>]+class="[^"]*)codehilite([^>]+)',
- r'\1code\2', output)
+ output = markdown(data, self.extensions)
out_file.write(output)
- def create_post(self, path, onefile=False, title="", slug="", date="",
- tags=""):
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
+ d_name = os.path.dirname(path)
+ if not os.path.isdir(d_name):
+ os.makedirs(os.path.dirname(path))
with codecs.open(path, "wb+", "utf8") as fd:
if onefile:
fd.write('<!-- \n')
- fd.write('.. title: {0}\n'.format(title))
- fd.write('.. slug: {0}\n'.format(slug))
- fd.write('.. date: {0}\n'.format(date))
- fd.write('.. tags: {0}\n'.format(tags))
- fd.write('.. link: \n')
- fd.write('.. description: \n')
+ for k, v in metadata.items():
+ fd.write('.. {0}: {1}\n'.format(k, v))
fd.write('-->\n\n')
- fd.write("\nWrite your post here.")
+ fd.write("Write your post here.")
diff --git a/nikola/plugins/compile_markdown/mdx_gist.py b/nikola/plugins/compile_markdown/mdx_gist.py
new file mode 100644
index 0000000..808e383
--- /dev/null
+++ b/nikola/plugins/compile_markdown/mdx_gist.py
@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2013 Michael Rabbitt.
+#
+# 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.
+#
+# Inspired by "[Python] reStructuredText GitHub Gist directive"
+# (https://gist.github.com/brianhsu/1407759), public domain by Brian Hsu
+
+from __future__ import print_function
+
+
+'''
+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>
+
+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>
+
+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>
+'''
+from __future__ import unicode_literals
+import warnings
+from markdown.extensions import Extension
+from markdown.inlinepatterns import Pattern
+from markdown.util import AtomicString
+from markdown.util import etree
+
+try:
+ import requests
+except ImportError:
+ requests = None # NOQA
+
+GIST_JS_URL = "https://gist.github.com/{0}.js"
+GIST_FILE_JS_URL = "https://gist.github.com/{0}.js?file={1}"
+GIST_RAW_URL = "https://raw.github.com/gist/{0}"
+GIST_FILE_RAW_URL = "https://raw.github.com/gist/{0}/{1}"
+
+GIST_MD_RE = r'\[:gist:\s*(?P<gist_id>\d+)(?:\s*(?P<filename>.+?))?\]'
+GIST_RST_RE = r'(?m)^\.\.\s*gist::\s*(?P<gist_id>\d+)(?:\s*(?P<filename>.+))\s*$'
+
+
+class GistPattern(Pattern):
+ """ InlinePattern for footnote markers in a document's body text. """
+
+ def __init__(self, pattern, configs):
+ Pattern.__init__(self, pattern)
+
+ def get_raw_gist_with_filename(self, gist_id, filename):
+ url = GIST_FILE_RAW_URL.format(gist_id, filename)
+ return requests.get(url).text
+
+ def get_raw_gist(self, gist_id):
+ url = GIST_RAW_URL.format(gist_id)
+ return requests.get(url).text
+
+ def handleMatch(self, m):
+ gist_id = m.group('gist_id')
+ gist_file = m.group('filename')
+
+ gist_elem = etree.Element('div')
+ gist_elem.set('class', 'gist')
+ script_elem = etree.SubElement(gist_elem, 'script')
+
+ if gist_file:
+ script_elem.set('src', GIST_FILE_JS_URL.format(
+ gist_id, gist_file))
+
+ else:
+ script_elem.set('src', GIST_JS_URL.format(
+ gist_id))
+
+ if requests:
+ if gist_file:
+ raw_gist = (self.get_raw_gist_with_filename(
+ gist_id, gist_file))
+ script_elem.set('src', GIST_FILE_JS_URL.format(
+ gist_id, gist_file))
+
+ else:
+ raw_gist = (self.get_raw_gist(gist_id))
+ script_elem.set('src', GIST_JS_URL.format(
+ gist_id))
+
+ # Insert source as <pre/> within <noscript>
+ noscript_elem = etree.SubElement(gist_elem, 'noscript')
+ pre_elem = etree.SubElement(noscript_elem, 'pre')
+ pre_elem.text = AtomicString(raw_gist)
+
+ else:
+ warnings.warn('"requests" package not installed. '
+ 'Please install to add inline gist source.')
+
+ return gist_elem
+
+
+class GistExtension(Extension):
+ def __init__(self, configs={}):
+ # set extension defaults
+ self.config = {}
+
+ # Override defaults with user settings
+ for key, value in configs:
+ self.setConfig(key, value)
+
+ def extendMarkdown(self, md, md_globals):
+ gist_md_pattern = GistPattern(GIST_MD_RE, self.getConfigs())
+ gist_md_pattern.md = md
+ md.inlinePatterns.add('gist', gist_md_pattern, "<not_strong")
+
+ gist_rst_pattern = GistPattern(GIST_RST_RE, self.getConfigs())
+ gist_rst_pattern.md = md
+ md.inlinePatterns.add('gist-rst', gist_rst_pattern, ">gist")
+
+ md.registerExtension(self)
+
+
+def makeExtension(configs=None):
+ return GistExtension(configs)
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod(optionflags=(doctest.NORMALIZE_WHITESPACE +
+ doctest.REPORT_NDIFF))
diff --git a/nikola/plugins/compile_markdown/mdx_nikola.py b/nikola/plugins/compile_markdown/mdx_nikola.py
new file mode 100644
index 0000000..f7a1959
--- /dev/null
+++ b/nikola/plugins/compile_markdown/mdx_nikola.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2012 Roberto Alsina y otros.
+
+# 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.
+
+"""Markdown Extension for Nikola-specific post-processing"""
+from __future__ import unicode_literals
+import re
+from markdown.postprocessors import Postprocessor
+from markdown.extensions import Extension
+
+
+class NikolaPostProcessor(Postprocessor):
+ def run(self, text):
+ output = text
+ # h1 is reserved for the title so increment all header levels
+ for n in reversed(range(1, 9)):
+ output = re.sub('<h%i>' % n, '<h%i>' % (n + 1), output)
+ output = re.sub('</h%i>' % n, '</h%i>' % (n + 1), output)
+
+ # python-markdown's highlighter uses the class 'codehilite' to wrap
+ # code, instead of the standard 'code'. None of the standard
+ # pygments stylesheets use this class, so swap it to be 'code'
+ output = re.sub(r'(<div[^>]+class="[^"]*)codehilite([^>]+)',
+ r'\1code\2', output)
+ return output
+
+
+class NikolaExtension(Extension):
+ def extendMarkdown(self, md, md_globals):
+ pp = NikolaPostProcessor()
+ md.postprocessors.add('nikola_post_processor', pp, '_end')
+ md.registerExtension(self)
+
+
+def makeExtension(configs=None):
+ return NikolaExtension(configs)
diff --git a/nikola/plugins/compile_markdown/mdx_podcast.py b/nikola/plugins/compile_markdown/mdx_podcast.py
new file mode 100644
index 0000000..be8bb6b
--- /dev/null
+++ b/nikola/plugins/compile_markdown/mdx_podcast.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2013 Michael Rabbitt, Roberto Alsina
+#
+# 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.
+#
+# Inspired by "[Python] reStructuredText GitHub Podcast directive"
+# (https://gist.github.com/brianhsu/1407759), public domain by Brian Hsu
+
+from __future__ import print_function, unicode_literals
+
+
+'''
+Extension to Python Markdown for Embedded Audio
+
+Basic Example:
+
+>>> import markdown
+>>> text = """[podcast]http://archive.org/download/Rebeldes_Stereotipos/rs20120609_1.mp3[/podcast]"""
+>>> html = markdown.markdown(text, [PodcastExtension()])
+>>> print(html)
+<p><audio src="http://archive.org/download/Rebeldes_Stereotipos/rs20120609_1.mp3"></audio></p>
+'''
+
+from markdown.extensions import Extension
+from markdown.inlinepatterns import Pattern
+from markdown.util import etree
+
+PODCAST_RE = r'\[podcast\](?P<url>.+)\[/podcast\]'
+
+
+class PodcastPattern(Pattern):
+ """ InlinePattern for footnote markers in a document's body text. """
+
+ def __init__(self, pattern, configs):
+ Pattern.__init__(self, pattern)
+
+ def handleMatch(self, m):
+ url = m.group('url').strip()
+ audio_elem = etree.Element('audio')
+ audio_elem.set('controls', '')
+ source_elem = etree.SubElement(audio_elem, 'source')
+ source_elem.set('src', url)
+ source_elem.set('type', 'audio/mpeg')
+ return audio_elem
+
+
+class PodcastExtension(Extension):
+ def __init__(self, configs={}):
+ # set extension defaults
+ self.config = {}
+
+ # Override defaults with user settings
+ for key, value in configs:
+ self.setConfig(key, value)
+
+ def extendMarkdown(self, md, md_globals):
+ podcast_md_pattern = PodcastPattern(PODCAST_RE, self.getConfigs())
+ podcast_md_pattern.md = md
+ md.inlinePatterns.add('podcast', podcast_md_pattern, "<not_strong")
+ md.registerExtension(self)
+
+
+def makeExtension(configs=None):
+ return PodcastExtension(configs)
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod(optionflags=(doctest.NORMALIZE_WHITESPACE +
+ doctest.REPORT_NDIFF))
diff --git a/nikola/plugins/compile_misaka.plugin b/nikola/plugins/compile_misaka.plugin
new file mode 100644
index 0000000..1b9c8a8
--- /dev/null
+++ b/nikola/plugins/compile_misaka.plugin
@@ -0,0 +1,10 @@
+[Core]
+Name = misaka
+Module = compile_misaka
+
+[Documentation]
+Author = Chris Lee
+Version = 0.1
+Website = http://c133.org/
+Description = Compile Markdown into HTML with Mikasa instead of python-markdown
+
diff --git a/nikola/plugins/compile_misaka/__init__.py b/nikola/plugins/compile_misaka/__init__.py
new file mode 100644
index 0000000..a3f687e
--- /dev/null
+++ b/nikola/plugins/compile_misaka/__init__.py
@@ -0,0 +1,82 @@
+# Copyright (c) 2013 Chris Lee
+
+# 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.
+
+"""Implementation of compile_html based on misaka."""
+
+from __future__ import unicode_literals
+
+import codecs
+import os
+
+try:
+ import misaka
+
+except ImportError:
+ misaka = None # NOQA
+ nikola_extension = None
+ gist_extension = None
+ podcast_extension = None
+
+from nikola.plugin_categories import PageCompiler
+
+
+class CompileMarkdown(PageCompiler):
+ """Compile markdown into HTML."""
+
+ name = "markdown"
+
+ def __init__(self, *args, **kwargs):
+ super(CompileMarkdown, self).__init__(*args, **kwargs)
+ if misaka is not None:
+ self.ext = misaka.EXT_FENCED_CODE | misaka.EXT_STRIKETHROUGH | \
+ misaka.EXT_AUTOLINK | misaka.EXT_NO_INTRA_EMPHASIS
+
+ def compile_html(self, source, dest):
+ if misaka is None:
+ raise Exception('To build this site, you need to install the '
+ '"misaka" package.')
+ try:
+ os.makedirs(os.path.dirname(dest))
+ except:
+ pass
+ with codecs.open(dest, "w+", "utf8") as out_file:
+ with codecs.open(source, "r", "utf8") as in_file:
+ data = in_file.read()
+ output = misaka.html(data, extensions=self.ext)
+ out_file.write(output)
+
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
+ d_name = os.path.dirname(path)
+ if not os.path.isdir(d_name):
+ os.makedirs(os.path.dirname(path))
+ with codecs.open(path, "wb+", "utf8") as fd:
+ if onefile:
+ fd.write('<!-- \n')
+ for k, v in metadata.items():
+ fd.write('.. {0}: {1}\n'.format(k, v))
+ fd.write('-->\n\n')
+ fd.write("\nWrite your post here.")
diff --git a/nikola/plugins/compile_rest/__init__.py b/nikola/plugins/compile_rest/__init__.py
index b0a0c00..3d41571 100644
--- a/nikola/plugins/compile_rest/__init__.py
+++ b/nikola/plugins/compile_rest/__init__.py
@@ -26,26 +26,28 @@ from __future__ import unicode_literals
import codecs
import os
-import docutils.core
-import docutils.io
-from docutils.parsers.rst import directives
-
-from .pygments_code_block_directive import (
- code_block_directive,
- listings_directive)
-directives.register_directive('code-block', code_block_directive)
-directives.register_directive('listing', listings_directive)
-
-from .youtube import youtube
-directives.register_directive('youtube', youtube)
-from .vimeo import vimeo
-directives.register_directive('vimeo', vimeo)
-from .slides import slides
-directives.register_directive('slides', slides)
-from .gist_directive import GitHubGist
-directives.register_directive('gist', GitHubGist)
-from .soundcloud import soundcloud
-directives.register_directive('soundcloud', soundcloud)
+try:
+ import docutils.core
+ import docutils.io
+ from docutils.parsers.rst import directives
+
+ from .listing import Listing, CodeBlock
+ directives.register_directive('code-block', CodeBlock)
+ directives.register_directive('sourcecode', CodeBlock)
+ directives.register_directive('listing', Listing)
+ from .youtube import Youtube
+ directives.register_directive('youtube', Youtube)
+ from .vimeo import Vimeo
+ directives.register_directive('vimeo', Vimeo)
+ from .slides import Slides
+ directives.register_directive('slides', Slides)
+ from .gist_directive import GitHubGist
+ directives.register_directive('gist', GitHubGist)
+ from .soundcloud import SoundCloud
+ directives.register_directive('soundcloud', SoundCloud)
+ has_docutils = True
+except ImportError:
+ has_docutils = False
from nikola.plugin_categories import PageCompiler
@@ -57,6 +59,9 @@ class CompileRest(PageCompiler):
def compile_html(self, source, dest):
"""Compile reSt into HTML."""
+ if not has_docutils:
+ raise Exception('To build this site, you need to install the '
+ '"docutils" package.')
try:
os.makedirs(os.path.dirname(dest))
except:
@@ -65,24 +70,38 @@ class CompileRest(PageCompiler):
with codecs.open(dest, "w+", "utf8") as out_file:
with codecs.open(source, "r", "utf8") as in_file:
data = in_file.read()
- output, error_level = rst2html(
- data, settings_overrides={'initial_header_level': 2})
+ output, error_level, deps = rst2html(
+ data, settings_overrides={
+ 'initial_header_level': 2,
+ 'record_dependencies': True,
+ 'stylesheet_path': None,
+ 'link_stylesheet': True,
+ 'syntax_highlight': 'short',
+ })
out_file.write(output)
+ deps_path = dest + '.dep'
+ if deps.list:
+ with codecs.open(deps_path, "wb+", "utf8") as deps_file:
+ deps_file.write('\n'.join(deps.list))
+ else:
+ if os.path.isfile(deps_path):
+ os.unlink(deps_path)
if error_level < 3:
return True
else:
return False
- def create_post(self, path, onefile=False, title="", slug="", date="",
- tags=""):
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
+ d_name = os.path.dirname(path)
+ if not os.path.isdir(d_name):
+ os.makedirs(os.path.dirname(path))
with codecs.open(path, "wb+", "utf8") as fd:
if onefile:
- fd.write('.. title: {0}\n'.format(title))
- fd.write('.. slug: {0}\n'.format(slug))
- fd.write('.. date: {0}\n'.format(date))
- fd.write('.. tags: {0}\n'.format(tags))
- fd.write('.. link: \n')
- fd.write('.. description: \n\n')
+ for k, v in metadata.items():
+ fd.write('.. {0}: {1}\n'.format(k, v))
fd.write("\nWrite your post here.")
@@ -116,4 +135,4 @@ def rst2html(source, source_path=None, source_class=docutils.io.StringInput,
settings_overrides=settings_overrides,
config_section=config_section,
enable_exit_status=enable_exit_status)
- return pub.writer.parts['fragment'], pub.document.reporter.max_level
+ return pub.writer.parts['fragment'], pub.document.reporter.max_level, pub.settings.record_dependencies
diff --git a/nikola/plugins/compile_rest/dummy.py b/nikola/plugins/compile_rest/dummy.py
new file mode 100644
index 0000000..39543fd
--- /dev/null
+++ b/nikola/plugins/compile_rest/dummy.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2012 Roberto Alsina y otros.
+
+# 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.
+
+"""A stupid codeblock replacement for neanderthals and users of Debian Sid."""
+
+from __future__ import unicode_literals
+
+from docutils import nodes
+from docutils.parsers.rst import Directive, directives
+
+CODE = '<pre>{0}</pre>'
+
+
+class CodeBlock(Directive):
+ required_arguments = 1
+ has_content = True
+
+ def run(self):
+ """ Required by the Directive interface. Create docutils nodes """
+ return [nodes.raw('', CODE.format('\n'.join(self.content)), format='html')]
+
+directives.register_directive('code', CodeBlock)
diff --git a/nikola/plugins/compile_rest/gist_directive.py b/nikola/plugins/compile_rest/gist_directive.py
index 0ea8f23..1506519 100644
--- a/nikola/plugins/compile_rest/gist_directive.py
+++ b/nikola/plugins/compile_rest/gist_directive.py
@@ -28,7 +28,7 @@ class GitHubGist(Directive):
return requests.get(url).text
def get_raw_gist(self, gistID):
- url = "https://raw.github.com/gist/{0}/".format(gistID)
+ url = "https://raw.github.com/gist/{0}".format(gistID)
return requests.get(url).text
def run(self):
diff --git a/nikola/plugins/compile_rest/listing.py b/nikola/plugins/compile_rest/listing.py
new file mode 100644
index 0000000..1b816f5
--- /dev/null
+++ b/nikola/plugins/compile_rest/listing.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2012 Roberto Alsina y otros.
+
+# 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.
+
+
+""" Define and register a listing directive using the existing CodeBlock """
+
+
+from __future__ import unicode_literals
+from codecs import open as codecs_open # for patching purposes
+try:
+ from urlparse import urlunsplit
+except ImportError:
+ from urllib.parse import urlunsplit # NOQA
+
+from docutils import core
+from docutils.parsers.rst import directives
+try:
+ from docutils.parsers.rst.directives.body import CodeBlock
+except ImportError: # docutils < 0.9 (Debian Sid For The Loss)
+ from dummy import CodeBlock # NOQA
+
+import os
+
+
+class Listing(CodeBlock):
+ """ listing directive: create a CodeBlock from file
+
+ Usage:
+
+ .. listing:: nikola.py python
+ :number-lines:
+
+ """
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 1
+
+ option_spec = {
+ 'start-at': directives.unchanged,
+ 'end-at': directives.unchanged,
+ 'start-after': directives.unchanged,
+ 'end-before': directives.unchanged,
+ }
+
+ def run(self):
+ fname = self.arguments.pop(0)
+ with codecs_open(os.path.join('listings', fname), 'rb+', 'utf8') as fileobject:
+ self.content = fileobject.read().splitlines()
+ self.trim_content()
+ target = urlunsplit(("link", 'listing', fname, '', ''))
+ generated_nodes = (
+ [core.publish_doctree('`{0} <{1}>`_'.format(fname, target))[0]])
+ generated_nodes += self.get_code_from_file(fileobject)
+ return generated_nodes
+
+ def trim_content(self):
+ """Cut the contents based in options."""
+ start = 0
+ end = len(self.content)
+ if 'start-at' in self.options:
+ for start, l in enumerate(self.content):
+ if self.options['start-at'] in l:
+ break
+ else:
+ start = 0
+ elif 'start-before' in self.options:
+ for start, l in enumerate(self.content):
+ if self.options['start-before'] in l:
+ if start > 0:
+ start -= 1
+ break
+ else:
+ start = 0
+ if 'end-at' in self.options:
+ for end, l in enumerate(self.content):
+ if self.options['end-at'] in l:
+ break
+ else:
+ end = len(self.content)
+ elif 'end-before' in self.options:
+ for end, l in enumerate(self.content):
+ if self.options['end-before'] in l:
+ end -= 1
+ break
+ else:
+ end = len(self.content)
+
+ self.content = self.content[start:end]
+
+ def get_code_from_file(self, data):
+ """ Create CodeBlock nodes from file object content """
+ return super(Listing, self).run()
+
+ def assert_has_content(self):
+ """ Listing has no content, override check from superclass """
+ pass
+
+
+directives.register_directive('listing', Listing)
diff --git a/nikola/plugins/compile_rest/pygments_code_block_directive.py b/nikola/plugins/compile_rest/pygments_code_block_directive.py
deleted file mode 100644
index 79bada2..0000000
--- a/nikola/plugins/compile_rest/pygments_code_block_directive.py
+++ /dev/null
@@ -1,424 +0,0 @@
-# -*- coding: utf-8 -*-
-#$Date: 2012-02-28 21:07:21 -0300 (Tue, 28 Feb 2012) $
-#$Revision: 2443 $
-
-# :Author: a Pygments author|contributor; Felix Wiemann; Guenter Milde
-# :Date: $Date: 2012-02-28 21:07:21 -0300 (Tue, 28 Feb 2012) $
-# :Copyright: This module has been placed in the public domain.
-#
-# This is a merge of `Using Pygments in ReST documents`_ from the pygments_
-# documentation, and a `proof of concept`_ by Felix Wiemann.
-#
-# ========== ===========================================================
-# 2007-06-01 Removed redundancy from class values.
-# 2007-06-04 Merge of successive tokens of same type
-# (code taken from pygments.formatters.others).
-# 2007-06-05 Separate docutils formatter script
-# Use pygments' CSS class names (like the html formatter)
-# allowing the use of pygments-produced style sheets.
-# 2007-06-07 Merge in the formatting of the parsed tokens
-# (misnamed as docutils_formatter) as class DocutilsInterface
-# 2007-06-08 Failsave implementation (fallback to a standard literal block
-# if pygments not found)
-# ========== ===========================================================
-#
-# ::
-
-"""Define and register a code-block directive using pygments"""
-
-from __future__ import unicode_literals
-
-# Requirements
-# ------------
-# ::
-
-import codecs
-from copy import copy
-import os
-try:
- from urlparse import urlunsplit
-except ImportError:
- from urllib.parse import urlunsplit # NOQA
-
-from docutils import nodes, core
-from docutils.parsers.rst import directives
-
-pygments = None
-try:
- import pygments
- from pygments.lexers import get_lexer_by_name
- from pygments.formatters.html import _get_ttype_class
-except ImportError:
- pass
-
-
-# Customisation
-# -------------
-#
-# Do not insert inline nodes for the following tokens.
-# (You could add e.g. Token.Punctuation like ``['', 'p']``.) ::
-
-unstyled_tokens = ['']
-
-
-# DocutilsInterface
-# -----------------
-#
-# This interface class combines code from
-# pygments.formatters.html and pygments.formatters.others.
-#
-# It does not require anything of docutils and could also become a part of
-# pygments::
-
-class DocutilsInterface(object):
- """Parse `code` string and yield "classified" tokens.
-
- Arguments
-
- code -- string of source code to parse
- language -- formal language the code is written in.
-
- Merge subsequent tokens of the same token-type.
-
- Yields the tokens as ``(ttype_class, value)`` tuples,
- where ttype_class is taken from pygments.token.STANDARD_TYPES and
- corresponds to the class argument used in pygments html output.
-
- """
-
- def __init__(self, code, language, custom_args={}):
- self.code = code
- self.language = language
- self.custom_args = custom_args
-
- def lex(self):
- """Get lexer for language (use text as fallback)"""
- try:
- if self.language and str(self.language).lower() != 'none':
- lexer = get_lexer_by_name(self.language.lower(),
- **self.custom_args)
- else:
- lexer = get_lexer_by_name('text', **self.custom_args)
- except ValueError:
- # what happens if pygment isn't present ?
- lexer = get_lexer_by_name('text')
- return pygments.lex(self.code, lexer)
-
- def join(self, tokens):
- """join subsequent tokens of same token-type
- """
- tokens = iter(tokens)
- (lasttype, lastval) = next(tokens)
- for ttype, value in tokens:
- if ttype is lasttype:
- lastval += value
- else:
- yield(lasttype, lastval)
- (lasttype, lastval) = (ttype, value)
- yield(lasttype, lastval)
-
- def __iter__(self):
- """parse code string and yield "clasified" tokens
- """
- try:
- tokens = self.lex()
- except IOError:
- yield ('', self.code)
- return
-
- for ttype, value in self.join(tokens):
- yield (_get_ttype_class(ttype), value)
-
-
-# code_block_directive
-# --------------------
-# ::
-
-def code_block_directive(name, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- """Parse and classify content of a code_block."""
- if 'include' in options:
- try:
- if 'encoding' in options:
- encoding = options['encoding']
- else:
- encoding = 'utf-8'
- content = codecs.open(
- options['include'], 'r', encoding).read().rstrip()
- except (IOError, UnicodeError): # no file or problem reading it
- content = ''
- line_offset = 0
- if content:
- # here we define the start-at and end-at options
- # so that limit is included in extraction
- # this is different than the start-after directive of docutils
- # (docutils/parsers/rst/directives/misc.py L73+)
- # which excludes the beginning
- # the reason is we want to be able to define a start-at like
- # def mymethod(self)
- # and have such a definition included
-
- after_text = options.get('start-at', None)
- if after_text:
- # skip content in include_text before
- # *and NOT incl.* a matching text
- after_index = content.find(after_text)
- if after_index < 0:
- raise state_machine.reporter.severe(
- 'Problem with "start-at" option of "{0}" '
- 'code-block directive:\nText not found.'.format(
- options['start-at']))
- # patch mmueller start
- # Move the after_index to the beginning of the line with the
- # match.
- for char in content[after_index:0:-1]:
- # codecs always opens binary. This works with '\n',
- # '\r' and '\r\n'. We are going backwards, so
- # '\n' is found first in '\r\n'.
- # Going with .splitlines() seems more appropriate
- # but needs a few more changes.
- if char == '\n' or char == '\r':
- break
- after_index -= 1
- # patch mmueller end
-
- content = content[after_index:]
- line_offset = len(content[:after_index].splitlines())
-
- after_text = options.get('start-after', None)
- if after_text:
- # skip content in include_text before
- # *and incl.* a matching text
- after_index = content.find(after_text)
- if after_index < 0:
- raise state_machine.reporter.severe(
- 'Problem with "start-after" option of "{0}" '
- 'code-block directive:\nText not found.'.format(
- options['start-after']))
- line_offset = len(content[:after_index +
- len(after_text)].splitlines())
- content = content[after_index + len(after_text):]
-
- # same changes here for the same reason
- before_text = options.get('end-at', None)
- if before_text:
- # skip content in include_text after
- # *and incl.* a matching text
- before_index = content.find(before_text)
- if before_index < 0:
- raise state_machine.reporter.severe(
- 'Problem with "end-at" option of "{0}" '
- 'code-block directive:\nText not found.'.format(
- options['end-at']))
- content = content[:before_index + len(before_text)]
-
- before_text = options.get('end-before', None)
- if before_text:
- # skip content in include_text after
- # *and NOT incl.* a matching text
- before_index = content.find(before_text)
- if before_index < 0:
- raise state_machine.reporter.severe(
- 'Problem with "end-before" option of "{0}" '
- 'code-block directive:\nText not found.'.format(
- options['end-before']))
- content = content[:before_index]
-
- else:
- content = '\n'.join(content)
-
- if 'tabsize' in options:
- tabw = options['tabsize']
- else:
- tabw = int(options.get('tab-width', 8))
-
- content = content.replace('\t', ' ' * tabw)
-
- withln = "linenos" in options
- if not "linenos_offset" in options:
- line_offset = 0
-
- language = arguments[0]
- # create a literal block element and set class argument
- code_block = nodes.literal_block(classes=["code", language])
-
- if withln:
- lineno = 1 + line_offset
- total_lines = content.count('\n') + 1 + line_offset
- lnwidth = len(str(total_lines))
- fstr = "\n%{0}d ".format(lnwidth)
- code_block += nodes.inline(fstr[1:].format(lineno),
- fstr[1:].format(lineno),
- classes=['linenumber'])
-
- # parse content with pygments and add to code_block element
- content = content.rstrip()
- if pygments is None:
- code_block += nodes.Text(content, content)
- else:
- # The [:-1] is because pygments adds a trailing \n which looks bad
- l = list(DocutilsInterface(content, language, options))
- if l[-1] == ('', '\n'):
- l = l[:-1]
- # We strip last element for the same reason (trailing \n looks bad)
- if l:
- l[-1] = (l[-1][0], l[-1][1].rstrip())
- for cls, value in l:
- if withln and "\n" in value:
- # Split on the "\n"s
- values = value.split("\n")
- # The first piece, pass as-is
- code_block += nodes.Text(values[0], values[0])
- # On the second and later pieces, insert \n and linenos
- linenos = list(range(lineno, lineno + len(values)))
- for chunk, ln in zip(values, linenos)[1:]:
- if ln <= total_lines:
- code_block += nodes.inline(fstr.format(ln),
- fstr.format(ln),
- classes=['linenumber'])
- code_block += nodes.Text(chunk, chunk)
- lineno += len(values) - 1
-
- elif cls in unstyled_tokens:
- # insert as Text to decrease the verbosity of the output.
- code_block += nodes.Text(value, value)
- else:
- code_block += nodes.inline(value, value, classes=[cls])
-
- return [code_block]
-
-# Custom argument validators
-# --------------------------
-# ::
-#
-# Move to separated module??
-
-
-def string_list(argument):
- """
- Converts a space- or comma-separated list of values into a python list
- of strings.
- (Directive option conversion function)
- Based in positive_int_list of docutils.parsers.rst.directives
- """
- if ',' in argument:
- entries = argument.split(',')
- else:
- entries = argument.split()
- return entries
-
-
-def string_bool(argument):
- """
- Converts True, true, False, False in python boolean values
- """
- if argument is None:
- msg = 'argument required but none supplied; choose "True" or "False"'
- raise ValueError(msg)
-
- elif argument.lower() == 'true':
- return True
- elif argument.lower() == 'false':
- return False
- else:
- raise ValueError('"{0}" unknown; choose from "True" or "False"'.format(
- argument))
-
-
-def csharp_unicodelevel(argument):
- return directives.choice(argument, ('none', 'basic', 'full'))
-
-
-def lhs_litstyle(argument):
- return directives.choice(argument, ('bird', 'latex'))
-
-
-def raw_compress(argument):
- return directives.choice(argument, ('gz', 'bz2'))
-
-
-def listings_directive(name, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- fname = arguments[0]
- options['include'] = os.path.join('listings', fname)
- target = urlunsplit(("link", 'listing', fname, '', ''))
- generated_nodes = [core.publish_doctree('`{0} <{1}>`_'.format(fname,
- target))[0]]
- generated_nodes += code_block_directive(name, [arguments[1]], options,
- content, lineno, content_offset,
- block_text, state, state_machine)
- return generated_nodes
-
-code_block_directive.arguments = (1, 0, 1)
-listings_directive.arguments = (2, 0, 1)
-code_block_directive.content = 1
-listings_directive.content = 1
-code_block_directive.options = {'include': directives.unchanged_required,
- 'start-at': directives.unchanged_required,
- 'end-at': directives.unchanged_required,
- 'start-after': directives.unchanged_required,
- 'end-before': directives.unchanged_required,
- 'linenos': directives.unchanged,
- 'linenos_offset': directives.unchanged,
- 'tab-width': directives.unchanged,
- # generic
- 'stripnl': string_bool,
- 'stripall': string_bool,
- 'ensurenl': string_bool,
- 'tabsize': directives.positive_int,
- 'encoding': directives.encoding,
- # Lua
- 'func_name_hightlighting': string_bool,
- 'disabled_modules': string_list,
- # Python Console
- 'python3': string_bool,
- # Delphi
- 'turbopascal': string_bool,
- 'delphi': string_bool,
- 'freepascal': string_bool,
- 'units': string_list,
- # Modula2
- 'pim': string_bool,
- 'iso': string_bool,
- 'objm2': string_bool,
- 'gm2ext': string_bool,
- # CSharp
- 'unicodelevel': csharp_unicodelevel,
- # Literate haskell
- 'litstyle': lhs_litstyle,
- # Raw
- 'compress': raw_compress,
- # Rst
- 'handlecodeblocks': string_bool,
- # Php
- 'startinline': string_bool,
- 'funcnamehighlighting': string_bool,
- 'disabledmodules': string_list,
- }
-
-listings_directive.options = copy(code_block_directive.options)
-listings_directive.options.pop('include')
-
-# .. _doctutils: http://docutils.sf.net/
-# .. _pygments: http://pygments.org/
-# .. _Using Pygments in ReST documents: http://pygments.org/docs/rstdirective/
-# .. _proof of concept:
-# http://article.gmane.org/gmane.text.docutils.user/3689
-#
-# Test output
-# -----------
-#
-# If called from the command line, call the docutils publisher to render the
-# input::
-
-if __name__ == '__main__':
- from docutils.core import publish_cmdline, default_description
- from docutils.parsers.rst import directives
- directives.register_directive('code-block', code_block_directive)
- description = "code-block directive test output" + default_description
- try:
- import locale
- locale.setlocale(locale.LC_ALL, '')
- except Exception:
- pass
- publish_cmdline(writer_name='html', description=description)
diff --git a/nikola/plugins/compile_rest/slides.py b/nikola/plugins/compile_rest/slides.py
index f9901f5..57fb754 100644
--- a/nikola/plugins/compile_rest/slides.py
+++ b/nikola/plugins/compile_rest/slides.py
@@ -22,71 +22,44 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-import json
+from __future__ import unicode_literals
from docutils import nodes
from docutils.parsers.rst import Directive, directives
-class slides(Directive):
+class Slides(Directive):
""" Restructured text extension for inserting slideshows."""
has_content = True
- option_spec = {
- "preload": directives.flag,
- "preloadImage": directives.uri,
- "container": directives.unchanged,
- "generateNextPrev": directives.flag,
- "next": directives.unchanged,
- "prev": directives.unchanged,
- "pagination": directives.flag,
- "generatePagination": directives.flag,
- "paginationClass": directives.unchanged,
- "currentClass": directives.unchanged,
- "fadeSpeed": directives.positive_int,
- "fadeEasing": directives.unchanged,
- "slideSpeed": directives.positive_int,
- "slideEasing": directives.unchanged,
- "start": directives.positive_int,
- "effect": directives.unchanged,
- "crossfade": directives.flag,
- "randomize": directives.flag,
- "play": directives.positive_int,
- "pause": directives.positive_int,
- "hoverPause": directives.flag,
- "autoHeight": directives.flag,
- "autoHeightSpeed": directives.positive_int,
- "bigTarget": directives.flag,
- "animationStart": directives.unchanged,
- "animationComplete": directives.unchanged,
- }
def run(self):
if len(self.content) == 0:
return
- for opt in ("preload", "generateNextPrev", "pagination",
- "generatePagination", "crossfade", "randomize",
- "hoverPause", "autoHeight", "bigTarget"):
- if opt in self.options:
- self.options[opt] = True
- options = {
- "autoHeight": True,
- "bigTarget": True,
- "paginationClass": "pager",
- "currentClass": "slide-current"
- }
- options.update(self.options)
- options = json.dumps(options)
output = []
- output.append('<script> $(function(){ $("#slides").slides(' + options +
- '); });'
- '</script>')
- output.append('<div id="slides" class="slides"><div '
- 'class="slides_container">')
- for image in self.content:
- output.append("""<div><img src="{0}"></div>""".format(image))
- output.append("""</div></div>""")
-
+ output.append("""
+ <div id="myCarousel" class="carousel slide">
+ <ol class="carousel-indicators">
+ """)
+ for i in range(len(self.content)):
+ if i == 0:
+ classname = 'class="active"'
+ else:
+ classname = ''
+ output.append(' <li data-target="#myCarousel" data-slide-to="{0}" {1}></li>'.format(i, classname))
+ output.append("""</ol>
+ <div class="carousel-inner">
+ """)
+ for i, image in enumerate(self.content):
+ if i == 0:
+ classname = "item active"
+ else:
+ classname = "item"
+ output.append("""<div class="{0}"><img src="{1}" alt="" style="margin: 0 auto 0 auto;"></div>""".format(classname, image))
+ output.append("""</div>
+ <a class="left carousel-control" href="#myCarousel" data-slide="prev">&lsaquo;</a>
+ <a class="right carousel-control" href="#myCarousel" data-slide="next">&rsaquo;</a>
+ </div>""")
return [nodes.raw('', '\n'.join(output), format='html')]
-directives.register_directive('slides', slides)
+directives.register_directive('slides', Slides)
diff --git a/nikola/plugins/compile_rest/soundcloud.py b/nikola/plugins/compile_rest/soundcloud.py
index d47bebf..6bdd4d5 100644
--- a/nikola/plugins/compile_rest/soundcloud.py
+++ b/nikola/plugins/compile_rest/soundcloud.py
@@ -1,5 +1,9 @@
+# coding: utf8
+
+
from docutils import nodes
-from docutils.parsers.rst import directives
+from docutils.parsers.rst import Directive, directives
+
CODE = ("""<iframe width="{width}" height="{height}"
scrolling="no" frameborder="no"
@@ -8,25 +12,39 @@ src="https://w.soundcloud.com/player/?url=http://api.soundcloud.com/tracks/"""
</iframe>""")
-def soundcloud(name, args, options, content, lineno,
- contentOffset, blockText, state, stateMachine):
- """ Restructured text extension for inserting SoundCloud embedded music """
- string_vars = {
- 'sid': content[0],
- 'width': 600,
- 'height': 160,
- 'extra': ''
+class SoundCloud(Directive):
+ """ Restructured text extension for inserting SoundCloud embedded music
+
+ Usage:
+ .. soundcloud:: <sound id>
+ :height: 400
+ :width: 600
+
+ """
+ has_content = True
+ required_arguments = 1
+ option_spec = {
+ 'width': directives.positive_int,
+ 'height': directives.positive_int,
}
- extra_args = content[1:] # Because content[0] is ID
- extra_args = [ea.strip().split("=") for ea in extra_args] # key=value
- extra_args = [ea for ea in extra_args if len(ea) == 2] # drop bad lines
- extra_args = dict(extra_args)
- if 'width' in extra_args:
- string_vars['width'] = extra_args.pop('width')
- if 'height' in extra_args:
- string_vars['height'] = extra_args.pop('height')
-
- return [nodes.raw('', CODE.format(**string_vars), format='html')]
-
-soundcloud.content = True
-directives.register_directive('soundcloud', soundcloud)
+
+ def run(self):
+ """ Required by the Directive interface. Create docutils nodes """
+ self.check_content()
+ options = {
+ 'sid': self.arguments[0],
+ 'width': 600,
+ 'height': 160,
+ }
+ options.update(self.options)
+ return [nodes.raw('', CODE.format(**options), format='html')]
+
+ def check_content(self):
+ """ Emit a deprecation warning if there is content """
+ if self.content:
+ raise self.warning("This directive does not accept content. The "
+ "'key=value' format for options is deprecated, "
+ "use ':key: value' instead")
+
+
+directives.register_directive('soundcloud', SoundCloud)
diff --git a/nikola/plugins/compile_rest/vimeo.py b/nikola/plugins/compile_rest/vimeo.py
index 34f2a50..c1dc143 100644
--- a/nikola/plugins/compile_rest/vimeo.py
+++ b/nikola/plugins/compile_rest/vimeo.py
@@ -1,3 +1,4 @@
+# coding: utf8
# Copyright (c) 2012 Roberto Alsina y otros.
# Permission is hereby granted, free of charge, to any
@@ -22,8 +23,9 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
from docutils import nodes
-from docutils.parsers.rst import directives
+from docutils.parsers.rst import Directive, directives
try:
import requests
@@ -37,6 +39,7 @@ except ImportError:
except ImportError:
json = None
+
CODE = """<iframe src="http://player.vimeo.com/video/{vimeo_id}"
width="{width}" height="{height}"
frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen>
@@ -47,46 +50,69 @@ VIDEO_DEFAULT_HEIGHT = 500
VIDEO_DEFAULT_WIDTH = 281
-def vimeo(name, args, options, content, lineno, contentOffset, blockText,
- state, stateMachine):
- """ Restructured text extension for inserting vimeo embedded videos """
- if requests is None:
- raise Exception("To use the Vimeo directive you need to install the "
- "requests module.")
- if json is None:
- raise Exception("To use the Vimeo directive you need python 2.6 or to "
- "install the simplejson module.")
- if len(content) == 0:
- return
-
- string_vars = {'vimeo_id': content[0]}
- extra_args = content[1:] # Because content[0] is ID
- extra_args = [ea.strip().split("=") for ea in extra_args] # key=value
- extra_args = [ea for ea in extra_args if len(ea) == 2] # drop bad lines
- extra_args = dict(extra_args)
- if 'width' in extra_args:
- string_vars['width'] = extra_args.pop('width')
- if 'height' in extra_args:
- string_vars['height'] = extra_args.pop('height')
-
- # Only need to make a connection if width and height aren't provided
- if 'height' not in string_vars or 'width' not in string_vars:
- string_vars['height'] = VIDEO_DEFAULT_HEIGHT
- string_vars['width'] = VIDEO_DEFAULT_WIDTH
-
- if json: # we can attempt to retrieve video attributes from vimeo
- try:
- url = ('http://vimeo.com/api/v2/video/{vimeo_id}'
- '.json'.format(**string_vars))
- data = requests.get(url).text
- video_attributes = json.loads(data)
- string_vars['height'] = video_attributes['height']
- string_vars['width'] = video_attributes['width']
- except Exception:
- # fall back to the defaults
- pass
-
- return [nodes.raw('', CODE.format(**string_vars), format='html')]
-
-vimeo.content = True
-directives.register_directive('vimeo', vimeo)
+class Vimeo(Directive):
+ """ Restructured text extension for inserting vimeo embedded videos
+
+ Usage:
+ .. vimeo:: 20241459
+ :height: 400
+ :width: 600
+
+ """
+ has_content = True
+ required_arguments = 1
+ option_spec = {
+ "width": directives.positive_int,
+ "height": directives.positive_int,
+ }
+
+ # set to False for not querying the vimeo api for size
+ request_size = True
+
+ def run(self):
+ self.check_content()
+ options = {
+ 'vimeo_id': self.arguments[0],
+ 'width': VIDEO_DEFAULT_WIDTH,
+ 'height': VIDEO_DEFAULT_HEIGHT,
+ }
+ if self.request_size:
+ self.check_modules()
+ self.set_video_size()
+ options.update(self.options)
+ return [nodes.raw('', CODE.format(**options), format='html')]
+
+ def check_modules(self):
+ if requests is None:
+ raise Exception("To use the Vimeo directive you need to install "
+ "the requests module.")
+ if json is None:
+ raise Exception("To use the Vimeo directive you need python 2.6 "
+ "or to install the simplejson module.")
+
+ def set_video_size(self):
+ # Only need to make a connection if width and height aren't provided
+ if 'height' not in self.options or 'width' not in self.options:
+ self.options['height'] = VIDEO_DEFAULT_HEIGHT
+ self.options['width'] = VIDEO_DEFAULT_WIDTH
+
+ if json: # we can attempt to retrieve video attributes from vimeo
+ try:
+ url = ('http://vimeo.com/api/v2/video/{0}'
+ '.json'.format(self.arguments[0]))
+ data = requests.get(url).text
+ video_attributes = json.loads(data)[0]
+ self.options['height'] = video_attributes['height']
+ self.options['width'] = video_attributes['width']
+ except Exception:
+ # fall back to the defaults
+ pass
+
+ def check_content(self):
+ if self.content:
+ raise self.warning("This directive does not accept content. The "
+ "'key=value' format for options is deprecated, "
+ "use ':key: value' instead")
+
+
+directives.register_directive('vimeo', Vimeo)
diff --git a/nikola/plugins/compile_rest/youtube.py b/nikola/plugins/compile_rest/youtube.py
index 30ac000..767be32 100644
--- a/nikola/plugins/compile_rest/youtube.py
+++ b/nikola/plugins/compile_rest/youtube.py
@@ -23,7 +23,8 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from docutils import nodes
-from docutils.parsers.rst import directives
+from docutils.parsers.rst import Directive, directives
+
CODE = """\
<iframe width="{width}"
@@ -32,25 +33,37 @@ src="http://www.youtube.com/embed/{yid}?rel=0&amp;hd=1&amp;wmode=transparent"
></iframe>"""
-def youtube(name, args, options, content, lineno,
- contentOffset, blockText, state, stateMachine):
- """ Restructured text extension for inserting youtube embedded videos """
- if len(content) == 0:
- return
- string_vars = {
- 'yid': content[0],
- 'width': 425,
- 'height': 344,
- 'extra': ''
+class Youtube(Directive):
+ """ Restructured text extension for inserting youtube embedded videos
+
+ Usage:
+ .. youtube:: lyViVmaBQDg
+ :height: 400
+ :width: 600
+
+ """
+ has_content = True
+ required_arguments = 1
+ option_spec = {
+ "width": directives.positive_int,
+ "height": directives.positive_int,
}
- extra_args = content[1:] # Because content[0] is ID
- extra_args = [ea.strip().split("=") for ea in extra_args] # key=value
- extra_args = [ea for ea in extra_args if len(ea) == 2] # drop bad lines
- extra_args = dict(extra_args)
- if 'width' in extra_args:
- string_vars['width'] = extra_args.pop('width')
- if 'height' in extra_args:
- string_vars['height'] = extra_args.pop('height')
- return [nodes.raw('', CODE.format(**string_vars), format='html')]
-youtube.content = True
-directives.register_directive('youtube', youtube)
+
+ def run(self):
+ self.check_content()
+ options = {
+ 'yid': self.arguments[0],
+ 'width': 425,
+ 'height': 344,
+ }
+ options.update(self.options)
+ return [nodes.raw('', CODE.format(**options), format='html')]
+
+ def check_content(self):
+ if self.content:
+ raise self.warning("This directive does not accept content. The "
+ "'key=value' format for options is deprecated, "
+ "use ':key: value' instead")
+
+
+directives.register_directive('youtube', Youtube)
diff --git a/nikola/plugins/compile_textile.py b/nikola/plugins/compile_textile.py
index 3ca370d..85efd3f 100644
--- a/nikola/plugins/compile_textile.py
+++ b/nikola/plugins/compile_textile.py
@@ -54,19 +54,17 @@ class CompileTextile(PageCompiler):
output = textile(data, head_offset=1)
out_file.write(output)
- def create_post(self, path, onefile=False, title="", slug="", date="",
- tags=""):
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
d_name = os.path.dirname(path)
if not os.path.isdir(d_name):
os.makedirs(os.path.dirname(path))
with codecs.open(path, "wb+", "utf8") as fd:
if onefile:
fd.write('<notextile> <!--\n')
- fd.write('.. title: {0}\n'.format(title))
- fd.write('.. slug: {0}\n'.format(slug))
- fd.write('.. date: {0}\n'.format(date))
- fd.write('.. tags: {0}\n'.format(tags))
- fd.write('.. link: \n')
- fd.write('.. description: \n')
+ for k, v in metadata.items():
+ fd.write('.. {0}: {1}\n'.format(k, v))
fd.write('--></notextile>\n\n')
fd.write("\nWrite your post here.")
diff --git a/nikola/plugins/compile_txt2tags.py b/nikola/plugins/compile_txt2tags.py
index 90372bd..001da6e 100644
--- a/nikola/plugins/compile_txt2tags.py
+++ b/nikola/plugins/compile_txt2tags.py
@@ -57,19 +57,17 @@ class CompileTextile(PageCompiler):
cmd = ["-t", "html", "--no-headers", "--outfile", dest, source]
txt2tags(cmd)
- def create_post(self, path, onefile=False, title="", slug="", date="",
- tags=""):
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
d_name = os.path.dirname(path)
if not os.path.isdir(d_name):
os.makedirs(os.path.dirname(path))
with codecs.open(path, "wb+", "utf8") as fd:
if onefile:
fd.write("\n'''\n<!--\n")
- fd.write('.. title: {0}\n'.format(title))
- fd.write('.. slug: {0}\n'.format(slug))
- fd.write('.. date: {0}\n'.format(date))
- fd.write('.. tags: {0}\n'.format(tags))
- fd.write('.. link: \n')
- fd.write('.. description: \n')
+ for k, v in metadata.items():
+ fd.write('.. {0}: {1}\n'.format(k, v))
fd.write("-->\n'''\n")
fd.write("\nWrite your post here.")
diff --git a/nikola/plugins/compile_wiki.py b/nikola/plugins/compile_wiki.py
index 1215506..fb9e010 100644
--- a/nikola/plugins/compile_wiki.py
+++ b/nikola/plugins/compile_wiki.py
@@ -57,14 +57,16 @@ class CompileTextile(PageCompiler):
output = HtmlEmitter(document).emit()
out_file.write(output)
- def create_post(self, path, onefile=False, title="", slug="", date="",
- tags=""):
+ def create_post(self, path, onefile=False, **kw):
+ metadata = {}
+ metadata.update(self.default_metadata)
+ metadata.update(kw)
+ d_name = os.path.dirname(path)
+ if not os.path.isdir(d_name):
+ os.makedirs(os.path.dirname(path))
if onefile:
raise Exception('There are no comments in CreoleWiki markup, so '
'one-file format is not possible, use the -2 '
'option.')
- d_name = os.path.dirname(path)
- if not os.path.isdir(d_name):
- os.makedirs(os.path.dirname(path))
with codecs.open(path, "wb+", "utf8") as fd:
fd.write("Write your post here.")
diff --git a/nikola/plugins/task_archive.py b/nikola/plugins/task_archive.py
index f91a10e..a67826f 100644
--- a/nikola/plugins/task_archive.py
+++ b/nikola/plugins/task_archive.py
@@ -22,7 +22,9 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+import calendar
import os
+import sys
from nikola.plugin_categories import Task
from nikola.utils import config_changed
@@ -39,16 +41,51 @@ class Archive(Task):
"translations": self.site.config['TRANSLATIONS'],
"output_folder": self.site.config['OUTPUT_FOLDER'],
"filters": self.site.config['FILTERS'],
+ "create_monthly_archive": self.site.config['CREATE_MONTHLY_ARCHIVE'],
}
self.site.scan_posts()
# TODO add next/prev links for years
- template_name = "list_post.tmpl"
- # TODO: posts_per_year is global, kill it
- for year, posts in list(self.site.posts_per_year.items()):
- for lang in kw["translations"]:
+ for lang in kw["translations"]:
+ for year, posts in self.site.posts_per_year.items():
+ output_name = os.path.join(
+ kw['output_folder'], self.site.path("archive", year, lang))
+ context = {}
+ context["lang"] = lang
+ context["title"] = kw["messages"][lang]["Posts for year %s"] % year
+ context["permalink"] = self.site.link("archive", year, lang)
+ if not kw["create_monthly_archive"]:
+ template_name = "list_post.tmpl"
+ post_list = [self.site.global_data[post] for post in posts]
+ post_list.sort(key=lambda a: a.date)
+ post_list.reverse()
+ context["posts"] = post_list
+ else: # Monthly archives, just list the months
+ months = set([m.split('/')[1] for m in self.site.posts_per_month.keys() if m.startswith(str(year))])
+ months = sorted(list(months))
+ template_name = "list.tmpl"
+ context["items"] = [[get_month_name(int(month), lang), month] for month in months]
+ post_list = []
+ task = self.site.generic_post_list_renderer(
+ lang,
+ [],
+ output_name,
+ template_name,
+ kw['filters'],
+ context,
+ )
+ task_cfg = {1: task['uptodate'][0].config, 2: kw}
+ task['uptodate'] = [config_changed(task_cfg)]
+ task['basename'] = self.name
+ yield task
+
+ if not kw["create_monthly_archive"]:
+ continue # Just to avoid nesting the other loop in this if
+ template_name = "list_post.tmpl"
+ for yearmonth, posts in self.site.posts_per_month.items():
output_name = os.path.join(
- kw['output_folder'], self.site.path("archive", year,
- lang)).encode('utf8')
+ kw['output_folder'], self.site.path("archive", yearmonth,
+ lang))
+ year, month = yearmonth.split('/')
post_list = [self.site.global_data[post] for post in posts]
post_list.sort(key=lambda a: a.date)
post_list.reverse()
@@ -56,8 +93,9 @@ class Archive(Task):
context["lang"] = lang
context["posts"] = post_list
context["permalink"] = self.site.link("archive", year, lang)
- context["title"] = kw["messages"][lang]["Posts for year %s"]\
- % year
+
+ context["title"] = kw["messages"][lang]["Posts for {month} {year}"].format(
+ year=year, month=get_month_name(int(month), lang))
task = self.site.generic_post_list_renderer(
lang,
post_list,
@@ -80,7 +118,7 @@ class Archive(Task):
context = {}
output_name = os.path.join(
kw['output_folder'], self.site.path("archive", None,
- lang)).encode('utf8')
+ lang))
context["title"] = kw["messages"][lang]["Archive"]
context["items"] = [(year, self.site.link("archive", year, lang))
for year in years]
@@ -97,3 +135,13 @@ class Archive(Task):
task['uptodate'] = [config_changed(task_cfg)]
task['basename'] = self.name
yield task
+
+
+def get_month_name(month_no, locale):
+ if sys.version_info[0] == 3: # Python 3
+ with calendar.different_locale((locale, "UTF-8")):
+ s = calendar.month_name[month_no]
+ else: # Python 2
+ with calendar.TimeEncoding((locale, "UTF-8")):
+ s = calendar.month_name[month_no]
+ return s
diff --git a/nikola/plugins/task_copy_assets.py b/nikola/plugins/task_copy_assets.py
index 39fef5a..06d17e7 100644
--- a/nikola/plugins/task_copy_assets.py
+++ b/nikola/plugins/task_copy_assets.py
@@ -22,6 +22,7 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+import codecs
import os
from nikola.plugin_categories import Task
@@ -44,15 +45,20 @@ class CopyAssets(Task):
"themes": self.site.THEMES,
"output_folder": self.site.config['OUTPUT_FOLDER'],
"filters": self.site.config['FILTERS'],
+ "code_color_scheme": self.site.config['CODE_COLOR_SCHEME'],
}
flag = True
+ has_code_css = False
tasks = {}
+ code_css_path = os.path.join(kw['output_folder'], 'assets', 'css', 'code.css')
for theme_name in kw['themes']:
src = os.path.join(utils.get_theme_path(theme_name), 'assets')
dst = os.path.join(kw['output_folder'], 'assets')
for task in utils.copy_tree(src, dst):
if task['name'] in tasks:
continue
+ if task['targets'][0] == code_css_path:
+ has_code_css = True
tasks[task['name']] = task
task['uptodate'] = [utils.config_changed(kw)]
task['basename'] = self.name
@@ -66,3 +72,22 @@ class CopyAssets(Task):
'uptodate': [True],
'actions': [],
}
+
+ if not has_code_css: # Generate it
+
+ def create_code_css():
+ from pygments.formatters import get_formatter_by_name
+ formatter = get_formatter_by_name('html', style=kw["code_color_scheme"])
+ with codecs.open(code_css_path, 'wb+', 'utf8') as outf:
+ outf.write(formatter.get_style_defs('.code'))
+ outf.write("table.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}")
+
+ task = {
+ 'basename': self.name,
+ 'name': code_css_path,
+ 'targets': [code_css_path],
+ 'uptodate': [utils.config_changed(kw)],
+ 'actions': [(create_code_css, [])],
+ 'clean': True,
+ }
+ yield utils.apply_filters(task, kw['filters'])
diff --git a/nikola/plugins/task_create_bundles.py b/nikola/plugins/task_create_bundles.py
index ad670e1..84ac0ab 100644
--- a/nikola/plugins/task_create_bundles.py
+++ b/nikola/plugins/task_create_bundles.py
@@ -22,6 +22,8 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+from __future__ import unicode_literals
+
import os
try:
@@ -53,6 +55,7 @@ class BuildBundles(LateTask):
'theme_bundles': get_theme_bundles(self.site.THEMES),
'themes': self.site.THEMES,
'files_folders': self.site.config['FILES_FOLDERS'],
+ 'code_color_scheme': self.site.config['CODE_COLOR_SCHEME'],
}
def build_bundle(output, inputs):
@@ -76,7 +79,7 @@ class BuildBundles(LateTask):
for name, files in kw['theme_bundles'].items():
output_path = os.path.join(kw['output_folder'], name)
dname = os.path.dirname(name)
- file_dep = [get_asset_path(
+ file_dep = [utils.get_asset_path(
os.path.join(dname, fname), kw['themes'],
kw['files_folders'])
for fname in files
@@ -101,47 +104,6 @@ class BuildBundles(LateTask):
}
-def get_asset_path(path, themes, files_folders={'files': ''}):
- """Checks which theme provides the path with the given asset,
- and returns the "real" path to the asset, relative to the
- current directory.
-
- If the asset is not provided by a theme, then it will be checked for
- in the FILES_FOLDERS
-
- >>> get_asset_path('assets/css/rst.css', ['site', 'default'])
- 'nikola/data/themes/default/assets/css/rst.css'
-
- >>> get_asset_path('assets/css/theme.css', ['site', 'default'])
- 'nikola/data/themes/site/assets/css/theme.css'
-
- >>> get_asset_path('nikola.py', ['site', 'default'], {'nikola': ''})
- 'nikola/nikola.py'
-
- >>> get_asset_path('nikola/nikola.py', ['site', 'default'],
- ... {'nikola':'nikola'})
- 'nikola/nikola.py'
-
- """
- for theme_name in themes:
- candidate = os.path.join(
- utils.get_theme_path(theme_name),
- path
- )
- if os.path.isfile(candidate):
- return os.path.relpath(candidate, os.getcwd())
- for src, rel_dst in files_folders.items():
- candidate = os.path.join(
- src,
- os.path.relpath(path, rel_dst)
- )
- if os.path.isfile(candidate):
- return os.path.relpath(candidate, os.getcwd())
-
- # whatever!
- return None
-
-
def get_theme_bundles(themes):
"""Given a theme chain, return the bundle definitions."""
bundles = {}
diff --git a/nikola/plugins/task_indexes.py b/nikola/plugins/task_indexes.py
index 7baf660..aa5e648 100644
--- a/nikola/plugins/task_indexes.py
+++ b/nikola/plugins/task_indexes.py
@@ -46,31 +46,35 @@ class Indexes(Task):
"index_teasers": self.site.config['INDEX_TEASERS'],
"output_folder": self.site.config['OUTPUT_FOLDER'],
"filters": self.site.config['FILTERS'],
+ "hide_untranslated_posts": self.site.config['HIDE_UNTRANSLATED_POSTS'],
+ "indexes_title": self.site.config['INDEXES_TITLE'],
+ "indexes_pages": self.site.config['INDEXES_PAGES'],
+ "blog_title": self.site.config["BLOG_TITLE"],
}
template_name = "index.tmpl"
- # TODO: timeline is global, get rid of it
posts = [x for x in self.site.timeline if x.use_in_feeds]
- # Split in smaller lists
- lists = []
- while posts:
- lists.append(posts[:kw["index_display_post_count"]])
- posts = posts[kw["index_display_post_count"]:]
- num_pages = len(lists)
- if not lists:
+ if not posts:
yield {'basename': 'render_indexes', 'actions': []}
for lang in kw["translations"]:
+ # Split in smaller lists
+ lists = []
+ if kw["hide_untranslated_posts"]:
+ filtered_posts = [x for x in posts if x.is_translation_available(lang)]
+ else:
+ filtered_posts = posts
+ while filtered_posts:
+ lists.append(filtered_posts[:kw["index_display_post_count"]])
+ filtered_posts = filtered_posts[kw["index_display_post_count"]:]
+ num_pages = len(lists)
for i, post_list in enumerate(lists):
context = {}
- if self.site.config.get("INDEXES_TITLE", ""):
- indexes_title = self.site.config['INDEXES_TITLE']
- else:
- indexes_title = self.site.config["BLOG_TITLE"]
+ indexes_title = kw['indexes_title'] or kw['blog_title']
if not i:
context["title"] = indexes_title
else:
- if self.site.config.get("INDEXES_PAGES", ""):
- indexes_pages = self.site.config["INDEXES_PAGES"] % i
+ if kw["indexes_pages"]:
+ indexes_pages = kw["indexes_pages"] % i
else:
indexes_pages = " (" + \
kw["messages"][lang]["old posts page %d"] % i + ")"
@@ -87,7 +91,7 @@ class Indexes(Task):
context["permalink"] = self.site.link("index", i, lang)
output_name = os.path.join(
kw['output_folder'], self.site.path("index", i,
- lang)).encode('utf8')
+ lang))
task = self.site.generic_post_list_renderer(
lang,
post_list,
@@ -103,7 +107,6 @@ class Indexes(Task):
if not self.site.config["STORY_INDEX"]:
return
- # TODO: do story indexes as described in #232
kw = {
"translations": self.site.config['TRANSLATIONS'],
"post_pages": self.site.config["post_pages"],
diff --git a/extra_plugins/task_localsearch.plugin b/nikola/plugins/task_localsearch.plugin
index 33eb78b..33eb78b 100644
--- a/extra_plugins/task_localsearch.plugin
+++ b/nikola/plugins/task_localsearch.plugin
diff --git a/extra_plugins/task_localsearch/MIT-LICENSE.txt b/nikola/plugins/task_localsearch/MIT-LICENSE.txt
index f131068..f131068 100644
--- a/extra_plugins/task_localsearch/MIT-LICENSE.txt
+++ b/nikola/plugins/task_localsearch/MIT-LICENSE.txt
diff --git a/extra_plugins/task_localsearch/__init__.py b/nikola/plugins/task_localsearch/__init__.py
index 9bb0a9e..db8610a 100644
--- a/extra_plugins/task_localsearch/__init__.py
+++ b/nikola/plugins/task_localsearch/__init__.py
@@ -22,6 +22,8 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+from __future__ import unicode_literals
+import codecs
import json
import os
@@ -65,9 +67,15 @@ class Tipue(LateTask):
pages = []
for lang in kw["translations"]:
for post in posts:
+ # Don't index drafts (Issue #387)
+ if post.is_draft:
+ continue
+ text = post.text(lang, strip_html=True)
+ text = text.replace('^', '')
+
data = {}
data["title"] = post.title(lang)
- data["text"] = post.text(lang)
+ data["text"] = text
data["tags"] = ",".join(post.tags)
data["loc"] = post.permalink(lang)
pages.append(data)
@@ -76,12 +84,12 @@ class Tipue(LateTask):
os.makedirs(os.path.dirname(dst_path))
except:
pass
- with open(dst_path, "wb+") as fd:
+ with codecs.open(dst_path, "wb+", "utf8") as fd:
fd.write(output)
yield {
"basename": str(self.name),
- "name": os.path.join("assets", "js", "tipuesearch_content.js"),
+ "name": dst_path,
"targets": [dst_path],
"actions": [(save_data, [])],
'uptodate': [config_changed(kw)]
diff --git a/nikola/plugins/task_localsearch/files/assets/css/img/expand.png b/nikola/plugins/task_localsearch/files/assets/css/img/expand.png
new file mode 100755
index 0000000..21bb7b0
--- /dev/null
+++ b/nikola/plugins/task_localsearch/files/assets/css/img/expand.png
Binary files differ
diff --git a/nikola/plugins/task_localsearch/files/assets/css/img/link.png b/nikola/plugins/task_localsearch/files/assets/css/img/link.png
new file mode 100755
index 0000000..d4e51c5
--- /dev/null
+++ b/nikola/plugins/task_localsearch/files/assets/css/img/link.png
Binary files differ
diff --git a/extra_plugins/task_localsearch/files/assets/css/loader.gif b/nikola/plugins/task_localsearch/files/assets/css/img/loader.gif
index 9c97738..9c97738 100644
--- a/extra_plugins/task_localsearch/files/assets/css/loader.gif
+++ b/nikola/plugins/task_localsearch/files/assets/css/img/loader.gif
Binary files differ
diff --git a/extra_plugins/task_localsearch/files/assets/css/search.gif b/nikola/plugins/task_localsearch/files/assets/css/img/search.gif
index 644bd17..644bd17 100644
--- a/extra_plugins/task_localsearch/files/assets/css/search.gif
+++ b/nikola/plugins/task_localsearch/files/assets/css/img/search.gif
Binary files differ
diff --git a/nikola/plugins/task_localsearch/files/assets/css/tipuesearch.css b/nikola/plugins/task_localsearch/files/assets/css/tipuesearch.css
new file mode 100755
index 0000000..96dadf0
--- /dev/null
+++ b/nikola/plugins/task_localsearch/files/assets/css/tipuesearch.css
@@ -0,0 +1,232 @@
+
+/*
+Tipue Search 2.1
+Copyright (c) 2013 Tipue
+Tipue Search is released under the MIT License
+http://www.tipue.com/search
+*/
+
+
+em
+{
+ font: inherit;
+ font-weight: 400;
+}
+#tipue_search_input
+{
+}
+#tipue_search_input:focus
+{
+ border-color: #c3c3c3;
+ box-shadow: 0 0 3px rgba(0,0,0,.2);
+}
+#tipue_search_button
+{
+ width: 60px;
+ height: 33px;
+ margin-top: 1px;
+ border: 1px solid #dcdcdc;
+ border-radius: 2px;
+ background: #f1f1f1 url('img/search.gif') no-repeat center;
+ outline: none;
+}
+#tipue_search_button:hover
+{
+ border: 1px solid #c3c3c3;
+ -moz-box-shadow: 1px 1px 2px #e3e3e3;
+ -webkit-box-shadow: 1px 1px 2px #e3e3e3;
+ box-shadow: 1px 1px 2px #e3e3e3;
+}
+
+#tipue_search_content
+{
+ clear: left;
+ max-width: 650px;
+ padding: 25px 0 13px 0;
+ margin: 0;
+}
+#tipue_search_loading
+{
+ padding-top: 60px;
+ background: #fff url('img/loader.gif') no-repeat left;
+}
+
+#tipue_search_warning_head
+{
+ font: 14px/1.5 'open sans', sans-serif;
+ color: #333;
+}
+#tipue_search_warning
+{
+ font: 300 14px/1.5 lato, sans-serif;
+ color: #111;
+ margin: 13px 0;
+}
+#tipue_search_warning a
+{
+ color: #36c;
+ text-decoration: none;
+}
+#tipue_search_warning a:hover
+{
+ color: #111;
+}
+
+#tipue_search_results_count
+{
+ font: 300 14px/1.5 lato, sans-serif;
+ color: #111;
+}
+
+.tipue_search_content_title
+{
+ font: 300 19px/1.5 'open sans', sans-serif;
+ margin-top: 31px;
+}
+.tipue_search_content_title a
+{
+ color: #36c;
+ text-decoration: none;
+}
+.tipue_search_content_title a:hover
+{
+ color: #333;
+}
+
+.tipue_search_content_image_box
+{
+ float: left;
+ border: 1px solid #f3f3f3;
+ padding: 13px;
+ margin: 21px 0 7px 0;
+}
+.tipue_search_content_image
+{
+ max-width: 110px;
+ height: auto;
+ outline: none;
+ cursor: pointer;
+}
+#tipue_lightbox
+{
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(255, 255, 255, .9);
+}
+#tipue_lightbox_content
+{
+ margin: 37px auto;
+ background-color: #fff;
+ padding: 30px;
+ border: 1px solid #ccc;
+ width: 250px;
+ text-align: center;
+ box-shadow: 0 1px 2px #eee;
+}
+#tipue_lightbox img
+{
+ max-width: 200px;
+ height: auto;
+}
+#tipue_lightbox_content_title
+{
+ font: 300 14px/1.7 lato, sans-serif;
+ color: #111;
+ padding: 17px 25px 0 25px;
+ width: 200px;
+}
+#tipue_lightbox_content_link
+{
+ float: left;
+ width: 30px;
+ height: 30px;
+ margin-top: 17px;
+ background: #fff url('img/link.png') no-repeat center;
+}
+#tipue_lightbox_content_expand
+{
+ float: left;
+ width: 30px;
+ height: 30px;
+ margin: 17px 0 0 3px;
+ background: #fff url('img/expand.png') no-repeat center;
+}
+#tipue_lightbox_content_link:hover, #tipue_lightbox_content_expand:hover
+{
+ background-color: #f3f3f3;
+}
+
+.tipue_search_content_text
+{
+ font: 300 14px/1.7 lato, sans-serif;
+ color: #111;
+ padding: 13px 0;
+}
+.tipue_search_content_loc
+{
+ font: 300 14px/1.5 lato, sans-serif;
+ color: #111;
+}
+.tipue_search_content_loc a
+{
+ color: #555;
+ text-decoration: none;
+}
+.tipue_search_content_loc a:hover
+{
+ padding-bottom: 1px;
+ border-bottom: 1px solid #ccc;
+}
+
+#tipue_search_foot
+{
+ margin: 47px 0 31px 0;
+}
+#tipue_search_foot_boxes
+{
+ padding: 0;
+ margin: 0;
+ font: 12px/1 'open sans', sans-serif;
+}
+#tipue_search_foot_boxes li
+{
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: inline;
+}
+#tipue_search_foot_boxes li a
+{
+ padding: 7px 10px 8px 10px;
+ background-color: #f5f5f5;
+ background: -webkit-linear-gradient(top, #f7f7f7, #f1f1f1);
+ background: -moz-linear-gradient(top, #f7f7f7, #f1f1f1);
+ background: -ms-linear-gradient(top, #f7f7f7, #f1f1f1);
+ background: -o-linear-gradient(top, #f7f7f7, #f1f1f1);
+ background: linear-gradient(top, #f7f7f7, #f1f1f1);
+ border: 1px solid #dcdcdc;
+ border-radius: 2px;
+ color: #333;
+ margin-right: 7px;
+ text-decoration: none;
+ text-align: center;
+}
+#tipue_search_foot_boxes li.current
+{
+ padding: 7px 10px 8px 10px;
+ background: #fff;
+ border: 1px solid #dcdcdc;
+ border-radius: 2px;
+ color: #333;
+ margin-right: 7px;
+ text-align: center;
+}
+#tipue_search_foot_boxes li a:hover
+{
+ border: 1px solid #c3c3c3;
+ box-shadow: 1px 1px 2px #e3e3e3;
+}
diff --git a/extra_plugins/task_localsearch/files/assets/js/tipuesearch.js b/nikola/plugins/task_localsearch/files/assets/js/tipuesearch.js
index 9a8d58e..5c766ea 100644
--- a/extra_plugins/task_localsearch/files/assets/js/tipuesearch.js
+++ b/nikola/plugins/task_localsearch/files/assets/js/tipuesearch.js
@@ -1,7 +1,7 @@
/*
-Tipue Search 2.0
-Copyright (c) 2012 Tipue
+Tipue Search 2.1
+Copyright (c) 2013 Tipue
Tipue Search is released under the MIT License
http://www.tipue.com/search
*/
@@ -21,6 +21,8 @@ http://www.tipue.com/search
'highlightTerms' : true,
'highlightEveryTerm' : false,
'mode' : 'static',
+ 'liveDescription' : '*',
+ 'liveContent' : '*',
'contentLocation' : 'tipuesearch/tipuesearch_content.json'
}, options);
@@ -41,9 +43,11 @@ http://www.tipue.com/search
$.get(tipuesearch_pages[i], '',
function (html)
{
- var cont = $('*', html).text();
+ var cont = $(set.liveContent, html).text();
cont = cont.replace(/\s+/g, ' ');
-
+ var desc = $(set.liveDescription, html).text();
+ desc = desc.replace(/\s+/g, ' ');
+
var t_1 = html.toLowerCase().indexOf('<title>');
var t_2 = html.toLowerCase().indexOf('</title>', t_1 + 7);
if (t_1 != -1 && t_2 != -1)
@@ -54,16 +58,6 @@ http://www.tipue.com/search
{
var tit = 'No title';
}
- var t_1 = html.toLowerCase().indexOf('<meta name="description"');
- var t_2 = html.toLowerCase().indexOf('"', t_1 + 34);
- if (t_1 != -1 && t_2 != -1)
- {
- var desc = html.slice(t_1 + 34, t_2);
- }
- else
- {
- var desc = cont;
- }
tipuesearch_in.pages.push({
"title": tit,
@@ -86,7 +80,7 @@ http://www.tipue.com/search
);
}
- if (set.mode == 'static')
+ if (set.mode == 'static' || set.mode == 'static-images')
{
tipuesearch_in = $.extend({}, tipuesearch);
}
@@ -130,6 +124,25 @@ http://www.tipue.com/search
var d = $('#tipue_search_input').val().toLowerCase();
d = $.trim(d);
var d_w = d.split(' ');
+ d = '';
+ for (var i = 0; i < d_w.length; i++)
+ {
+ var a_w = true;
+ for (var f = 0; f < tipuesearch_stop_words.length; f++)
+ {
+ if (d_w[i] == tipuesearch_stop_words[f])
+ {
+ a_w = false;
+ show_stop = true;
+ }
+ }
+ if (a_w)
+ {
+ d = d + ' ' + d_w[i];
+ }
+ }
+ d = $.trim(d);
+ d_w = d.split(' ');
if (d.length >= set.minimumLength)
{
@@ -148,7 +161,7 @@ http://www.tipue.com/search
}
}
d_w = d.split(' ');
- }
+ }
var d_t = d;
for (var i = 0; i < d_w.length; i++)
@@ -167,18 +180,18 @@ http://www.tipue.com/search
found = new Array();
for (var i = 0; i < tipuesearch_in.pages.length; i++)
{
- var score = 10000000;
+ var score = 1000000000;
var s_t = tipuesearch_in.pages[i].text;
for (var f = 0; f < d_w.length; f++)
{
var pat = new RegExp(d_w[f], 'i');
if (tipuesearch_in.pages[i].title.search(pat) != -1)
{
- score -= (2000 - i);
+ score -= (200000 - i);
}
if (tipuesearch_in.pages[i].text.search(pat) != -1)
{
- score -= (1500 - i);
+ score -= (150000 - i);
}
if (set.highlightTerms)
@@ -196,12 +209,19 @@ http://www.tipue.com/search
if (tipuesearch_in.pages[i].tags.search(pat) != -1)
{
- score -= (1000 - i);
+ score -= (100000 - i);
}
}
- if (score < 10000000)
+ if (score < 1000000000)
{
- found[c++] = score + '^' + tipuesearch_in.pages[i].title + '^' + s_t + '^' + tipuesearch_in.pages[i].loc;
+ if (set.mode == 'static-images')
+ {
+ found[c++] = score + '^' + tipuesearch_in.pages[i].title + '^' + s_t + '^' + tipuesearch_in.pages[i].loc + '^' + tipuesearch_in.pages[i].image;
+ }
+ else
+ {
+ found[c++] = score + '^' + tipuesearch_in.pages[i].title + '^' + s_t + '^' + tipuesearch_in.pages[i].loc;
+ }
}
}
@@ -229,8 +249,17 @@ http://www.tipue.com/search
var fo = found[i].split('^');
if (l_o >= start && l_o < set.show + start)
{
- out += '<div id="tipue_search_content_title"><a href="' + fo[3] + '"' + tipue_search_w + '>' + fo[1] + '</a></div>';
-
+ out += '<div class="tipue_search_content_title"><a href="' + fo[3] + '"' + tipue_search_w + '>' + fo[1] + '</a></div>';
+
+ if (set.mode == 'static-images')
+ {
+ if (fo[4])
+ {
+ out += '<div class="tipue_search_content_image_box"><img class="tipue_search_content_image" id="' + fo[1] + '^' + fo[3] + '" ';
+ out += 'src=' + fo[4] + '></div><div style="clear: both;"></div>';
+ }
+ }
+
var t = fo[2];
var t_d = '';
var t_w = t.split(' ');
@@ -250,11 +279,11 @@ http://www.tipue.com/search
{
t_d += ' ...';
}
- out += '<div id="tipue_search_content_text">' + t_d + '</div>';
+ out += '<div class="tipue_search_content_text">' + t_d + '</div>';
if (set.showURL)
{
- out += '<div id="tipue_search_content_loc"><a href="' + fo[3] + '"' + tipue_search_w + '>' + fo[3] + '</a></div>';
+ out += '<div class="tipue_search_content_loc"><a href="' + fo[3] + '"' + tipue_search_w + '>' + fo[3] + '</a></div>';
}
}
l_o++;
@@ -351,6 +380,33 @@ http://www.tipue.com/search
getTipueSearch(0, false);
});
+ $('.tipue_search_content_image').click(function()
+ {
+ var src_i = $(this).attr('src');
+ var id_v = $(this).attr('id');
+ var id_a = id_v.split('^');
+
+ var l_c_i = '<img src="' + src_i + '"><div id="tipue_lightbox_content_title">' + id_a[0] + '</div>';
+ l_c_i += '<a href="' + id_a[1] + '"' + tipue_search_w + '><div id="tipue_lightbox_content_link"></div></a>';
+ l_c_i += '<a href="' + src_i + '"' + tipue_search_w + '><div id="tipue_lightbox_content_expand"></div></a><div style="clear: both;"></div>';
+
+ if ($('#tipue_lightbox').length > 0)
+ {
+ $('#tipue_lightbox_content').html(l_c_i);
+ $('#tipue_lightbox').fadeIn();
+ }
+ else
+ {
+ var tipue_lightbox = '<div id="tipue_lightbox">' + '<div id="tipue_lightbox_content">' + l_c_i + '</div></div>';
+ $('body').append(tipue_lightbox);
+ $('#tipue_lightbox').fadeIn();
+ }
+ });
+ $('#tipue_lightbox').live('click', function()
+ {
+ $('#tipue_lightbox').hide();
+ });
+
$('.tipue_search_foot_box').click(function()
{
var id_v = $(this).attr('id');
@@ -362,6 +418,9 @@ http://www.tipue.com/search
});
};
-
+
})(jQuery);
+
+
+
diff --git a/extra_plugins/task_localsearch/files/assets/js/tipuesearch_set.js b/nikola/plugins/task_localsearch/files/assets/js/tipuesearch_set.js
index 8989c3c..8989c3c 100644
--- a/extra_plugins/task_localsearch/files/assets/js/tipuesearch_set.js
+++ b/nikola/plugins/task_localsearch/files/assets/js/tipuesearch_set.js
diff --git a/nikola/plugins/task_localsearch/files/tipue_search.html b/nikola/plugins/task_localsearch/files/tipue_search.html
new file mode 100755
index 0000000..789fbe5
--- /dev/null
+++ b/nikola/plugins/task_localsearch/files/tipue_search.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+<head>
+<title>Tipue Search</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
+
+<link rel="stylesheet" type="text/css" href="assets/css/tipuesearch.css">
+<script type="text/javascript" src="assets/js/tipuesearch_set.js"></script>
+<script type="text/javascript" src="assets/js/tipuesearch.js"></script>
+
+</head>
+<body>
+
+<div style="float: left;"><input type="text" id="tipue_search_input"></div>
+<div style="float: left; margin-left: 13px;"><input type="button" id="tipue_search_button"></div>
+<div id="tipue_search_content"><div id="tipue_search_loading"></div></div>
+</div>
+
+<script type="text/javascript">
+$(document).ready(function() {
+ $('#tipue_search_input').tipuesearch({
+ 'mode': 'json',
+ 'contentLocation': 'assets/js/tipuesearch_content.json'
+ });
+});
+</script>
+</body>
+</html>
diff --git a/extra_plugins/task_mustache.plugin b/nikola/plugins/task_mustache.plugin
index 6103936..6103936 100644
--- a/extra_plugins/task_mustache.plugin
+++ b/nikola/plugins/task_mustache.plugin
diff --git a/extra_plugins/task_mustache/__init__.py b/nikola/plugins/task_mustache/__init__.py
index 8b5ec13..7364979 100644
--- a/extra_plugins/task_mustache/__init__.py
+++ b/nikola/plugins/task_mustache/__init__.py
@@ -22,11 +22,14 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+from __future__ import unicode_literals
+
+import codecs
import json
import os
from nikola.plugin_categories import Task
-from nikola.utils import config_changed, copy_file
+from nikola.utils import config_changed, copy_file, unicode_str
class Mustache(Task):
@@ -73,8 +76,7 @@ class Mustache(Task):
# Configuration
for k, v in self.site.config.items():
- # FIXME: not py3 ready
- if isinstance(v, (str, unicode)): # NOQA
+ if isinstance(v, (str, unicode_str)): # NOQA
data[k] = v
# Tag data
@@ -137,7 +139,7 @@ class Mustache(Task):
os.makedirs(os.path.dirname(path))
except:
pass
- with open(path, 'wb+') as fd:
+ with codecs.open(path, 'wb+', 'utf8') as fd:
fd.write(json.dumps(data))
for lang in kw["translations"]:
@@ -146,11 +148,17 @@ class Mustache(Task):
out_file = os.path.join(kw['output_folder'], out_path)
task = {
'basename': 'render_mustache',
- 'name': str(out_path),
+ 'name': out_file,
'file_dep': post.fragment_deps(lang),
'targets': [out_file],
'actions': [(write_file, (out_file, post, lang))],
'task_dep': ['render_posts'],
+ 'uptodate': [config_changed({
+ 1: post.text(lang),
+ 2: post.prev_post,
+ 3: post.next_post,
+ 4: post.title(lang),
+ })]
}
yield task
@@ -163,7 +171,7 @@ class Mustache(Task):
dst = os.path.join(kw['output_folder'], 'mustache-template.html')
yield {
'basename': 'render_mustache',
- 'name': 'mustache-template.html',
+ 'name': dst,
'targets': [dst],
'file_dep': [src],
'actions': [(copy_file, (src, dst))],
@@ -174,14 +182,14 @@ class Mustache(Task):
dst = os.path.join(kw['output_folder'], 'mustache.html')
def copy_mustache():
- with open(src, 'rb') as in_file:
- with open(dst, 'wb+') as out_file:
+ with codecs.open(src, 'rb', 'utf8') as in_file:
+ with codecs.open(dst, 'wb+', 'utf8') as out_file:
data = in_file.read().replace('{{first_post_data}}',
first_post_data)
out_file.write(data)
yield {
'basename': 'render_mustache',
- 'name': 'mustache.html',
+ 'name': dst,
'targets': [dst],
'file_dep': [src],
'uptodate': [config_changed({1: first_post_data})],
diff --git a/extra_plugins/task_mustache/mustache-template.html b/nikola/plugins/task_mustache/mustache-template.html
index 7f2b34c..7f2b34c 100644
--- a/extra_plugins/task_mustache/mustache-template.html
+++ b/nikola/plugins/task_mustache/mustache-template.html
diff --git a/extra_plugins/task_mustache/mustache.html b/nikola/plugins/task_mustache/mustache.html
index 5dbebef..5dbebef 100644
--- a/extra_plugins/task_mustache/mustache.html
+++ b/nikola/plugins/task_mustache/mustache.html
diff --git a/nikola/plugins/task_redirect.py b/nikola/plugins/task_redirect.py
index e440c30..2503bb7 100644
--- a/nikola/plugins/task_redirect.py
+++ b/nikola/plugins/task_redirect.py
@@ -57,7 +57,7 @@ class Redirect(Task):
src_path = os.path.join(kw["output_folder"], src)
yield {
'basename': self.name,
- 'name': src_path.encode('utf8'),
+ 'name': src_path,
'targets': [src_path],
'actions': [(create_redirect, (src_path, dst))],
'clean': True,
diff --git a/nikola/plugins/task_render_galleries.py b/nikola/plugins/task_render_galleries.py
index 0880e3e..d4e4a3a 100644
--- a/nikola/plugins/task_render_galleries.py
+++ b/nikola/plugins/task_render_galleries.py
@@ -62,6 +62,7 @@ class Galleries(Task):
'default_lang': self.site.config['DEFAULT_LANG'],
'blog_description': self.site.config['BLOG_DESCRIPTION'],
'use_filename_as_title': self.site.config['USE_FILENAME_AS_TITLE'],
+ 'gallery_path': self.site.config['GALLERY_PATH']
}
# FIXME: lots of work is done even when images don't change,
@@ -70,7 +71,7 @@ class Galleries(Task):
template_name = "gallery.tmpl"
gallery_list = []
- for root, dirs, files in os.walk('galleries'):
+ for root, dirs, files in os.walk(kw['gallery_path']):
gallery_list.append(root)
if not gallery_list:
yield {
@@ -95,7 +96,7 @@ class Galleries(Task):
if not os.path.isdir(output_gallery):
yield {
'basename': str('render_galleries'),
- 'name': str(output_gallery),
+ 'name': output_gallery,
'actions': [(os.makedirs, (output_gallery,))],
'targets': [output_gallery],
'clean': True,
@@ -152,7 +153,7 @@ class Galleries(Task):
thumbs.append(os.path.basename(thumb_path))
yield {
'basename': str('render_galleries'),
- 'name': thumb_path.encode('utf8'),
+ 'name': thumb_path,
'file_dep': [img],
'targets': [thumb_path],
'actions': [
@@ -164,7 +165,7 @@ class Galleries(Task):
}
yield {
'basename': str('render_galleries'),
- 'name': orig_dest_path.encode('utf8'),
+ 'name': orig_dest_path,
'file_dep': [img],
'targets': [orig_dest_path],
'actions': [
@@ -187,7 +188,7 @@ class Galleries(Task):
excluded_dest_path = os.path.join(output_gallery, img_name)
yield {
'basename': str('render_galleries_clean'),
- 'name': excluded_thumb_dest_path.encode('utf8'),
+ 'name': excluded_thumb_dest_path,
'file_dep': [exclude_path],
#'targets': [excluded_thumb_dest_path],
'actions': [
@@ -198,7 +199,7 @@ class Galleries(Task):
}
yield {
'basename': str('render_galleries_clean'),
- 'name': excluded_dest_path.encode('utf8'),
+ 'name': excluded_dest_path,
'file_dep': [exclude_path],
#'targets': [excluded_dest_path],
'actions': [
@@ -240,7 +241,7 @@ class Galleries(Task):
compile_html = self.site.get_compiler(index_path)
yield {
'basename': str('render_galleries'),
- 'name': index_dst_path.encode('utf-8'),
+ 'name': index_dst_path,
'file_dep': [index_path],
'targets': [index_dst_path],
'actions': [(compile_html, [index_path, index_dst_path])],
@@ -258,12 +259,11 @@ class Galleries(Task):
file_dep.append(index_dst_path)
else:
context['text'] = ''
- self.site.render_template(template_name, output_name.encode(
- 'utf8'), context)
+ self.site.render_template(template_name, output_name, context)
yield {
'basename': str('render_galleries'),
- 'name': output_name.encode('utf8'),
+ 'name': output_name,
'file_dep': file_dep,
'targets': [output_name],
'actions': [(render_gallery, (output_name, context,
@@ -303,8 +303,13 @@ class Galleries(Task):
break
- im.thumbnail(size, Image.ANTIALIAS)
- im.save(dst)
+ try:
+ im.thumbnail(size, Image.ANTIALIAS)
+ except Exception:
+ # TODO: inform the user, but do not fail
+ pass
+ else:
+ im.save(dst)
else:
utils.copy_file(src, dst)
diff --git a/nikola/plugins/task_render_listings.py b/nikola/plugins/task_render_listings.py
index b115a2f..0cadfd3 100644
--- a/nikola/plugins/task_render_listings.py
+++ b/nikola/plugins/task_render_listings.py
@@ -78,7 +78,7 @@ class Listings(Task):
'files': files,
'description': title,
}
- self.site.render_template('listing.tmpl', out_name.encode('utf8'),
+ self.site.render_template('listing.tmpl', out_name,
context)
flag = True
template_deps = self.site.template_system.template_deps('listing.tmpl')
@@ -91,14 +91,15 @@ class Listings(Task):
)
yield {
'basename': self.name,
- 'name': out_name.encode('utf8'),
+ 'name': out_name,
'file_dep': template_deps,
'targets': [out_name],
'actions': [(render_listing, [None, out_name, dirs, files])],
# This is necessary to reflect changes in blog title,
# sidebar links, etc.
'uptodate': [utils.config_changed(
- self.site.config['GLOBAL_CONTEXT'])]
+ self.site.config['GLOBAL_CONTEXT'])],
+ 'clean': True,
}
for f in files:
ext = os.path.splitext(f)[-1]
@@ -111,14 +112,15 @@ class Listings(Task):
f) + '.html'
yield {
'basename': self.name,
- 'name': out_name.encode('utf8'),
+ 'name': out_name,
'file_dep': template_deps + [in_name],
'targets': [out_name],
'actions': [(render_listing, [in_name, out_name])],
# This is necessary to reflect changes in blog title,
# sidebar links, etc.
'uptodate': [utils.config_changed(
- self.site.config['GLOBAL_CONTEXT'])]
+ self.site.config['GLOBAL_CONTEXT'])],
+ 'clean': True,
}
if flag:
yield {
diff --git a/nikola/plugins/task_render_pages.py b/nikola/plugins/task_render_pages.py
index 0145579..1883d7b 100644
--- a/nikola/plugins/task_render_pages.py
+++ b/nikola/plugins/task_render_pages.py
@@ -22,6 +22,7 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+from __future__ import unicode_literals
from nikola.plugin_categories import Task
from nikola.utils import config_changed
@@ -37,17 +38,21 @@ class RenderPages(Task):
"post_pages": self.site.config["post_pages"],
"translations": self.site.config["TRANSLATIONS"],
"filters": self.site.config["FILTERS"],
+ "hide_untranslated_posts": self.site.config['HIDE_UNTRANSLATED_POSTS'],
}
self.site.scan_posts()
flag = False
for lang in kw["translations"]:
for post in self.site.timeline:
+ if kw["hide_untranslated_posts"] and not post.is_translation_available(lang):
+ continue
for task in self.site.generic_page_renderer(lang, post,
kw["filters"]):
task['uptodate'] = [config_changed({
1: task['uptodate'][0].config,
2: kw})]
task['basename'] = self.name
+ task['task_dep'] = ['render_posts']
flag = True
yield task
if flag is False: # No page rendered, yield a dummy task
diff --git a/nikola/plugins/task_render_posts.py b/nikola/plugins/task_render_posts.py
index a4d5578..4be68bf 100644
--- a/nikola/plugins/task_render_posts.py
+++ b/nikola/plugins/task_render_posts.py
@@ -23,10 +23,20 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from copy import copy
-import os
+import codecs
+import string
from nikola.plugin_categories import Task
-from nikola import utils
+from nikola import utils, rc4
+
+
+def wrap_encrypt(path, password):
+ """Wrap a post with encryption."""
+ with codecs.open(path, 'rb+', 'utf8') as inf:
+ data = inf.read() + "<!--tail-->"
+ data = CRYPT.substitute(data=rc4.rc4(password, data))
+ with codecs.open(path, 'wb+', 'utf8') as outf:
+ outf.write(data)
class RenderPosts(Task):
@@ -41,25 +51,26 @@ class RenderPosts(Task):
"translations": self.site.config["TRANSLATIONS"],
"timeline": self.site.timeline,
"default_lang": self.site.config["DEFAULT_LANG"],
+ "hide_untranslated_posts": self.site.config['HIDE_UNTRANSLATED_POSTS'],
}
flag = False
for lang in kw["translations"]:
- # TODO: timeline is global, get rid of it
deps_dict = copy(kw)
deps_dict.pop('timeline')
for post in kw['timeline']:
source = post.source_path
dest = post.base_path
- if lang != kw["default_lang"]:
- dest += '.' + lang
- source_lang = source + '.' + lang
- if os.path.exists(source_lang):
- source = source_lang
+ if not post.is_translation_available(lang) and kw["hide_untranslated_posts"]:
+ continue
+ else:
+ source = post.translated_source_path(lang)
+ if lang != post.default_lang:
+ dest = dest + '.' + lang
flag = True
- yield {
+ task = {
'basename': self.name,
- 'name': dest.encode('utf-8'),
+ 'name': dest,
'file_dep': post.fragment_deps(lang),
'targets': [dest],
'actions': [(self.site.get_compiler(post.source_path),
@@ -67,6 +78,9 @@ class RenderPosts(Task):
'clean': True,
'uptodate': [utils.config_changed(deps_dict)],
}
+ if post.meta('password'):
+ task['actions'].append((wrap_encrypt, (dest, post.meta('password'))))
+ yield task
if flag is False: # Return a dummy task
yield {
'basename': self.name,
@@ -74,3 +88,53 @@ class RenderPosts(Task):
'uptodate': [True],
'actions': [],
}
+
+
+CRYPT = string.Template("""\
+<script>
+function rc4(key, str) {
+ var s = [], j = 0, x, res = '';
+ for (var i = 0; i < 256; i++) {
+ s[i] = i;
+ }
+ for (i = 0; i < 256; i++) {
+ j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
+ x = s[i];
+ s[i] = s[j];
+ s[j] = x;
+ }
+ i = 0;
+ j = 0;
+ for (var y = 0; y < str.length; y++) {
+ i = (i + 1) % 256;
+ j = (j + s[i]) % 256;
+ x = s[i];
+ s[i] = s[j];
+ s[j] = x;
+ res += String.fromCharCode(str.charCodeAt(y) ^ s[(s[i] + s[j]) % 256]);
+ }
+ return res;
+}
+function decrypt() {
+ key = $$("#key").val();
+ crypt_div = $$("#encr")
+ crypted = crypt_div.html();
+ decrypted = rc4(key, window.atob(crypted));
+ if (decrypted.substr(decrypted.length - 11) == "<!--tail-->"){
+ crypt_div.html(decrypted);
+ $$("#pwform").hide();
+ crypt_div.show();
+ } else { alert("Wrong password"); };
+}
+</script>
+
+<div id="encr" style="display: none;">${data}</div>
+<div id="pwform">
+<form onsubmit="javascript:decrypt(); return false;" class="form-inline">
+<fieldset>
+<legend>This post is password-protected.</legend>
+<input type="password" id="key" placeholder="Type password here">
+<button type="submit" class="btn">Show Content</button>
+</fieldset>
+</form>
+</div>""")
diff --git a/nikola/plugins/task_render_rss.py b/nikola/plugins/task_render_rss.py
index 9ce1d63..3000e47 100644
--- a/nikola/plugins/task_render_rss.py
+++ b/nikola/plugins/task_render_rss.py
@@ -43,25 +43,30 @@ class RenderRSS(Task):
"blog_description": self.site.config["BLOG_DESCRIPTION"],
"output_folder": self.site.config["OUTPUT_FOLDER"],
"rss_teasers": self.site.config["RSS_TEASERS"],
+ "hide_untranslated_posts": self.site.config['HIDE_UNTRANSLATED_POSTS'],
}
self.site.scan_posts()
- # TODO: timeline is global, kill it
for lang in kw["translations"]:
output_name = os.path.join(kw['output_folder'],
self.site.path("rss", None, lang))
deps = []
- posts = [x for x in self.site.timeline if x.use_in_feeds][:10]
+ if kw["hide_untranslated_posts"]:
+ posts = [x for x in self.site.timeline if x.use_in_feeds
+ and x.is_translation_available(lang)][:10]
+ else:
+ posts = [x for x in self.site.timeline if x.use_in_feeds][:10]
for post in posts:
deps += post.deps(lang)
yield {
'basename': 'render_rss',
- 'name': output_name.encode('utf8'),
+ 'name': os.path.normpath(output_name),
'file_dep': deps,
'targets': [output_name],
'actions': [(utils.generic_rss_renderer,
(lang, kw["blog_title"], kw["site_url"],
kw["blog_description"], posts, output_name,
kw["rss_teasers"]))],
+ 'task_dep': ['render_posts'],
'clean': True,
'uptodate': [utils.config_changed(kw)],
}
diff --git a/nikola/plugins/task_render_sources.py b/nikola/plugins/task_render_sources.py
index 529e68e..392345c 100644
--- a/nikola/plugins/task_render_sources.py
+++ b/nikola/plugins/task_render_sources.py
@@ -53,6 +53,8 @@ class Sources(Task):
flag = False
for lang in kw["translations"]:
for post in self.site.timeline:
+ if post.meta('password'):
+ continue
output_name = os.path.join(
kw['output_folder'], post.destination_path(
lang, post.source_ext()))
@@ -63,15 +65,16 @@ class Sources(Task):
source_lang = source + '.' + lang
if os.path.exists(source_lang):
source = source_lang
- yield {
- 'basename': 'render_sources',
- 'name': output_name.encode('utf8'),
- 'file_dep': [source],
- 'targets': [output_name],
- 'actions': [(utils.copy_file, (source, output_name))],
- 'clean': True,
- 'uptodate': [utils.config_changed(kw)],
- }
+ if os.path.isfile(source):
+ yield {
+ 'basename': 'render_sources',
+ 'name': os.path.normpath(output_name),
+ 'file_dep': [source],
+ 'targets': [output_name],
+ 'actions': [(utils.copy_file, (source, output_name))],
+ 'clean': True,
+ 'uptodate': [utils.config_changed(kw)],
+ }
if flag is False: # No page rendered, yield a dummy task
yield {
'basename': 'render_sources',
diff --git a/nikola/plugins/task_render_tags.py b/nikola/plugins/task_render_tags.py
index 744f0cb..58a7ff3 100644
--- a/nikola/plugins/task_render_tags.py
+++ b/nikola/plugins/task_render_tags.py
@@ -52,6 +52,7 @@ class RenderTags(Task):
self.site.config['INDEX_DISPLAY_POST_COUNT'],
"index_teasers": self.site.config['INDEX_TEASERS'],
"rss_teasers": self.site.config["RSS_TEASERS"],
+ "hide_untranslated_posts": self.site.config['HIDE_UNTRANSLATED_POSTS'],
}
self.site.scan_posts()
@@ -67,13 +68,17 @@ class RenderTags(Task):
post_list.sort(key=lambda a: a.date)
post_list.reverse()
for lang in kw["translations"]:
- yield self.tag_rss(tag, lang, posts, kw)
-
+ if kw["hide_untranslated_posts"]:
+ filtered_posts = [x for x in post_list if x.is_translation_available(lang)]
+ else:
+ filtered_posts = post_list
+ rss_post_list = [p.post_name for p in filtered_posts]
+ yield self.tag_rss(tag, lang, rss_post_list, kw)
# Render HTML
if kw['tag_pages_are_indexes']:
- yield self.tag_page_as_index(tag, lang, post_list, kw)
+ yield self.tag_page_as_index(tag, lang, filtered_posts, kw)
else:
- yield self.tag_page_as_list(tag, lang, post_list, kw)
+ yield self.tag_page_as_list(tag, lang, filtered_posts, kw)
# Tag cloud json file
tag_cloud_data = {}
@@ -98,6 +103,7 @@ class RenderTags(Task):
task['uptodate'] = [utils.config_changed(tag_cloud_data)]
task['targets'] = [output_name]
task['actions'] = [(write_tag_data, [tag_cloud_data])]
+ task['clean'] = True
yield task
def list_tags_page(self, kw):
@@ -110,7 +116,7 @@ class RenderTags(Task):
for lang in kw["translations"]:
output_name = os.path.join(
kw['output_folder'], self.site.path('tag_index', None, lang))
- output_name = output_name.encode('utf8')
+ output_name = output_name
context = {}
context["title"] = kw["messages"][lang]["Tags"]
context["items"] = [(tag, self.site.link("tag", tag, lang)) for tag
@@ -157,7 +163,6 @@ class RenderTags(Task):
context['rss_link'] = rss_link
output_name = os.path.join(kw['output_folder'],
page_name(tag, i, lang))
- output_name = output_name.encode('utf8')
context["title"] = kw["messages"][lang][
"Posts about %s"] % tag
context["prevlink"] = None
@@ -192,7 +197,6 @@ class RenderTags(Task):
template_name = "tag.tmpl"
output_name = os.path.join(kw['output_folder'], self.site.path(
"tag", tag, lang))
- output_name = output_name.encode('utf8')
context = {}
context["lang"] = lang
context["title"] = kw["messages"][lang]["Posts about %s"] % tag
@@ -217,7 +221,6 @@ class RenderTags(Task):
#Render RSS
output_name = os.path.join(kw['output_folder'],
self.site.path("tag_rss", tag, lang))
- output_name = output_name.encode('utf8')
deps = []
post_list = [self.site.global_data[post] for post in posts if
self.site.global_data[post].use_in_feeds]
@@ -236,4 +239,5 @@ class RenderTags(Task):
output_name, kw["rss_teasers"]))],
'clean': True,
'uptodate': [utils.config_changed(kw)],
+ 'task_dep': ['render_posts'],
}
diff --git a/nikola/plugins/task_sitemap/__init__.py b/nikola/plugins/task_sitemap/__init__.py
index 9d89070..044e0e3 100644
--- a/nikola/plugins/task_sitemap/__init__.py
+++ b/nikola/plugins/task_sitemap/__init__.py
@@ -22,72 +22,82 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-from __future__ import print_function, absolute_import
+from __future__ import print_function, absolute_import, unicode_literals
+import codecs
+import datetime
import os
-import sys
-import tempfile
+try:
+ from urlparse import urljoin
+except ImportError:
+ from urllib.parse import urljoin # NOQA
from nikola.plugin_categories import LateTask
from nikola.utils import config_changed
-from nikola.plugins.task_sitemap import sitemap_gen
+
+header = """<?xml version="1.0" encoding="UTF-8"?>
+<urlset
+ xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
+ http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
+"""
+
+url_format = """ <url>
+ <loc>{0}</loc>
+ <lastmod>{1}</lastmod>
+ <priority>0.5000</priority>
+ </url>
+"""
+
+get_lastmod = lambda p: datetime.datetime.fromtimestamp(os.stat(p).st_mtime).isoformat().split('T')[0]
class Sitemap(LateTask):
- """Copy theme assets into output."""
+ """Generate google sitemap."""
name = "sitemap"
def gen_tasks(self):
- if sys.version_info[0] == 3:
- print("sitemap generation is not available for python 3")
- yield {
- 'basename': 'sitemap',
- 'name': 'sitemap',
- 'actions': [],
- }
- return
"""Generate Google sitemap."""
kw = {
"base_url": self.site.config["BASE_URL"],
"site_url": self.site.config["SITE_URL"],
"output_folder": self.site.config["OUTPUT_FOLDER"],
+ "mapped_extensions": self.site.config.get('MAPPED_EXTENSIONS', ['.html', '.htm'])
}
- output_path = os.path.abspath(kw['output_folder'])
- sitemap_path = os.path.join(output_path, "sitemap.xml.gz")
+ output_path = kw['output_folder']
+ sitemap_path = os.path.join(output_path, "sitemap.xml")
def sitemap():
- # Generate config
- config_data = """<?xml version="1.0" encoding="UTF-8"?>
- <site
- base_url="{0}"
- store_into="{1}"
- verbose="1" >
- <directory path="{2}" url="{3}" />
- <filter action="drop" type="wildcard" pattern="*~" />
- <filter action="drop" type="regexp" pattern="/\.[^/]*" />
- </site>""".format(kw["site_url"], sitemap_path, output_path,
- kw["base_url"])
- config_file = tempfile.NamedTemporaryFile(delete=False)
- config_file.write(config_data.encode('utf8'))
- config_file.close()
+ with codecs.open(sitemap_path, 'wb+', 'utf8') as outf:
+ output = kw['output_folder']
+ base_url = kw['base_url']
+ mapped_exts = kw['mapped_extensions']
+ outf.write(header)
+ locs = {}
+ for root, dirs, files in os.walk(output):
+ path = os.path.relpath(root, output)
+ path = path.replace(os.sep, '/') + '/'
+ lastmod = get_lastmod(root)
+ loc = urljoin(base_url, path)
+ locs[loc] = url_format.format(loc, lastmod)
+ for fname in files:
+ if os.path.splitext(fname)[-1] in mapped_exts:
+ real_path = os.path.join(root, fname)
+ path = os.path.relpath(real_path, output)
+ path = path.replace(os.sep, '/')
+ lastmod = get_lastmod(real_path)
+ loc = urljoin(base_url, path)
+ locs[loc] = url_format.format(loc, lastmod)
- # Generate sitemap
- sitemap = sitemap_gen.CreateSitemapFromFile(config_file.name, True)
- if not sitemap:
- sitemap_gen.output.Log('Configuration file errors -- exiting.',
- 0)
- else:
- sitemap.Generate()
- sitemap_gen.output.Log('Number of errors: {0}'.format(
- sitemap_gen.output.num_errors), 1)
- sitemap_gen.output.Log('Number of warnings: {0}'.format(
- sitemap_gen.output.num_warns), 1)
- os.unlink(config_file.name)
+ for k in sorted(locs.keys()):
+ outf.write(locs[k])
+ outf.write("</urlset>")
yield {
"basename": "sitemap",
- "name": os.path.join(kw['output_folder'], "sitemap.xml.gz"),
+ "name": sitemap_path,
"targets": [sitemap_path],
"actions": [(sitemap,)],
"uptodate": [config_changed(kw)],
diff --git a/nikola/plugins/task_sitemap/sitemap_gen.py b/nikola/plugins/task_sitemap/sitemap_gen.py
deleted file mode 100644
index 898325a..0000000
--- a/nikola/plugins/task_sitemap/sitemap_gen.py
+++ /dev/null
@@ -1,2137 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (c) 2004, 2005 Google Inc.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#
-# * 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.
-#
-# * Neither the name of Google nor the names of its contributors may
-# be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# 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.
-#
-#
-# The sitemap_gen.py script is written in Python 2.2 and released to
-# the open source community for continuous improvements under the BSD
-# 2.0 new license, which can be found at:
-#
-# http://www.opensource.org/licenses/bsd-license.php
-#
-from __future__ import print_function
-
-__usage__ = \
- """A simple script to automatically produce sitemaps for a webserver,
-in the Google Sitemap Protocol (GSP).
-
-Usage: python sitemap_gen.py --config=config.xml [--help] [--testing]
- --config=config.xml, specifies config file location
- --help, displays usage message
- --testing, specified when user is experimenting
-"""
-
-import fnmatch
-import glob
-import gzip
-import os
-import re
-import stat
-import sys
-import time
-import urllib
-import xml.sax
-
-try:
- import md5
-except ImportError:
- md5 = None # NOQA
- import hashlib
-
-try:
- from urlparse import urlsplit, urlunsplit, urljoin
-except ImportError:
- from urllib.parse import urlsplit, urlunsplit, urljoin # NOQA
-
-try:
- from urllib import quote as urllib_quote
- from urllib import FancyURLopener
- from urllib import urlopen
-except ImportError:
- from urllib.parse import quote as urllib_quote # NOQA
- from urllib.request import FancyURLopener # NOQA
- from urllib.request import urlopen # NOQA
-
-
-if sys.version_info[0] == 3:
- # Python 3
- bytes_str = bytes
- unicode_str = str
- unichr = chr
-else:
- bytes_str = str
- unicode_str = unicode # NOQA
-
-# Text encodings
-ENC_ASCII = 'ASCII'
-ENC_UTF8 = 'UTF-8'
-ENC_IDNA = 'IDNA'
-ENC_ASCII_LIST = ['ASCII', 'US-ASCII', 'US', 'IBM367', 'CP367', 'ISO646-US'
- 'ISO_646.IRV:1991', 'ISO-IR-6', 'ANSI_X3.4-1968',
- 'ANSI_X3.4-1986', 'CPASCII']
-ENC_DEFAULT_LIST = ['ISO-8859-1', 'ISO-8859-2', 'ISO-8859-5']
-
-# Available Sitemap types
-SITEMAP_TYPES = ['web', 'mobile', 'news']
-
-# General Sitemap tags
-GENERAL_SITEMAP_TAGS = ['loc', 'changefreq', 'priority', 'lastmod']
-
-# News specific tags
-NEWS_SPECIFIC_TAGS = ['keywords', 'publication_date', 'stock_tickers']
-
-# News Sitemap tags
-NEWS_SITEMAP_TAGS = GENERAL_SITEMAP_TAGS + NEWS_SPECIFIC_TAGS
-
-# Maximum number of urls in each sitemap, before next Sitemap is created
-MAXURLS_PER_SITEMAP = 50000
-
-# Suffix on a Sitemap index file
-SITEINDEX_SUFFIX = '_index.xml'
-
-# Regular expressions tried for extracting URLs from access logs.
-ACCESSLOG_CLF_PATTERN = re.compile(
- r'.+\s+"([^\s]+)\s+([^\s]+)\s+HTTP/\d+\.\d+"\s+200\s+.*'
-)
-
-# Match patterns for lastmod attributes
-DATE_PATTERNS = list(map(re.compile, [
- r'^\d\d\d\d$',
- r'^\d\d\d\d-\d\d$',
- r'^\d\d\d\d-\d\d-\d\d$',
- r'^\d\d\d\d-\d\d-\d\dT\d\d:\d\dZ$',
- r'^\d\d\d\d-\d\d-\d\dT\d\d:\d\d[+-]\d\d:\d\d$',
- r'^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z$',
- r'^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?[+-]\d\d:\d\d$',
-]))
-
-# Match patterns for changefreq attributes
-CHANGEFREQ_PATTERNS = [
- 'always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'
-]
-
-# XML formats
-GENERAL_SITEINDEX_HEADER = \
- '<?xml version="1.0" encoding="UTF-8"?>\n' \
- '<sitemapindex\n' \
- ' xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"\n' \
- ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' \
- ' xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9\n' \
- ' http://www.sitemaps.org/schemas/sitemap/0.9/' \
- 'siteindex.xsd">\n'
-
-NEWS_SITEINDEX_HEADER = \
- '<?xml version="1.0" encoding="UTF-8"?>\n' \
- '<sitemapindex\n' \
- ' xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"\n' \
- ' xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"\n' \
- ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' \
- ' xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9\n' \
- ' http://www.sitemaps.org/schemas/sitemap/0.9/' \
- 'siteindex.xsd">\n'
-
-SITEINDEX_FOOTER = '</sitemapindex>\n'
-SITEINDEX_ENTRY = \
- ' <sitemap>\n' \
- ' <loc>%(loc)s</loc>\n' \
- ' <lastmod>%(lastmod)s</lastmod>\n' \
- ' </sitemap>\n'
-GENERAL_SITEMAP_HEADER = \
- '<?xml version="1.0" encoding="UTF-8"?>\n' \
- '<urlset\n' \
- ' xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"\n' \
- ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' \
- ' xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9\n' \
- ' http://www.sitemaps.org/schemas/sitemap/0.9/' \
- 'sitemap.xsd">\n'
-
-NEWS_SITEMAP_HEADER = \
- '<?xml version="1.0" encoding="UTF-8"?>\n' \
- '<urlset\n' \
- ' xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"\n' \
- ' xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"\n' \
- ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' \
- ' xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9\n' \
- ' http://www.sitemaps.org/schemas/sitemap/0.9/' \
- 'sitemap.xsd">\n'
-
-SITEMAP_FOOTER = '</urlset>\n'
-SITEURL_XML_PREFIX = ' <url>\n'
-SITEURL_XML_SUFFIX = ' </url>\n'
-
-NEWS_TAG_XML_PREFIX = ' <news:news>\n'
-NEWS_TAG_XML_SUFFIX = ' </news:news>\n'
-
-# Search engines to notify with the updated sitemaps
-#
-# This list is very non-obvious in what's going on. Here's the gist:
-# Each item in the list is a 6-tuple of items. The first 5 are "almost"
-# the same as the input arguments to urlparse.urlunsplit():
-# 0 - schema
-# 1 - netloc
-# 2 - path
-# 3 - query <-- EXCEPTION: specify a query map rather than a string
-# 4 - fragment
-# Additionally, add item 5:
-# 5 - query attribute that should be set to the new Sitemap URL
-# Clear as mud, I know.
-NOTIFICATION_SITES = [
- ('http', 'www.google.com', 'webmasters/sitemaps/ping', {}, '', 'sitemap'),
-]
-
-
-def get_hash(text):
- if md5 is not None:
- return md5.new(text).digest()
- else:
- m = hashlib.md5()
- m.update(text.encode('utf8'))
- return m.digest()
-
-
-class Error(Exception):
- """
- Base exception class. In this module we tend not to use our own exception
- types for very much, but they come in very handy on XML parsing with SAX.
- """
- pass
-# end class Error
-
-
-class SchemaError(Error):
- """Failure to process an XML file according to the schema we know."""
- pass
-# end class SchemeError
-
-
-class Encoder:
- """
- Manages wide-character/narrow-character conversions for just about all
- text that flows into or out of the script.
-
- You should always use this class for string coercion, as opposed to
- letting Python handle coercions automatically. Reason: Python
- usually assumes ASCII (7-bit) as a default narrow character encoding,
- which is not the kind of data we generally deal with.
-
- General high-level methodologies used in sitemap_gen:
-
- [PATHS]
- File system paths may be wide or narrow, depending on platform.
- This works fine, just be aware of it and be very careful to not
- mix them. That is, if you have to pass several file path arguments
- into a library call, make sure they are all narrow or all wide.
- This class has MaybeNarrowPath() which should be called on every
- file system path you deal with.
-
- [URLS]
- URL locations are stored in Narrow form, already escaped. This has the
- benefit of keeping escaping and encoding as close as possible to the format
- we read them in. The downside is we may end up with URLs that have
- intermingled encodings -- the root path may be encoded in one way
- while the filename is encoded in another. This is obviously wrong, but
- it should hopefully be an issue hit by very few users. The workaround
- from the user level (assuming they notice) is to specify a default_encoding
- parameter in their config file.
-
- [OTHER]
- Other text, such as attributes of the URL class, configuration options,
- etc, are generally stored in Unicode for simplicity.
- """
-
- def __init__(self):
- self._user = None # User-specified default encoding
- self._learned = [] # Learned default encodings
- self._widefiles = False # File system can be wide
-
- # Can the file system be Unicode?
- try:
- self._widefiles = os.path.supports_unicode_filenames
- except AttributeError:
- try:
- self._widefiles = sys.getwindowsversion(
- ) == os.VER_PLATFORM_WIN32_NT
- except AttributeError:
- pass
-
- # Try to guess a working default
- try:
- encoding = sys.getfilesystemencoding()
- if encoding and not (encoding.upper() in ENC_ASCII_LIST):
- self._learned = [encoding]
- except AttributeError:
- pass
-
- if not self._learned:
- encoding = sys.getdefaultencoding()
- if encoding and not (encoding.upper() in ENC_ASCII_LIST):
- self._learned = [encoding]
-
- # If we had no guesses, start with some European defaults
- if not self._learned:
- self._learned = ENC_DEFAULT_LIST
- # end def __init__
-
- def SetUserEncoding(self, encoding):
- self._user = encoding
- # end def SetUserEncoding
-
- def NarrowText(self, text, encoding):
- """ Narrow a piece of arbitrary text """
- if isinstance(text, bytes_str):
- return text
-
- # Try the passed in preference
- if encoding:
- try:
- result = text.encode(encoding)
- if not encoding in self._learned:
- self._learned.append(encoding)
- return result
- except UnicodeError:
- pass
- except LookupError:
- output.Warn('Unknown encoding: %s' % encoding)
-
- # Try the user preference
- if self._user:
- try:
- return text.encode(self._user)
- except UnicodeError:
- pass
- except LookupError:
- temp = self._user
- self._user = None
- output.Warn('Unknown default_encoding: %s' % temp)
-
- # Look through learned defaults, knock any failing ones out of the list
- while self._learned:
- try:
- return text.encode(self._learned[0])
- except:
- del self._learned[0]
-
- # When all other defaults are exhausted, use UTF-8
- try:
- return text.encode(ENC_UTF8)
- except UnicodeError:
- pass
-
- # Something is seriously wrong if we get to here
- return text.encode(ENC_ASCII, 'ignore')
- # end def NarrowText
-
- def MaybeNarrowPath(self, text):
- """ Paths may be allowed to stay wide """
- if self._widefiles:
- return text
- return self.NarrowText(text, None)
- # end def MaybeNarrowPath
-
- def WidenText(self, text, encoding):
- """ Widen a piece of arbitrary text """
- if not isinstance(text, bytes_str):
- return text
-
- # Try the passed in preference
- if encoding:
- try:
- result = unicode_str(text, encoding)
- if not encoding in self._learned:
- self._learned.append(encoding)
- return result
- except UnicodeError:
- pass
- except LookupError:
- output.Warn('Unknown encoding: %s' % encoding)
-
- # Try the user preference
- if self._user:
- try:
- return unicode_str(text, self._user)
- except UnicodeError:
- pass
- except LookupError:
- temp = self._user
- self._user = None
- output.Warn('Unknown default_encoding: %s' % temp)
-
- # Look through learned defaults, knock any failing ones out of the list
- while self._learned:
- try:
- return unicode_str(text, self._learned[0])
- except:
- del self._learned[0]
-
- # When all other defaults are exhausted, use UTF-8
- try:
- return unicode_str(text, ENC_UTF8)
- except UnicodeError:
- pass
-
- # Getting here means it wasn't UTF-8 and we had no working default.
- # We really don't have anything "right" we can do anymore.
- output.Warn('Unrecognized encoding in text: %s' % text)
- if not self._user:
- output.Warn('You may need to set a default_encoding in your '
- 'configuration file.')
- return text.decode(ENC_ASCII, 'ignore')
- # end def WidenText
-# end class Encoder
-encoder = Encoder()
-
-
-class Output:
- """
- Exposes logging functionality, and tracks how many errors
- we have thus output.
-
- Logging levels should be used as thus:
- Fatal -- extremely sparingly
- Error -- config errors, entire blocks of user 'intention' lost
- Warn -- individual URLs lost
- Log(,0) -- Un-suppressable text that's not an error
- Log(,1) -- touched files, major actions
- Log(,2) -- parsing notes, filtered or duplicated URLs
- Log(,3) -- each accepted URL
- """
-
- def __init__(self):
- self.num_errors = 0 # Count of errors
- self.num_warns = 0 # Count of warnings
-
- self._errors_shown = {} # Shown errors
- self._warns_shown = {} # Shown warnings
- self._verbose = 0 # Level of verbosity
- # end def __init__
-
- def Log(self, text, level):
- """ Output a blurb of diagnostic text, if the verbose level allows it """
- if text:
- text = encoder.NarrowText(text, None)
- if self._verbose >= level:
- print(text)
- # end def Log
-
- def Warn(self, text):
- """ Output and count a warning. Suppress duplicate warnings. """
- if text:
- text = encoder.NarrowText(text, None)
- hash = get_hash(text)
- if not hash in self._warns_shown:
- self._warns_shown[hash] = 1
- print('[WARNING] ' + text)
- else:
- self.Log('(suppressed) [WARNING] ' + text, 3)
- self.num_warns = self.num_warns + 1
- # end def Warn
-
- def Error(self, text):
- """ Output and count an error. Suppress duplicate errors. """
- if text:
- text = encoder.NarrowText(text, None)
- hash = get_hash(text)
- if not hash in self._errors_shown:
- self._errors_shown[hash] = 1
- print('[ERROR] ' + text)
- else:
- self.Log('(suppressed) [ERROR] ' + text, 3)
- self.num_errors = self.num_errors + 1
- # end def Error
-
- def Fatal(self, text):
- """ Output an error and terminate the program. """
- if text:
- text = encoder.NarrowText(text, None)
- print('[FATAL] ' + text)
- else:
- print('Fatal error.')
- sys.exit(1)
- # end def Fatal
-
- def SetVerbose(self, level):
- """ Sets the verbose level. """
- try:
- if not isinstance(level, int):
- level = int(level)
- if (level >= 0) and (level <= 3):
- self._verbose = level
- return
- except ValueError:
- pass
- self.Error(
- 'Verbose level (%s) must be between 0 and 3 inclusive.' % level)
- # end def SetVerbose
-# end class Output
-output = Output()
-
-
-class URL(object):
- """ URL is a smart structure grouping together the properties we
- care about for a single web reference. """
- __slots__ = 'loc', 'lastmod', 'changefreq', 'priority'
-
- def __init__(self):
- self.loc = None # URL -- in Narrow characters
- self.lastmod = None # ISO8601 timestamp of last modify
- self.changefreq = None # Text term for update frequency
- self.priority = None # Float between 0 and 1 (inc)
- # end def __init__
-
- def __cmp__(self, other):
- if self.loc < other.loc:
- return -1
- if self.loc > other.loc:
- return 1
- return 0
- # end def __cmp__
-
- def TrySetAttribute(self, attribute, value):
- """ Attempt to set the attribute to the value, with a pretty try
- block around it. """
- if attribute == 'loc':
- self.loc = self.Canonicalize(value)
- else:
- try:
- setattr(self, attribute, value)
- except AttributeError:
- output.Warn('Unknown URL attribute: %s' % attribute)
- # end def TrySetAttribute
-
- def IsAbsolute(loc):
- """ Decide if the URL is absolute or not """
- if not loc:
- return False
- narrow = encoder.NarrowText(loc, None)
- (scheme, netloc, path, query, frag) = urlsplit(narrow)
- if (not scheme) or (not netloc):
- return False
- return True
- # end def IsAbsolute
- IsAbsolute = staticmethod(IsAbsolute)
-
- def Canonicalize(loc):
- """ Do encoding and canonicalization on a URL string """
- if not loc:
- return loc
-
- # Let the encoder try to narrow it
- narrow = encoder.NarrowText(loc, None)
-
- # Escape components individually
- (scheme, netloc, path, query, frag) = urlsplit(narrow)
- unr = '-._~'
- sub = '!$&\'()*+,;='
- netloc = urllib_quote(netloc, unr + sub + '%:@/[]')
- path = urllib_quote(path, unr + sub + '%:@/')
- query = urllib_quote(query, unr + sub + '%:@/?')
- frag = urllib_quote(frag, unr + sub + '%:@/?')
-
- # Try built-in IDNA encoding on the netloc
- try:
- (ignore, widenetloc, ignore, ignore, ignore) = urlsplit(loc)
- for c in widenetloc:
- if c >= unichr(128):
- netloc = widenetloc.encode(ENC_IDNA)
- netloc = urllib_quote(netloc, unr + sub + '%:@/[]')
- break
- except UnicodeError:
- # urlsplit must have failed, based on implementation differences in the
- # library. There is not much we can do here, except ignore it.
- pass
- except LookupError:
- output.Warn('An International Domain Name (IDN) is being used, but this '
- 'version of Python does not have support for IDNA encoding. '
- ' (IDNA support was introduced in Python 2.3) The encoding '
- 'we have used instead is wrong and will probably not yield '
- 'valid URLs.')
- bad_netloc = False
- if '%' in netloc:
- bad_netloc = True
-
- # Put it all back together
- narrow = urlunsplit((scheme, netloc, path, query, frag))
-
- # I let '%' through. Fix any that aren't pre-existing escapes.
- HEXDIG = '0123456789abcdefABCDEF'
- list = narrow.split('%')
- narrow = list[0]
- del list[0]
- for item in list:
- if (len(item) >= 2) and (item[0] in HEXDIG) and (item[1] in HEXDIG):
- narrow = narrow + '%' + item
- else:
- narrow = narrow + '%25' + item
-
- # Issue a warning if this is a bad URL
- if bad_netloc:
- output.Warn('Invalid characters in the host or domain portion of a URL: '
- + narrow)
-
- return narrow
- # end def Canonicalize
- Canonicalize = staticmethod(Canonicalize)
-
- def VerifyDate(self, date, metatag):
- """Verify the date format is valid"""
- match = False
- if date:
- date = date.upper()
- for pattern in DATE_PATTERNS:
- match = pattern.match(date)
- if match:
- return True
- if not match:
- output.Warn('The value for %s does not appear to be in ISO8601 '
- 'format on URL: %s' % (metatag, self.loc))
- return False
- # end of VerifyDate
-
- def Validate(self, base_url, allow_fragment):
- """ Verify the data in this URL is well-formed, and override if not. """
- assert isinstance(base_url, bytes_str)
-
- # Test (and normalize) the ref
- if not self.loc:
- output.Warn('Empty URL')
- return False
- if allow_fragment:
- self.loc = urljoin(base_url, self.loc)
- if not self.loc.startswith(base_url):
- output.Warn('Discarded URL for not starting with the base_url: %s' %
- self.loc)
- self.loc = None
- return False
-
- # Test the lastmod
- if self.lastmod:
- if not self.VerifyDate(self.lastmod, "lastmod"):
- self.lastmod = None
-
- # Test the changefreq
- if self.changefreq:
- match = False
- self.changefreq = self.changefreq.lower()
- for pattern in CHANGEFREQ_PATTERNS:
- if self.changefreq == pattern:
- match = True
- break
- if not match:
- output.Warn('Changefreq "%s" is not a valid change frequency on URL '
- ': %s' % (self.changefreq, self.loc))
- self.changefreq = None
-
- # Test the priority
- if self.priority:
- priority = -1.0
- try:
- priority = float(self.priority)
- except ValueError:
- pass
- if (priority < 0.0) or (priority > 1.0):
- output.Warn('Priority "%s" is not a number between 0 and 1 inclusive '
- 'on URL: %s' % (self.priority, self.loc))
- self.priority = None
-
- return True
- # end def Validate
-
- def MakeHash(self):
- """ Provides a uniform way of hashing URLs """
- if not self.loc:
- return None
- if self.loc.endswith('/'):
- return get_hash(self.loc[:-1])
- return get_hash(self.loc)
- # end def MakeHash
-
- def Log(self, prefix='URL', level=3):
- """ Dump the contents, empty or not, to the log. """
- out = prefix + ':'
-
- for attribute in self.__slots__:
- value = getattr(self, attribute)
- if not value:
- value = ''
- out = out + (' %s=[%s]' % (attribute, value))
-
- output.Log('%s' % encoder.NarrowText(out, None), level)
- # end def Log
-
- def WriteXML(self, file):
- """ Dump non-empty contents to the output file, in XML format. """
- if not self.loc:
- return
- out = SITEURL_XML_PREFIX
-
- for attribute in self.__slots__:
- value = getattr(self, attribute)
- if value:
- if isinstance(value, unicode_str):
- value = encoder.NarrowText(value, None)
- elif not isinstance(value, bytes_str):
- value = str(value)
- value = xml.sax.saxutils.escape(value)
- out = out + (' <%s>%s</%s>\n' % (attribute, value, attribute))
-
- out = out + SITEURL_XML_SUFFIX
- file.write(out)
- # end def WriteXML
-# end class URL
-
-
-class NewsURL(URL):
- """ NewsURL is a subclass of URL with News-Sitemap specific properties. """
- __slots__ = 'loc', 'lastmod', 'changefreq', 'priority', 'publication_date', \
- 'keywords', 'stock_tickers'
-
- def __init__(self):
- URL.__init__(self)
- self.publication_date = None # ISO8601 timestamp of publication date
- self.keywords = None # Text keywords
- self.stock_tickers = None # Text stock
- # end def __init__
-
- def Validate(self, base_url, allow_fragment):
- """ Verify the data in this News URL is well-formed, and override if not. """
- assert isinstance(base_url, bytes_str)
-
- if not URL.Validate(self, base_url, allow_fragment):
- return False
-
- if not URL.VerifyDate(self, self.publication_date, "publication_date"):
- self.publication_date = None
-
- return True
- # end def Validate
-
- def WriteXML(self, file):
- """ Dump non-empty contents to the output file, in XML format. """
- if not self.loc:
- return
- out = SITEURL_XML_PREFIX
-
- # printed_news_tag indicates if news-specific metatags are present
- printed_news_tag = False
- for attribute in self.__slots__:
- value = getattr(self, attribute)
- if value:
- if isinstance(value, unicode_str):
- value = encoder.NarrowText(value, None)
- elif not isinstance(value, bytes_str):
- value = str(value)
- value = xml.sax.saxutils.escape(value)
- if attribute in NEWS_SPECIFIC_TAGS:
- if not printed_news_tag:
- printed_news_tag = True
- out = out + NEWS_TAG_XML_PREFIX
- out = out + (' <news:%s>%s</news:%s>\n' %
- (attribute, value, attribute))
- else:
- out = out + (' <%s>%s</%s>\n' % (
- attribute, value, attribute))
-
- if printed_news_tag:
- out = out + NEWS_TAG_XML_SUFFIX
- out = out + SITEURL_XML_SUFFIX
- file.write(out)
- # end def WriteXML
-# end class NewsURL
-
-
-class Filter:
- """
- A filter on the stream of URLs we find. A filter is, in essence,
- a wildcard applied to the stream. You can think of this as an
- operator that returns a tri-state when given a URL:
-
- True -- this URL is to be included in the sitemap
- None -- this URL is undecided
- False -- this URL is to be dropped from the sitemap
- """
-
- def __init__(self, attributes):
- self._wildcard = None # Pattern for wildcard match
- self._regexp = None # Pattern for regexp match
- self._pass = False # "Drop" filter vs. "Pass" filter
-
- if not ValidateAttributes('FILTER', attributes,
- ('pattern', 'type', 'action')):
- return
-
- # Check error count on the way in
- num_errors = output.num_errors
-
- # Fetch the attributes
- pattern = attributes.get('pattern')
- type = attributes.get('type', 'wildcard')
- action = attributes.get('action', 'drop')
- if type:
- type = type.lower()
- if action:
- action = action.lower()
-
- # Verify the attributes
- if not pattern:
- output.Error('On a filter you must specify a "pattern" to match')
- elif (not type) or ((type != 'wildcard') and (type != 'regexp')):
- output.Error('On a filter you must specify either \'type="wildcard"\' '
- 'or \'type="regexp"\'')
- elif (action != 'pass') and (action != 'drop'):
- output.Error('If you specify a filter action, it must be either '
- '\'action="pass"\' or \'action="drop"\'')
-
- # Set the rule
- if action == 'drop':
- self._pass = False
- elif action == 'pass':
- self._pass = True
-
- if type == 'wildcard':
- self._wildcard = pattern
- elif type == 'regexp':
- try:
- self._regexp = re.compile(pattern)
- except re.error:
- output.Error('Bad regular expression: %s' % pattern)
-
- # Log the final results iff we didn't add any errors
- if num_errors == output.num_errors:
- output.Log('Filter: %s any URL that matches %s "%s"' %
- (action, type, pattern), 2)
- # end def __init__
-
- def Apply(self, url):
- """ Process the URL, as above. """
- if (not url) or (not url.loc):
- return None
-
- if self._wildcard:
- if fnmatch.fnmatchcase(url.loc, self._wildcard):
- return self._pass
- return None
-
- if self._regexp:
- if self._regexp.search(url.loc):
- return self._pass
- return None
-
- assert False # unreachable
- # end def Apply
-# end class Filter
-
-
-class InputURL:
- """
- Each Input class knows how to yield a set of URLs from a data source.
-
- This one handles a single URL, manually specified in the config file.
- """
-
- def __init__(self, attributes):
- self._url = None # The lonely URL
-
- if not ValidateAttributes('URL', attributes,
- ('href', 'lastmod', 'changefreq', 'priority')):
- return
-
- url = URL()
- for attr in attributes.keys():
- if attr == 'href':
- url.TrySetAttribute('loc', attributes[attr])
- else:
- url.TrySetAttribute(attr, attributes[attr])
-
- if not url.loc:
- output.Error('Url entries must have an href attribute.')
- return
-
- self._url = url
- output.Log('Input: From URL "%s"' % self._url.loc, 2)
- # end def __init__
-
- def ProduceURLs(self, consumer):
- """ Produces URLs from our data source, hands them in to the consumer. """
- if self._url:
- consumer(self._url, True)
- # end def ProduceURLs
-# end class InputURL
-
-
-class InputURLList:
- """
- Each Input class knows how to yield a set of URLs from a data source.
-
- This one handles a text file with a list of URLs
- """
-
- def __init__(self, attributes):
- self._path = None # The file path
- self._encoding = None # Encoding of that file
-
- if not ValidateAttributes('URLLIST', attributes, ('path', 'encoding')):
- return
-
- self._path = attributes.get('path')
- self._encoding = attributes.get('encoding', ENC_UTF8)
- if self._path:
- self._path = encoder.MaybeNarrowPath(self._path)
- if os.path.isfile(self._path):
- output.Log('Input: From URLLIST "%s"' % self._path, 2)
- else:
- output.Error('Can not locate file: %s' % self._path)
- self._path = None
- else:
- output.Error('Urllist entries must have a "path" attribute.')
- # end def __init__
-
- def ProduceURLs(self, consumer):
- """ Produces URLs from our data source, hands them in to the consumer. """
-
- # Open the file
- (frame, file) = OpenFileForRead(self._path, 'URLLIST')
- if not file:
- return
-
- # Iterate lines
- linenum = 0
- for line in file.readlines():
- linenum = linenum + 1
-
- # Strip comments and empty lines
- if self._encoding:
- line = encoder.WidenText(line, self._encoding)
- line = line.strip()
- if (not line) or line[0] == '#':
- continue
-
- # Split the line on space
- url = URL()
- cols = line.split(' ')
- for i in range(0, len(cols)):
- cols[i] = cols[i].strip()
- url.TrySetAttribute('loc', cols[0])
-
- # Extract attributes from the other columns
- for i in range(1, len(cols)):
- if cols[i]:
- try:
- (attr_name, attr_val) = cols[i].split('=', 1)
- url.TrySetAttribute(attr_name, attr_val)
- except ValueError:
- output.Warn('Line %d: Unable to parse attribute: %s' %
- (linenum, cols[i]))
-
- # Pass it on
- consumer(url, False)
-
- file.close()
- if frame:
- frame.close()
- # end def ProduceURLs
-# end class InputURLList
-
-
-class InputNewsURLList:
- """
- Each Input class knows how to yield a set of URLs from a data source.
-
- This one handles a text file with a list of News URLs and their metadata
- """
-
- def __init__(self, attributes):
- self._path = None # The file path
- self._encoding = None # Encoding of that file
- self._tag_order = [] # Order of URL metadata
-
- if not ValidateAttributes('URLLIST', attributes, ('path', 'encoding', 'tag_order')):
- return
-
- self._path = attributes.get('path')
- self._encoding = attributes.get('encoding', ENC_UTF8)
- self._tag_order = attributes.get('tag_order')
-
- if self._path:
- self._path = encoder.MaybeNarrowPath(self._path)
- if os.path.isfile(self._path):
- output.Log('Input: From URLLIST "%s"' % self._path, 2)
- else:
- output.Error('Can not locate file: %s' % self._path)
- self._path = None
- else:
- output.Error('Urllist entries must have a "path" attribute.')
-
- # parse tag_order into an array
- # tag_order_ascii created for more readable logging
- tag_order_ascii = []
- if self._tag_order:
- self._tag_order = self._tag_order.split(",")
- for i in range(0, len(self._tag_order)):
- element = self._tag_order[i].strip().lower()
- self._tag_order[i] = element
- tag_order_ascii.append(element.encode('ascii'))
- output.Log(
- 'Input: From URLLIST tag order is "%s"' % tag_order_ascii, 0)
- else:
- output.Error('News Urllist configuration file must contain tag_order '
- 'to define Sitemap metatags.')
-
- # verify all tag_order inputs are valid
- tag_order_dict = {}
- for tag in self._tag_order:
- tag_order_dict[tag] = ""
- if not ValidateAttributes('URLLIST', tag_order_dict,
- NEWS_SITEMAP_TAGS):
- return
-
- # loc tag must be present
- loc_tag = False
- for tag in self._tag_order:
- if tag == 'loc':
- loc_tag = True
- break
- if not loc_tag:
- output.Error('News Urllist tag_order in configuration file '
- 'does not contain "loc" value: %s' % tag_order_ascii)
- # end def __init__
-
- def ProduceURLs(self, consumer):
- """ Produces URLs from our data source, hands them in to the consumer. """
-
- # Open the file
- (frame, file) = OpenFileForRead(self._path, 'URLLIST')
- if not file:
- return
-
- # Iterate lines
- linenum = 0
- for line in file.readlines():
- linenum = linenum + 1
-
- # Strip comments and empty lines
- if self._encoding:
- line = encoder.WidenText(line, self._encoding)
- line = line.strip()
- if (not line) or line[0] == '#':
- continue
-
- # Split the line on tabs
- url = NewsURL()
- cols = line.split('\t')
- for i in range(0, len(cols)):
- cols[i] = cols[i].strip()
-
- for i in range(0, len(cols)):
- if cols[i]:
- attr_value = cols[i]
- if i < len(self._tag_order):
- attr_name = self._tag_order[i]
- try:
- url.TrySetAttribute(attr_name, attr_value)
- except ValueError:
- output.Warn('Line %d: Unable to parse attribute: %s' %
- (linenum, cols[i]))
-
- # Pass it on
- consumer(url, False)
-
- file.close()
- if frame:
- frame.close()
- # end def ProduceURLs
-# end class InputNewsURLList
-
-
-class InputDirectory:
- """
- Each Input class knows how to yield a set of URLs from a data source.
-
- This one handles a directory that acts as base for walking the filesystem.
- """
-
- def __init__(self, attributes, base_url):
- self._path = None # The directory
- self._url = None # The URL equivalent
- self._default_file = None
- self._remove_empty_directories = False
-
- if not ValidateAttributes('DIRECTORY', attributes, ('path', 'url',
- 'default_file', 'remove_empty_directories')):
- return
-
- # Prep the path -- it MUST end in a sep
- path = attributes.get('path')
- if not path:
- output.Error('Directory entries must have both "path" and "url" '
- 'attributes')
- return
- path = encoder.MaybeNarrowPath(path)
- if not path.endswith(os.sep):
- path = path + os.sep
- if not os.path.isdir(path):
- output.Error('Can not locate directory: %s' % path)
- return
-
- # Prep the URL -- it MUST end in a sep
- url = attributes.get('url')
- if not url:
- output.Error('Directory entries must have both "path" and "url" '
- 'attributes')
- return
- url = URL.Canonicalize(url)
- if not url.endswith('/'):
- url = url + '/'
- if not url.startswith(base_url):
- url = urljoin(base_url, url)
- if not url.startswith(base_url):
- output.Error('The directory URL "%s" is not relative to the '
- 'base_url: %s' % (url, base_url))
- return
-
- # Prep the default file -- it MUST be just a filename
- file = attributes.get('default_file')
- if file:
- file = encoder.MaybeNarrowPath(file)
- if os.sep in file:
- output.Error('The default_file "%s" can not include path information.'
- % file)
- file = None
-
- # Prep the remove_empty_directories -- default is false
- remove_empty_directories = attributes.get('remove_empty_directories')
- if remove_empty_directories:
- if (remove_empty_directories == '1') or \
- (remove_empty_directories.lower() == 'true'):
- remove_empty_directories = True
- elif (remove_empty_directories == '0') or \
- (remove_empty_directories.lower() == 'false'):
- remove_empty_directories = False
- # otherwise the user set a non-default value
- else:
- output.Error('Configuration file remove_empty_directories '
- 'value is not recognized. Value must be true or false.')
- return
- else:
- remove_empty_directories = False
-
- self._path = path
- self._url = url
- self._default_file = file
- self._remove_empty_directories = remove_empty_directories
-
- if file:
- output.Log('Input: From DIRECTORY "%s" (%s) with default file "%s"'
- % (path, url, file), 2)
- else:
- output.Log('Input: From DIRECTORY "%s" (%s) with no default file'
- % (path, url), 2)
- # end def __init__
-
- def ProduceURLs(self, consumer):
- """ Produces URLs from our data source, hands them in to the consumer. """
- if not self._path:
- return
-
- root_path = self._path
- root_URL = self._url
- root_file = self._default_file
- remove_empty_directories = self._remove_empty_directories
-
- def HasReadPermissions(path):
- """ Verifies a given path has read permissions. """
- stat_info = os.stat(path)
- mode = stat_info[stat.ST_MODE]
- if mode & stat.S_IREAD:
- return True
- else:
- return None
-
- def PerFile(dirpath, name):
- """
- Called once per file.
- Note that 'name' will occasionally be None -- for a directory itself
- """
- # Pull a timestamp
- url = URL()
- isdir = False
- try:
- if name:
- path = os.path.join(dirpath, name)
- else:
- path = dirpath
- isdir = os.path.isdir(path)
- time = None
- if isdir and root_file:
- file = os.path.join(path, root_file)
- try:
- time = os.stat(file)[stat.ST_MTIME]
- except OSError:
- pass
- if not time:
- time = os.stat(path)[stat.ST_MTIME]
- url.lastmod = TimestampISO8601(time)
- except OSError:
- pass
- except ValueError:
- pass
-
- # Build a URL
- middle = dirpath[len(root_path):]
- if os.sep != '/':
- middle = middle.replace(os.sep, '/')
- if middle:
- middle = middle + '/'
- if name:
- middle = middle + name
- if isdir:
- middle = middle + '/'
- url.TrySetAttribute(
- 'loc', root_URL + encoder.WidenText(middle, None))
-
- # Suppress default files. (All the way down here so we can log
- # it.)
- if name and (root_file == name):
- url.Log(prefix='IGNORED (default file)', level=2)
- return
-
- # Suppress directories when remove_empty_directories="true"
- try:
- if isdir:
- if HasReadPermissions(path):
- if remove_empty_directories == 'true' and \
- len(os.listdir(path)) == 0:
- output.Log(
- 'IGNORED empty directory %s' % str(path), level=1)
- return
- elif path == self._path:
- output.Error('IGNORED configuration file directory input %s due '
- 'to file permissions' % self._path)
- else:
- output.Log('IGNORED files within directory %s due to file '
- 'permissions' % str(path), level=0)
- except OSError:
- pass
- except ValueError:
- pass
-
- consumer(url, False)
- # end def PerFile
-
- def PerDirectory(ignore, dirpath, namelist):
- """
- Called once per directory with a list of all the contained files/dirs.
- """
- ignore = ignore # Avoid warnings of an unused parameter
-
- if not dirpath.startswith(root_path):
- output.Warn('Unable to decide what the root path is for directory: '
- '%s' % dirpath)
- return
-
- for name in namelist:
- PerFile(dirpath, name)
- # end def PerDirectory
-
- output.Log('Walking DIRECTORY "%s"' % self._path, 1)
- PerFile(self._path, None)
- os.path.walk(self._path, PerDirectory, None)
- # end def ProduceURLs
-# end class InputDirectory
-
-
-class InputAccessLog:
- """
- Each Input class knows how to yield a set of URLs from a data source.
-
- This one handles access logs. It's non-trivial in that we want to
- auto-detect log files in the Common Logfile Format (as used by Apache,
- for instance) and the Extended Log File Format (as used by IIS, for
- instance).
- """
-
- def __init__(self, attributes):
- self._path = None # The file path
- self._encoding = None # Encoding of that file
- self._is_elf = False # Extended Log File Format?
- self._is_clf = False # Common Logfile Format?
- self._elf_status = -1 # ELF field: '200'
- self._elf_method = -1 # ELF field: 'HEAD'
- self._elf_uri = -1 # ELF field: '/foo?bar=1'
- self._elf_urifrag1 = -1 # ELF field: '/foo'
- self._elf_urifrag2 = -1 # ELF field: 'bar=1'
-
- if not ValidateAttributes('ACCESSLOG', attributes, ('path', 'encoding')):
- return
-
- self._path = attributes.get('path')
- self._encoding = attributes.get('encoding', ENC_UTF8)
- if self._path:
- self._path = encoder.MaybeNarrowPath(self._path)
- if os.path.isfile(self._path):
- output.Log('Input: From ACCESSLOG "%s"' % self._path, 2)
- else:
- output.Error('Can not locate file: %s' % self._path)
- self._path = None
- else:
- output.Error('Accesslog entries must have a "path" attribute.')
- # end def __init__
-
- def RecognizeELFLine(self, line):
- """ Recognize the Fields directive that heads an ELF file """
- if not line.startswith('#Fields:'):
- return False
- fields = line.split(' ')
- del fields[0]
- for i in range(0, len(fields)):
- field = fields[i].strip()
- if field == 'sc-status':
- self._elf_status = i
- elif field == 'cs-method':
- self._elf_method = i
- elif field == 'cs-uri':
- self._elf_uri = i
- elif field == 'cs-uri-stem':
- self._elf_urifrag1 = i
- elif field == 'cs-uri-query':
- self._elf_urifrag2 = i
- output.Log('Recognized an Extended Log File Format file.', 2)
- return True
- # end def RecognizeELFLine
-
- def GetELFLine(self, line):
- """ Fetch the requested URL from an ELF line """
- fields = line.split(' ')
- count = len(fields)
-
- # Verify status was Ok
- if self._elf_status >= 0:
- if self._elf_status >= count:
- return None
- if not fields[self._elf_status].strip() == '200':
- return None
-
- # Verify method was HEAD or GET
- if self._elf_method >= 0:
- if self._elf_method >= count:
- return None
- if not fields[self._elf_method].strip() in ('HEAD', 'GET'):
- return None
-
- # Pull the full URL if we can
- if self._elf_uri >= 0:
- if self._elf_uri >= count:
- return None
- url = fields[self._elf_uri].strip()
- if url != '-':
- return url
-
- # Put together a fragmentary URL
- if self._elf_urifrag1 >= 0:
- if self._elf_urifrag1 >= count or self._elf_urifrag2 >= count:
- return None
- urlfrag1 = fields[self._elf_urifrag1].strip()
- urlfrag2 = None
- if self._elf_urifrag2 >= 0:
- urlfrag2 = fields[self._elf_urifrag2]
- if urlfrag1 and (urlfrag1 != '-'):
- if urlfrag2 and (urlfrag2 != '-'):
- urlfrag1 = urlfrag1 + '?' + urlfrag2
- return urlfrag1
-
- return None
- # end def GetELFLine
-
- def RecognizeCLFLine(self, line):
- """ Try to tokenize a logfile line according to CLF pattern and see if
- it works. """
- match = ACCESSLOG_CLF_PATTERN.match(line)
- recognize = match and (match.group(1) in ('HEAD', 'GET'))
- if recognize:
- output.Log('Recognized a Common Logfile Format file.', 2)
- return recognize
- # end def RecognizeCLFLine
-
- def GetCLFLine(self, line):
- """ Fetch the requested URL from a CLF line """
- match = ACCESSLOG_CLF_PATTERN.match(line)
- if match:
- request = match.group(1)
- if request in ('HEAD', 'GET'):
- return match.group(2)
- return None
- # end def GetCLFLine
-
- def ProduceURLs(self, consumer):
- """ Produces URLs from our data source, hands them in to the consumer. """
-
- # Open the file
- (frame, file) = OpenFileForRead(self._path, 'ACCESSLOG')
- if not file:
- return
-
- # Iterate lines
- for line in file.readlines():
- if self._encoding:
- line = encoder.WidenText(line, self._encoding)
- line = line.strip()
-
- # If we don't know the format yet, try them both
- if (not self._is_clf) and (not self._is_elf):
- self._is_elf = self.RecognizeELFLine(line)
- self._is_clf = self.RecognizeCLFLine(line)
-
- # Digest the line
- match = None
- if self._is_elf:
- match = self.GetELFLine(line)
- elif self._is_clf:
- match = self.GetCLFLine(line)
- if not match:
- continue
-
- # Pass it on
- url = URL()
- url.TrySetAttribute('loc', match)
- consumer(url, True)
-
- file.close()
- if frame:
- frame.close()
- # end def ProduceURLs
-# end class InputAccessLog
-
-
-class FilePathGenerator:
- """
- This class generates filenames in a series, upon request.
- You can request any iteration number at any time, you don't
- have to go in order.
-
- Example of iterations for '/path/foo.xml.gz':
- 0 --> /path/foo.xml.gz
- 1 --> /path/foo1.xml.gz
- 2 --> /path/foo2.xml.gz
- _index.xml --> /path/foo_index.xml
- """
-
- def __init__(self):
- self.is_gzip = False # Is this a GZIP file?
-
- self._path = None # '/path/'
- self._prefix = None # 'foo'
- self._suffix = None # '.xml.gz'
- # end def __init__
-
- def Preload(self, path):
- """ Splits up a path into forms ready for recombination. """
- path = encoder.MaybeNarrowPath(path)
-
- # Get down to a base name
- path = os.path.normpath(path)
- base = os.path.basename(path).lower()
- if not base:
- output.Error('Couldn\'t parse the file path: %s' % path)
- return False
- lenbase = len(base)
-
- # Recognize extension
- lensuffix = 0
- compare_suffix = ['.xml', '.xml.gz', '.gz']
- for suffix in compare_suffix:
- if base.endswith(suffix):
- lensuffix = len(suffix)
- break
- if not lensuffix:
- output.Error('The path "%s" doesn\'t end in a supported file '
- 'extension.' % path)
- return False
- self.is_gzip = suffix.endswith('.gz')
-
- # Split the original path
- lenpath = len(path)
- self._path = path[:lenpath - lenbase]
- self._prefix = path[lenpath - lenbase:lenpath - lensuffix]
- self._suffix = path[lenpath - lensuffix:]
-
- return True
- # end def Preload
-
- def GeneratePath(self, instance):
- """ Generates the iterations, as described above. """
- prefix = self._path + self._prefix
- if isinstance(instance, int):
- if instance:
- return '%s%d%s' % (prefix, instance, self._suffix)
- return prefix + self._suffix
- return prefix + instance
- # end def GeneratePath
-
- def GenerateURL(self, instance, root_url):
- """ Generates iterations, but as a URL instead of a path. """
- prefix = root_url + self._prefix
- retval = None
- if isinstance(instance, int):
- if instance:
- retval = '%s%d%s' % (prefix, instance, self._suffix)
- else:
- retval = prefix + self._suffix
- else:
- retval = prefix + instance
- return URL.Canonicalize(retval)
- # end def GenerateURL
-
- def GenerateWildURL(self, root_url):
- """ Generates a wildcard that should match all our iterations """
- prefix = URL.Canonicalize(root_url + self._prefix)
- temp = URL.Canonicalize(prefix + self._suffix)
- suffix = temp[len(prefix):]
- return prefix + '*' + suffix
- # end def GenerateURL
-# end class FilePathGenerator
-
-
-class PerURLStatistics:
- """ Keep track of some simple per-URL statistics, like file extension. """
-
- def __init__(self):
- self._extensions = {} # Count of extension instances
- # end def __init__
-
- def Consume(self, url):
- """ Log some stats for the URL. At the moment, that means extension. """
- if url and url.loc:
- (scheme, netloc, path, query, frag) = urlsplit(url.loc)
- if not path:
- return
-
- # Recognize directories
- if path.endswith('/'):
- if '/' in self._extensions:
- self._extensions['/'] = self._extensions['/'] + 1
- else:
- self._extensions['/'] = 1
- return
-
- # Strip to a filename
- i = path.rfind('/')
- if i >= 0:
- assert i < len(path)
- path = path[i:]
-
- # Find extension
- i = path.rfind('.')
- if i > 0:
- assert i < len(path)
- ext = path[i:].lower()
- if ext in self._extensions:
- self._extensions[ext] = self._extensions[ext] + 1
- else:
- self._extensions[ext] = 1
- else:
- if '(no extension)' in self._extensions:
- self._extensions['(no extension)'] = self._extensions[
- '(no extension)'] + 1
- else:
- self._extensions['(no extension)'] = 1
- # end def Consume
-
- def Log(self):
- """ Dump out stats to the output. """
- if len(self._extensions):
- output.Log('Count of file extensions on URLs:', 1)
- set = sorted(self._extensions.keys())
- for ext in set:
- output.Log(' %7d %s' % (self._extensions[ext], ext), 1)
- # end def Log
-
-
-class Sitemap(xml.sax.handler.ContentHandler):
- """
- This is the big workhorse class that processes your inputs and spits
- out sitemap files. It is built as a SAX handler for set up purposes.
- That is, it processes an XML stream to bring itself up.
- """
-
- def __init__(self, suppress_notify):
- xml.sax.handler.ContentHandler.__init__(self)
- self._filters = [] # Filter objects
- self._inputs = [] # Input objects
- self._urls = {} # Maps URLs to count of dups
- self._set = [] # Current set of URLs
- self._filegen = None # Path generator for output files
- self._wildurl1 = None # Sitemap URLs to filter out
- self._wildurl2 = None # Sitemap URLs to filter out
- self._sitemaps = 0 # Number of output files
- # We init _dup_max to 2 so the default priority is 0.5 instead of 1.0
- self._dup_max = 2 # Max number of duplicate URLs
- self._stat = PerURLStatistics() # Some simple stats
- self._in_site = False # SAX: are we in a Site node?
- self._in_Site_ever = False # SAX: were we ever in a Site?
-
- self._default_enc = None # Best encoding to try on URLs
- self._base_url = None # Prefix to all valid URLs
- self._store_into = None # Output filepath
- self._sitemap_type = None # Sitemap type (web, mobile or news)
- self._suppress = suppress_notify # Suppress notify of servers
- # end def __init__
-
- def ValidateBasicConfig(self):
- """ Verifies (and cleans up) the basic user-configurable options. """
- all_good = True
-
- if self._default_enc:
- encoder.SetUserEncoding(self._default_enc)
-
- # Canonicalize the base_url
- if all_good and not self._base_url:
- output.Error('A site needs a "base_url" attribute.')
- all_good = False
- if all_good and not URL.IsAbsolute(self._base_url):
- output.Error('The "base_url" must be absolute, not relative: %s' %
- self._base_url)
- all_good = False
- if all_good:
- self._base_url = URL.Canonicalize(self._base_url)
- if not self._base_url.endswith('/'):
- self._base_url = self._base_url + '/'
- output.Log('BaseURL is set to: %s' % self._base_url, 2)
-
- # Load store_into into a generator
- if all_good:
- if self._store_into:
- self._filegen = FilePathGenerator()
- if not self._filegen.Preload(self._store_into):
- all_good = False
- else:
- output.Error('A site needs a "store_into" attribute.')
- all_good = False
-
- # Ask the generator for patterns on what its output will look like
- if all_good:
- self._wildurl1 = self._filegen.GenerateWildURL(self._base_url)
- self._wildurl2 = self._filegen.GenerateURL(SITEINDEX_SUFFIX,
- self._base_url)
-
- # Unify various forms of False
- if all_good:
- if self._suppress:
- if (isinstance(self._suppress, bytes_str)) or (isinstance(self._suppress, unicode_str)):
- if (self._suppress == '0') or (self._suppress.lower() == 'false'):
- self._suppress = False
-
- # Clean up the sitemap_type
- if all_good:
- match = False
- # If sitemap_type is not specified, default to web sitemap
- if not self._sitemap_type:
- self._sitemap_type = 'web'
- else:
- self._sitemap_type = self._sitemap_type.lower()
- for pattern in SITEMAP_TYPES:
- if self._sitemap_type == pattern:
- match = True
- break
- if not match:
- output.Error('The "sitemap_type" value must be "web", "mobile" '
- 'or "news": %s' % self._sitemap_type)
- all_good = False
- output.Log('The Sitemap type is %s Sitemap.' %
- self._sitemap_type.upper(), 0)
-
- # Done
- if not all_good:
- output.Log('See "example_config.xml" for more information.', 0)
- return all_good
- # end def ValidateBasicConfig
-
- def Generate(self):
- """ Run over all the Inputs and ask them to Produce """
- # Run the inputs
- for input in self._inputs:
- input.ProduceURLs(self.ConsumeURL)
-
- # Do last flushes
- if len(self._set):
- self.FlushSet()
- if not self._sitemaps:
- output.Warn('No URLs were recorded, writing an empty sitemap.')
- self.FlushSet()
-
- # Write an index as needed
- if self._sitemaps > 1:
- self.WriteIndex()
-
- # Notify
- self.NotifySearch()
-
- # Dump stats
- self._stat.Log()
- # end def Generate
-
- def ConsumeURL(self, url, allow_fragment):
- """
- All per-URL processing comes together here, regardless of Input.
- Here we run filters, remove duplicates, spill to disk as needed, etc.
-
- """
- if not url:
- return
-
- # Validate
- if not url.Validate(self._base_url, allow_fragment):
- return
-
- # Run filters
- accept = None
- for filter in self._filters:
- accept = filter.Apply(url)
- if accept is not None:
- break
- if not (accept or (accept is None)):
- url.Log(prefix='FILTERED', level=2)
- return
-
- # Ignore our out output URLs
- if fnmatch.fnmatchcase(url.loc, self._wildurl1) or fnmatch.fnmatchcase(
- url.loc, self._wildurl2):
- url.Log(prefix='IGNORED (output file)', level=2)
- return
-
- # Note the sighting
- hash = url.MakeHash()
- if hash in self._urls:
- dup = self._urls[hash]
- if dup > 0:
- dup = dup + 1
- self._urls[hash] = dup
- if self._dup_max < dup:
- self._dup_max = dup
- url.Log(prefix='DUPLICATE')
- return
-
- # Acceptance -- add to set
- self._urls[hash] = 1
- self._set.append(url)
- self._stat.Consume(url)
- url.Log()
-
- # Flush the set if needed
- if len(self._set) >= MAXURLS_PER_SITEMAP:
- self.FlushSet()
- # end def ConsumeURL
-
- def FlushSet(self):
- """
- Flush the current set of URLs to the output. This is a little
- slow because we like to sort them all and normalize the priorities
- before dumping.
- """
-
- # Determine what Sitemap header to use (News or General)
- if self._sitemap_type == 'news':
- sitemap_header = NEWS_SITEMAP_HEADER
- else:
- sitemap_header = GENERAL_SITEMAP_HEADER
-
- # Sort and normalize
- output.Log('Sorting and normalizing collected URLs.', 1)
- self._set.sort()
- for url in self._set:
- hash = url.MakeHash()
- dup = self._urls[hash]
- if dup > 0:
- self._urls[hash] = -1
- if not url.priority:
- url.priority = '%.4f' % (float(dup) / float(self._dup_max))
-
- # Get the filename we're going to write to
- filename = self._filegen.GeneratePath(self._sitemaps)
- if not filename:
- output.Fatal('Unexpected: Couldn\'t generate output filename.')
- self._sitemaps = self._sitemaps + 1
- output.Log('Writing Sitemap file "%s" with %d URLs' %
- (filename, len(self._set)), 1)
-
- # Write to it
- frame = None
- file = None
-
- try:
- if self._filegen.is_gzip:
- basename = os.path.basename(filename)
- frame = open(filename, 'wb')
- file = gzip.GzipFile(
- fileobj=frame, filename=basename, mode='wt')
- else:
- file = open(filename, 'wt')
-
- file.write(sitemap_header)
- for url in self._set:
- url.WriteXML(file)
- file.write(SITEMAP_FOOTER)
-
- file.close()
- if frame:
- frame.close()
-
- frame = None
- file = None
- except IOError:
- output.Fatal('Couldn\'t write out to file: %s' % filename)
- os.chmod(filename, 0o0644)
-
- # Flush
- self._set = []
- # end def FlushSet
-
- def WriteIndex(self):
- """ Write the master index of all Sitemap files """
- # Make a filename
- filename = self._filegen.GeneratePath(SITEINDEX_SUFFIX)
- if not filename:
- output.Fatal(
- 'Unexpected: Couldn\'t generate output index filename.')
- output.Log('Writing index file "%s" with %d Sitemaps' %
- (filename, self._sitemaps), 1)
-
- # Determine what Sitemap index header to use (News or General)
- if self._sitemap_type == 'news':
- sitemap_index_header = NEWS_SITEMAP_HEADER
- else:
- sitemap_index_header = GENERAL_SITEMAP_HEADER
-
- # Make a lastmod time
- lastmod = TimestampISO8601(time.time())
-
- # Write to it
- try:
- fd = open(filename, 'wt')
- fd.write(sitemap_index_header)
-
- for mapnumber in range(0, self._sitemaps):
- # Write the entry
- mapurl = self._filegen.GenerateURL(mapnumber, self._base_url)
- mapattributes = {'loc': mapurl, 'lastmod': lastmod}
- fd.write(SITEINDEX_ENTRY % mapattributes)
-
- fd.write(SITEINDEX_FOOTER)
-
- fd.close()
- fd = None
- except IOError:
- output.Fatal('Couldn\'t write out to file: %s' % filename)
- os.chmod(filename, 0o0644)
- # end def WriteIndex
-
- def NotifySearch(self):
- """ Send notification of the new Sitemap(s) to the search engines. """
- if self._suppress:
- output.Log('Search engine notification is suppressed.', 1)
- return
-
- output.Log('Notifying search engines.', 1)
-
- # Override the urllib's opener class with one that doesn't ignore 404s
- class ExceptionURLopener(FancyURLopener):
- def http_error_default(self, url, fp, errcode, errmsg, headers):
- output.Log('HTTP error %d: %s' % (errcode, errmsg), 2)
- raise IOError
- # end def http_error_default
- # end class ExceptionURLOpener
- if sys.version_info[0] == 3:
- old_opener = urllib.request._urlopener
- urllib.request._urlopener = ExceptionURLopener()
- else:
- old_opener = urllib._urlopener
- urllib._urlopener = ExceptionURLopener()
-
- # Build the URL we want to send in
- if self._sitemaps > 1:
- url = self._filegen.GenerateURL(SITEINDEX_SUFFIX, self._base_url)
- else:
- url = self._filegen.GenerateURL(0, self._base_url)
-
- # Test if we can hit it ourselves
- try:
- u = urlopen(url)
- u.close()
- except IOError:
- output.Error('When attempting to access our generated Sitemap at the '
- 'following URL:\n %s\n we failed to read it. Please '
- 'verify the store_into path you specified in\n'
- ' your configuration file is web-accessable. Consult '
- 'the FAQ for more\n information.' % url)
- output.Warn('Proceeding to notify with an unverifyable URL.')
-
- # Cycle through notifications
- # To understand this, see the comment near the NOTIFICATION_SITES
- # comment
- for ping in NOTIFICATION_SITES:
- query_map = ping[3]
- query_attr = ping[5]
- query_map[query_attr] = url
- query = urllib.urlencode(query_map)
- notify = urlunsplit((ping[0], ping[1], ping[2], query, ping[4]))
-
- # Send the notification
- output.Log('Notifying: %s' % ping[1], 0)
- output.Log('Notification URL: %s' % notify, 2)
- try:
- u = urlopen(notify)
- u.read()
- u.close()
- except IOError:
- output.Warn('Cannot contact: %s' % ping[1])
-
- if old_opener:
- if sys.version_info[0] == 3:
- urllib.request._urlopener = old_opener
- else:
- urllib._urlopener = old_opener
- # end def NotifySearch
-
- def startElement(self, tag, attributes):
- """ SAX processing, called per node in the config stream. """
- if tag == 'site':
- if self._in_site:
- output.Error('Can not nest Site entries in the configuration.')
- else:
- self._in_site = True
-
- if not ValidateAttributes('SITE', attributes,
- ('verbose', 'default_encoding', 'base_url', 'store_into',
- 'suppress_search_engine_notify', 'sitemap_type')):
- return
-
- verbose = attributes.get('verbose', 0)
- if verbose:
- output.SetVerbose(verbose)
-
- self._default_enc = attributes.get('default_encoding')
- self._base_url = attributes.get('base_url')
- self._store_into = attributes.get('store_into')
- self._sitemap_type = attributes.get('sitemap_type')
- if not self._suppress:
- self._suppress = attributes.get(
- 'suppress_search_engine_notify',
- False)
- self.ValidateBasicConfig()
- elif tag == 'filter':
- self._filters.append(Filter(attributes))
-
- elif tag == 'url':
- print(type(attributes))
- self._inputs.append(InputURL(attributes))
-
- elif tag == 'urllist':
- for attributeset in ExpandPathAttribute(attributes, 'path'):
- if self._sitemap_type == 'news':
- self._inputs.append(InputNewsURLList(attributeset))
- else:
- self._inputs.append(InputURLList(attributeset))
-
- elif tag == 'directory':
- self._inputs.append(InputDirectory(attributes, self._base_url))
-
- elif tag == 'accesslog':
- for attributeset in ExpandPathAttribute(attributes, 'path'):
- self._inputs.append(InputAccessLog(attributeset))
- else:
- output.Error('Unrecognized tag in the configuration: %s' % tag)
- # end def startElement
-
- def endElement(self, tag):
- """ SAX processing, called per node in the config stream. """
- if tag == 'site':
- assert self._in_site
- self._in_site = False
- self._in_site_ever = True
- # end def endElement
-
- def endDocument(self):
- """ End of SAX, verify we can proceed. """
- if not self._in_site_ever:
- output.Error('The configuration must specify a "site" element.')
- else:
- if not self._inputs:
- output.Warn('There were no inputs to generate a sitemap from.')
- # end def endDocument
-# end class Sitemap
-
-
-def ValidateAttributes(tag, attributes, goodattributes):
- """ Makes sure 'attributes' does not contain any attribute not
- listed in 'goodattributes' """
- all_good = True
- for attr in attributes.keys():
- if not attr in goodattributes:
- output.Error('Unknown %s attribute: %s' % (tag, attr))
- all_good = False
- return all_good
-# end def ValidateAttributes
-
-
-def ExpandPathAttribute(src, attrib):
- """ Given a dictionary of attributes, return a list of dictionaries
- with all the same attributes except for the one named attrib.
- That one, we treat as a file path and expand into all its possible
- variations. """
- # Do the path expansion. On any error, just return the source dictionary.
- path = src.get(attrib)
- if not path:
- return [src]
- path = encoder.MaybeNarrowPath(path)
- pathlist = glob.glob(path)
- if not pathlist:
- return [src]
-
- # If this isn't actually a dictionary, make it one
- if not isinstance(src, dict):
- tmp = {}
- for key in src.keys():
- tmp[key] = src[key]
- src = tmp
- # Create N new dictionaries
- retval = []
- for path in pathlist:
- dst = src.copy()
- dst[attrib] = path
- retval.append(dst)
-
- return retval
-# end def ExpandPathAttribute
-
-
-def OpenFileForRead(path, logtext):
- """ Opens a text file, be it GZip or plain """
-
- frame = None
- file = None
-
- if not path:
- return (frame, file)
-
- try:
- if path.endswith('.gz'):
- frame = open(path, 'rb')
- file = gzip.GzipFile(fileobj=frame, mode='rt')
- else:
- file = open(path, 'rt')
-
- if logtext:
- output.Log('Opened %s file: %s' % (logtext, path), 1)
- else:
- output.Log('Opened file: %s' % path, 1)
- except IOError:
- output.Error('Can not open file: %s' % path)
-
- return (frame, file)
-# end def OpenFileForRead
-
-
-def TimestampISO8601(t):
- """Seconds since epoch (1970-01-01) --> ISO 8601 time string."""
- return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(t))
-# end def TimestampISO8601
-
-
-def CreateSitemapFromFile(configpath, suppress_notify):
- """ Sets up a new Sitemap object from the specified configuration file. """
-
- # Remember error count on the way in
- num_errors = output.num_errors
-
- # Rev up SAX to parse the config
- sitemap = Sitemap(suppress_notify)
- try:
- output.Log('Reading configuration file: %s' % configpath, 0)
- xml.sax.parse(configpath, sitemap)
- except IOError:
- output.Error('Cannot read configuration file: %s' % configpath)
- except xml.sax._exceptions.SAXParseException as e:
- output.Error('XML error in the config file (line %d, column %d): %s' %
- (e._linenum, e._colnum, e.getMessage()))
- except xml.sax._exceptions.SAXReaderNotAvailable:
- output.Error('Some installs of Python 2.2 did not include complete support'
- ' for XML.\n Please try upgrading your version of Python'
- ' and re-running the script.')
-
- # If we added any errors, return no sitemap
- if num_errors == output.num_errors:
- return sitemap
- return None
-# end def CreateSitemapFromFile
-
-
-def ProcessCommandFlags(args):
- """
- Parse command line flags per specified usage, pick off key, value pairs
- All flags of type "--key=value" will be processed as __flags[key] = value,
- "--option" will be processed as __flags[option] = option
- """
-
- flags = {}
- rkeyval = '--(?P<key>\S*)[=](?P<value>\S*)' # --key=val
- roption = '--(?P<option>\S*)' # --key
- r = '(' + rkeyval + ')|(' + roption + ')'
- rc = re.compile(r)
- for a in args:
- try:
- rcg = rc.search(a).groupdict()
- if 'key' in rcg:
- flags[rcg['key']] = rcg['value']
- if 'option' in rcg:
- flags[rcg['option']] = rcg['option']
- except AttributeError:
- return None
- return flags
-# end def ProcessCommandFlags
-
-
-#
-# __main__
-#
-
-if __name__ == '__main__':
- flags = ProcessCommandFlags(sys.argv[1:])
- if not flags or not 'config' in flags or 'help' in flags:
- output.Log(__usage__, 0)
- else:
- suppress_notify = 'testing' in flags
- sitemap = CreateSitemapFromFile(flags['config'], suppress_notify)
- if not sitemap:
- output.Log('Configuration file errors -- exiting.', 0)
- else:
- sitemap.Generate()
- output.Log('Number of errors: %d' % output.num_errors, 1)
- output.Log('Number of warnings: %d' % output.num_warns, 1)
diff --git a/nikola/post.py b/nikola/post.py
index 5060583..ac97c73 100644
--- a/nikola/post.py
+++ b/nikola/post.py
@@ -26,15 +26,14 @@
from __future__ import unicode_literals, print_function
import codecs
+from collections import defaultdict
import os
import re
-import sys
import string
-import unidecode
import lxml.html
-from .utils import to_datetime, slugify
+from .utils import to_datetime, slugify, bytes_str, Functionary, LocaleBorg
__all__ = ['Post']
@@ -48,7 +47,8 @@ class Post(object):
def __init__(
self, source_path, cache_folder, destination, use_in_feeds,
translations, default_lang, base_url, messages, template_name,
- file_metadata_regexp=None, tzinfo=None
+ file_metadata_regexp=None, strip_index_html=False, tzinfo=None,
+ skip_untranslated=False,
):
"""Initialize post.
@@ -56,14 +56,13 @@ class Post(object):
the meta file, as well as any translations available, and
the .html fragment file path.
"""
- self.translated_to = set([default_lang])
- self.tags = ''
- self.date = None
- self.prev_post = None
- self.next_post = None
+ self.translated_to = set([])
+ self._prev_post = None
+ self._next_post = None
self.base_url = base_url
self.is_draft = False
self.is_mathjax = False
+ self.strip_index_html = strip_index_html
self.source_path = source_path # posts/blah.txt
self.post_name = os.path.splitext(source_path)[0] # posts/blah
# cache/posts/blah.html
@@ -73,72 +72,170 @@ class Post(object):
self.translations = translations
self.default_lang = default_lang
self.messages = messages
- self.template_name = template_name
- self.meta = get_meta(self, file_metadata_regexp)
+ self.skip_untranslated = skip_untranslated
+ self._template_name = template_name
- default_title = self.meta.get('title', '')
- default_pagename = self.meta.get('slug', '')
- default_description = self.meta.get('description', '')
+ default_metadata = get_meta(self, file_metadata_regexp)
- for k, v in self.meta.items():
- if k not in ['title', 'slug', 'description']:
- if sys.version_info[0] == 2:
- setattr(self, unidecode.unidecode(unicode(k)), v) # NOQA
- else:
- setattr(self, k, v)
+ self.meta = Functionary(lambda: None, self.default_lang)
+ self.meta[default_lang] = default_metadata
- if not default_title or not default_pagename or not self.date:
+ # Load internationalized metadata
+ for lang in translations:
+ if lang != default_lang:
+ if os.path.isfile(self.source_path + "." + lang):
+ self.translated_to.add(lang)
+
+ meta = defaultdict(lambda: '')
+ meta.update(default_metadata)
+ meta.update(get_meta(self, file_metadata_regexp, lang))
+ self.meta[lang] = meta
+ elif os.path.isfile(self.source_path):
+ self.translated_to.add(default_lang)
+
+ if not self.is_translation_available(default_lang):
+ # Special case! (Issue #373)
+ # Fill default_metadata with stuff from the other languages
+ for lang in sorted(self.translated_to):
+ default_metadata.update(self.meta[lang])
+
+ 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_title, default_pagename,
- self.date, source_path))
+ "{3}]".format(default_metadata.get('title', None),
+ default_metadata.get('slug', None),
+ default_metadata.get('date', None),
+ source_path))
# If timezone is set, build localized datetime.
- self.date = to_datetime(self.date, tzinfo)
- self.tags = [x.strip() for x in self.tags.split(',')]
- self.tags = [_f for _f in self.tags if _f]
+ self.date = to_datetime(self.meta[default_lang]['date'], tzinfo)
+
+ is_draft = False
+ is_retired = False
+ self._tags = {}
+ for lang in self.translated_to:
+ self._tags[lang] = [x.strip() for x in self.meta[lang]['tags'].split(',')]
+ self._tags[lang] = [t for t in self._tags[lang] if t]
+ if 'draft' in self._tags[lang]:
+ is_draft = True
+ self._tags[lang].remove('draft')
+ if 'retired' in self._tags[lang]:
+ is_retired = True
+ self._tags[lang].remove('retired')
# While draft comes from the tags, it's not really a tag
- self.use_in_feeds = use_in_feeds and "draft" not in self.tags
- self.is_draft = 'draft' in self.tags
- self.tags = [t for t in self.tags if t != 'draft']
+ self.is_draft = is_draft
+ self.use_in_feeds = use_in_feeds and not is_draft and not is_retired
# If mathjax is a tag, then enable mathjax rendering support
self.is_mathjax = 'mathjax' in self.tags
+ @property
+ def alltags(self):
+ """This is ALL the tags for this post."""
+ tags = []
+ for l in self._tags:
+ tags.extend(self._tags[l])
+ return list(set(tags))
+
+ @property
+ def tags(self):
+ lang = self.current_lang()
+ if lang in self._tags:
+ return self._tags[lang]
+ elif self.default_lang in self._tags:
+ return self._tags[self.default_lang]
+ else:
+ return []
+
+ @property
+ def prev_post(self):
+ lang = self.current_lang()
+ rv = self._prev_post
+ while self.skip_untranslated:
+ if rv is None:
+ break
+ if rv.is_translation_available(lang):
+ break
+ rv = rv._prev_post
+ return rv
+
+ @prev_post.setter # NOQA
+ def prev_post(self, v):
+ self._prev_post = v
+
+ @property
+ def next_post(self):
+ lang = self.current_lang()
+ rv = self._next_post
+ while self.skip_untranslated:
+ if rv is None:
+ break
+ if rv.is_translation_available(lang):
+ break
+ rv = rv._next_post
+ return rv
+
+ @next_post.setter # NOQA
+ def next_post(self, v):
+ self._next_post = v
+
+ @property
+ def template_name(self):
+ return self.meta('template') or self._template_name
+
+ def _add_old_metadata(self):
+ # Compatibility for themes up to Nikola 5.4.1
+ # TODO: remove before Nikola 6
self.pagenames = {}
self.titles = {}
- self.descriptions = {}
-
- # Load internationalized metadata
- for lang in translations:
- if lang == default_lang:
- self.titles[lang] = default_title
- self.pagenames[lang] = default_pagename
- self.descriptions[lang] = default_description
- else:
- if os.path.isfile(self.source_path + "." + lang):
- self.translated_to.add(lang)
-
- meta = self.meta.copy()
- meta.update(get_meta(self, file_metadata_regexp, lang))
-
- # FIXME this only gets three pieces of metadata from the i18n files
- self.titles[lang] = meta.get('title', default_title)
- self.pagenames[lang] = meta.get('slug', default_pagename)
- self.descriptions[lang] = meta.get('description', default_description)
-
- def title(self, lang):
- """Return localized title."""
- return self.titles[lang]
+ for lang in self.translations:
+ self.pagenames[lang] = self.meta[lang]['slug']
+ self.titles[lang] = self.meta[lang]['title']
+
+ def formatted_date(self, date_format):
+ """Return the formatted date, as unicode."""
+ fmt_date = self.date.strftime(date_format)
+ # Issue #383, this changes from py2 to py3
+ if isinstance(fmt_date, bytes_str):
+ fmt_date = fmt_date.decode('utf8')
+ return fmt_date
+
+ def current_lang(self):
+ """Return the currently set locale, if it's one of the
+ available translations, or default_lang."""
+ lang = LocaleBorg().current_lang
+ if lang:
+ if lang in self.translations:
+ return lang
+ lang = lang.split('_')[0]
+ if lang in self.translations:
+ return lang
+ # whatever
+ return self.default_lang
+
+ def title(self, lang=None):
+ """Return localized title.
+
+ If lang is not specified, it will use the currently set locale,
+ because templates set it.
+ """
+ if lang is None:
+ lang = self.current_lang()
+ return self.meta[lang]['title']
- def description(self, lang):
+ def description(self, lang=None):
"""Return localized description."""
- return self.descriptions[lang]
+ if lang is None:
+ lang = self.current_lang()
+ return self.meta[lang]['description']
def deps(self, lang):
"""Return a list of dependencies to build this post's page."""
- deps = [self.base_path]
+ deps = []
+ if self.default_lang in self.translated_to:
+ deps.append(self.base_path)
if lang != self.default_lang:
deps += [self.base_path + "." + lang]
deps += self.fragment_deps(lang)
@@ -146,9 +243,15 @@ class Post(object):
def fragment_deps(self, lang):
"""Return a list of dependencies to build this post's fragment."""
- deps = [self.source_path]
+ deps = []
+ if self.default_lang in self.translated_to:
+ deps.append(self.source_path)
if os.path.isfile(self.metadata_path):
deps.append(self.metadata_path)
+ dep_path = self.base_path + '.dep'
+ if os.path.isfile(dep_path):
+ with codecs.open(dep_path, 'rb+', 'utf8') as depf:
+ deps.extend([l.strip() for l in depf.readlines()])
if lang != self.default_lang:
lang_deps = list(filter(os.path.exists, [x + "." + lang for x in
deps]))
@@ -159,67 +262,86 @@ class Post(object):
"""Return true if the translation actually exists."""
return lang in self.translated_to
+ def translated_source_path(self, lang):
+ """Return path to the translation's source file."""
+ if lang in self.translated_to:
+ if lang == self.default_lang:
+ return self.source_path
+ else:
+ return '.'.join((self.source_path, lang))
+ elif lang != self.default_lang:
+ return self.source_path
+ else:
+ return '.'.join((self.source_path, sorted(self.translated_to)[0]))
+
def _translated_file_path(self, lang):
"""Return path to the translation's file, or to the original."""
- file_name = self.base_path
- if lang != self.default_lang:
- file_name_lang = '.'.join((file_name, lang))
- if os.path.exists(file_name_lang):
- file_name = file_name_lang
- return file_name
+ if lang in self.translated_to:
+ if lang == self.default_lang:
+ return self.base_path
+ else:
+ return '.'.join((self.base_path, lang))
+ elif lang != self.default_lang:
+ return self.base_path
+ else:
+ return '.'.join((self.base_path, sorted(self.translated_to)[0]))
- def text(self, lang, teaser_only=False, strip_html=False):
- """Read the post file for that language and return its contents"""
- file_name = self._translated_file_path(lang)
+ def text(self, lang=None, teaser_only=False, strip_html=False):
+ """Read the post file for that language and return its contents."""
+ if lang is None:
+ lang = self.current_lang()
+ file_name = self._translated_file_path(lang)
with codecs.open(file_name, "r", "utf8") as post_file:
- data = post_file.read()
-
- if data:
- data = lxml.html.make_links_absolute(data, self.permalink(lang=lang))
- if data and teaser_only:
- e = lxml.html.fromstring(data)
- teaser = []
- teaser_str = self.messages[lang]["Read more"] + '...'
- flag = False
- for elem in e:
- elem_string = lxml.html.tostring(elem).decode('utf8')
- match = TEASER_REGEXP.match(elem_string)
- if match:
- flag = True
- if match.group(2):
- teaser_str = match.group(2)
- break
- teaser.append(elem_string)
- if flag:
- teaser.append('<p><a href="{0}">{1}</a></p>'.format(
- self.permalink(lang), teaser_str))
- data = ''.join(teaser)
+ data = post_file.read().strip()
+
+ try:
+ document = lxml.html.document_fromstring(data)
+ except lxml.etree.ParserError as e:
+ # if we don't catch this, it breaks later (Issue #374)
+ if str(e) == "Document is empty":
+ return ""
+ # let other errors raise
+ raise(e)
+ document.make_links_absolute(self.permalink(lang=lang))
+ data = lxml.html.tostring(document, encoding='unicode')
+ if teaser_only:
+ teaser = TEASER_REGEXP.split(data)[0]
+ if teaser != data:
+ teaser_str = self.messages[lang]["Read more"] + '...'
+ teaser += '<p><a href="{0}">{1}</a></p>'.format(
+ self.permalink(lang), teaser_str)
+ # This closes all open tags and sanitizes the broken HTML
+ document = lxml.html.fromstring(teaser)
+ data = lxml.html.tostring(document, encoding='unicode')
if data and strip_html:
content = lxml.html.fromstring(data)
data = content.text_content().strip() # No whitespace wanted.
-
return data
def destination_path(self, lang, extension='.html'):
path = os.path.join(self.translations[lang],
- self.folder, self.pagenames[lang] + extension)
+ self.folder, self.meta[lang]['slug'] + extension)
return path
def permalink(self, lang=None, absolute=False, extension='.html'):
if lang is None:
- lang = self.default_lang
- pieces = list(os.path.split(self.translations[lang]))
- pieces += list(os.path.split(self.folder))
- pieces += [self.pagenames[lang] + extension]
+ lang = self.current_lang()
+
+ pieces = self.translations[lang].split(os.sep)
+ pieces += self.folder.split(os.sep)
+ pieces += [self.meta[lang]['slug'] + extension]
pieces = [_f for _f in pieces if _f and _f != '.']
if absolute:
pieces = [self.base_url] + pieces
else:
pieces = [""] + pieces
link = "/".join(pieces)
- return link
+ if self.strip_index_html and link.endswith('/index.html'):
+ return link[:-10]
+ else:
+ return link
def source_ext(self):
return os.path.splitext(self.source_path)[1]
@@ -359,7 +481,7 @@ def get_meta(post, file_metadata_regexp=None, lang=None):
If any metadata is then found inside the file the metadata from the
file will override previous findings.
"""
- meta = {}
+ meta = defaultdict(lambda: '')
meta.update(get_metadata_from_meta_file(post.metadata_path, lang))
diff --git a/nikola/rc4.py b/nikola/rc4.py
new file mode 100644
index 0000000..6e63474
--- /dev/null
+++ b/nikola/rc4.py
@@ -0,0 +1,76 @@
+"""
+ Copyright (C) 2012 Bo Zhu http://about.bozhu.me
+
+ 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.
+"""
+
+import base64
+import sys
+
+
+def KSA(key):
+ keylength = len(key)
+
+ S = list(range(256))
+
+ j = 0
+ for i in range(256):
+ j = (j + S[i] + key[i % keylength]) % 256
+ S[i], S[j] = S[j], S[i] # swap
+
+ return S
+
+
+def PRGA(S):
+ i = 0
+ j = 0
+ while True:
+ i = (i + 1) % 256
+ j = (j + S[i]) % 256
+ S[i], S[j] = S[j], S[i] # swap
+
+ K = S[(S[i] + S[j]) % 256]
+ yield K
+
+
+def RC4(key):
+ S = KSA(key)
+ return PRGA(S)
+
+
+def rc4(key, string):
+ """Encrypt things.
+ >>> print(rc4("Key", "Plaintext"))
+ u/MW6NlArwrT
+ """
+
+ string.encode('utf8')
+ key.encode('utf8')
+
+ def convert_key(s):
+ return [ord(c) for c in s]
+ key = convert_key(key)
+ keystream = RC4(key)
+ r = b''
+ for c in string:
+ if sys.version_info[0] == 3:
+ r += bytes([ord(c) ^ next(keystream)])
+ else:
+ r += chr(ord(c) ^ next(keystream))
+ return base64.b64encode(r).replace(b'\n', b'').decode('ascii')
diff --git a/nikola/utils.py b/nikola/utils.py
index 5589d68..423ded8 100644
--- a/nikola/utils.py
+++ b/nikola/utils.py
@@ -25,10 +25,11 @@
"""Utility functions."""
-from __future__ import print_function
+from __future__ import print_function, unicode_literals
from collections import defaultdict, Callable
import datetime
import hashlib
+import locale
import os
import re
import codecs
@@ -49,9 +50,12 @@ if sys.version_info[0] == 3:
bytes_str = bytes
unicode_str = str
unichr = chr
+ from imp import reload as _reload
else:
bytes_str = str
unicode_str = unicode # NOQA
+ _reload = reload # NOQA
+ unichr = unichr
from doit import tools
from unidecode import unidecode
@@ -60,7 +64,38 @@ import PyRSS2Gen as rss
__all__ = ['get_theme_path', 'get_theme_chain', 'load_messages', 'copy_tree',
'generic_rss_renderer', 'copy_file', 'slugify', 'unslugify',
- 'to_datetime', 'apply_filters', 'config_changed', 'get_crumbs']
+ 'to_datetime', 'apply_filters', 'config_changed', 'get_crumbs',
+ 'get_asset_path', '_reload', 'unicode_str', 'bytes_str',
+ 'unichr', 'Functionary', 'LocaleBorg']
+
+
+class Functionary(defaultdict):
+
+ """Class that looks like a function, but is a defaultdict."""
+
+ def __init__(self, default, default_lang):
+ super(Functionary, self).__init__(default)
+ self.default_lang = default_lang
+
+ def current_lang(self):
+ """Guess the current language from locale or default."""
+ lang = locale.getlocale()[0]
+ if lang:
+ if lang in self.keys():
+ return lang
+ lang = lang.split('_')[0]
+ if lang in self.keys():
+ return lang
+ # whatever
+ return self.default_lang
+
+ def __call__(self, key, lang=None):
+ """When called as a function, take an optional lang
+ and return self[lang][key]."""
+
+ if lang is None:
+ lang = self.current_lang()
+ return self[lang][key]
class CustomEncoder(json.JSONEncoder):
@@ -140,13 +175,13 @@ def get_theme_chain(theme):
return themes
-def load_messages(themes, translations):
+def load_messages(themes, translations, default_lang):
""" Load theme's messages into context.
All the messages from parent themes are loaded,
and "younger" themes have priority.
"""
- messages = defaultdict(dict)
+ messages = Functionary(dict, default_lang)
warned = []
oldpath = sys.path[:]
for theme_name in themes[::-1]:
@@ -285,16 +320,18 @@ def slugify(value):
From Django's "django/template/defaultfilters.py".
- >>> slugify('\xe1\xe9\xed.\xf3\xfa')
- 'aeiou'
+ >>> print(slugify('\xe1\xe9\xed.\xf3\xfa'))
+ aeiou
- >>> slugify('foo/bar')
- 'foobar'
+ >>> print(slugify('foo/bar'))
+ foobar
- >>> slugify('foo bar')
- 'foo-bar'
+ >>> print(slugify('foo bar'))
+ foo-bar
"""
+ if not isinstance(value, unicode_str):
+ raise ValueError("Not a unicode object: {0}".format(value))
value = unidecode(value)
# WARNING: this may not be python2/3 equivalent
# value = unicode(_slugify_strip_re.sub('', value).strip().lower())
@@ -367,6 +404,15 @@ def to_datetime(value, tzinfo=None):
return tzinfo.localize(dt)
except ValueError:
pass
+ # So, let's try dateutil
+ try:
+ from dateutil import parser
+ dt = parser.parse(value)
+ if tzinfo is None:
+ return dt
+ return tzinfo.localize(dt)
+ except ImportError:
+ raise ValueError('Unrecognized date/time: {0!r}, try installing dateutil...'.format(value))
raise ValueError('Unrecognized date/time: {0!r}'.format(value))
@@ -383,7 +429,7 @@ def apply_filters(task, filters):
if isinstance(key, (tuple, list)):
if ext in key:
return value
- elif isinstance(key, (str, bytes)):
+ elif isinstance(key, (bytes_str, unicode_str)):
if ext == key:
return value
else:
@@ -408,14 +454,29 @@ def apply_filters(task, filters):
def get_crumbs(path, is_file=False):
"""Create proper links for a crumb bar.
- >>> get_crumbs('galleries')
- [['#', 'galleries']]
-
- >>> get_crumbs(os.path.join('galleries','demo'))
- [['..', 'galleries'], ['#', 'demo']]
-
- >>> get_crumbs(os.path.join('listings','foo','bar'), is_file=True)
- [['..', 'listings'], ['.', 'foo'], ['#', 'bar']]
+ >>> crumbs = get_crumbs('galleries')
+ >>> len(crumbs)
+ 1
+ >>> print('|'.join(crumbs[0]))
+ #|galleries
+
+ >>> crumbs = get_crumbs(os.path.join('galleries','demo'))
+ >>> len(crumbs)
+ 2
+ >>> print('|'.join(crumbs[0]))
+ ..|galleries
+ >>> print('|'.join(crumbs[1]))
+ #|demo
+
+ >>> crumbs = get_crumbs(os.path.join('listings','foo','bar'), is_file=True)
+ >>> len(crumbs)
+ 3
+ >>> print('|'.join(crumbs[0]))
+ ..|listings
+ >>> print('|'.join(crumbs[1]))
+ .|foo
+ >>> print('|'.join(crumbs[2]))
+ #|bar
"""
crumbs = path.split(os.sep)
@@ -431,3 +492,53 @@ def get_crumbs(path, is_file=False):
_path = '/'.join(['..'] * i) or '#'
_crumbs.append([_path, crumb])
return list(reversed(_crumbs))
+
+
+def get_asset_path(path, themes, files_folders={'files': ''}):
+ """Checks which theme provides the path with the given asset,
+ and returns the "real" path to the asset, relative to the
+ current directory.
+
+ If the asset is not provided by a theme, then it will be checked for
+ in the FILES_FOLDERS
+
+ >>> print(get_asset_path('assets/css/rst.css', ['site', 'default']))
+ nikola/data/themes/default/assets/css/rst.css
+
+ >>> print(get_asset_path('assets/css/theme.css', ['site', 'default']))
+ nikola/data/themes/site/assets/css/theme.css
+
+ >>> print(get_asset_path('nikola.py', ['site', 'default'], {'nikola': ''}))
+ nikola/nikola.py
+
+ >>> print(get_asset_path('nikola/nikola.py', ['site', 'default'],
+ ... {'nikola':'nikola'}))
+ nikola/nikola.py
+
+ """
+ for theme_name in themes:
+ candidate = os.path.join(
+ get_theme_path(theme_name),
+ path
+ )
+ if os.path.isfile(candidate):
+ return os.path.relpath(candidate, os.getcwd())
+ for src, rel_dst in files_folders.items():
+ candidate = os.path.join(
+ src,
+ os.path.relpath(path, rel_dst)
+ )
+ if os.path.isfile(candidate):
+ return os.path.relpath(candidate, os.getcwd())
+
+ # whatever!
+ return None
+
+
+class LocaleBorg:
+ __shared_state = {
+ 'current_lang': None
+ }
+
+ def __init__(self):
+ self.__dict__ = self.__shared_state
diff --git a/requirements-3.txt b/requirements-3.txt
deleted file mode 100644
index 69f6a1c..0000000
--- a/requirements-3.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-https://github.com/python-imaging/Pillow/archive/master.zip
-mock>=1.0.0
-requests
-markdown
-Jinja2
-bbcode
diff --git a/requirements.txt b/requirements.txt
index f7134c0..03451ef 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
mock>=1.0.0
-requests
+requests>=1.0
markdown
Jinja2
bbcode
diff --git a/scripts/import_po.py b/scripts/import_po.py
new file mode 100644
index 0000000..6514391
--- /dev/null
+++ b/scripts/import_po.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+"""Download translations from transifex and regenerate files."""
+
+from __future__ import unicode_literals, print_function
+import codecs
+from glob import glob
+import os
+
+import polib
+
+os.system("tx pull -a")
+trans_files = glob(os.path.join('translations', 'nikola.messages', '*.po'))
+for fname in trans_files:
+ lang = os.path.splitext(os.path.basename(fname))[0].lower()
+ outf = os.path.join('nikola', 'data', 'themes', 'default',
+ 'messages', 'messages_{0}.py'.format(lang))
+ po = polib.pofile(fname)
+ lines = """# -*- encoding:utf-8 -*-
+from __future__ import unicode_literals
+
+MESSAGES = {""".splitlines()
+ lines2 = []
+ for entry in po:
+ lines2.append(' "{0}": "{1}",'. format(entry.msgid, entry.msgstr))
+ lines.extend(sorted(lines2))
+ lines.append("}\n")
+ print("Generating:", outf)
+ with codecs.open(outf, "wb+", "utf8") as outfile:
+ outfile.write('\n'.join(lines))
diff --git a/scripts/nikola.bat b/scripts/nikola.bat
new file mode 100644
index 0000000..0aad2d4
--- /dev/null
+++ b/scripts/nikola.bat
@@ -0,0 +1,2 @@
+@echo off
+python "%~dpn0" %* \ No newline at end of file
diff --git a/setup.py b/setup.py
index cdb8ac0..9fbe258 100755
--- a/setup.py
+++ b/setup.py
@@ -42,9 +42,24 @@ dependencies = [
'pytz',
]
+########### platform specific stuff #############
+import platform
+platform_system = platform.system()
+
+scripts = ['scripts/nikola']
+# platform specific scripts
+if platform_system == "Windows":
+ scripts.append('scripts/nikola.bat')
+
+##################################################
+
if sys.version_info[0] == 2:
# in Python 3 this becomes a builtin, for Python 2 we need the backport
dependencies.append('configparser')
+elif sys.version_info[0] == 3:
+ # Pillow introduced support for Python 3 with 2.0.0
+ dependencies.remove('pillow')
+ dependencies.append('pillow>=2.0.0')
# Provided as an attribute, so you can append to these instead
# of replicating them:
@@ -182,17 +197,23 @@ def find_package_data(
return out
setup(name='Nikola',
- version='5.4.2',
+ version='5.4.4',
description='Static blog/website generator',
author='Roberto Alsina and others',
author_email='ralsina@netmanagers.com.ar',
url='http://nikola.ralsina.com.ar/',
packages=['nikola',
'nikola.plugins',
+ 'nikola.plugins.command_planetoid',
+ 'nikola.plugins.compile_ipynb',
'nikola.plugins.compile_markdown',
+ 'nikola.plugins.compile_misaka',
+ 'nikola.plugins.compile_rest',
+ 'nikola.plugins.task_localsearch',
+ 'nikola.plugins.task_mustache',
'nikola.plugins.task_sitemap',
- 'nikola.plugins.compile_rest'],
- scripts=['scripts/nikola'],
+ ],
+ scripts=scripts,
install_requires=dependencies,
package_data=find_package_data(),
cmdclass={'install': nikola_install},
diff --git a/tests/base.py b/tests/base.py
new file mode 100644
index 0000000..92576c7
--- /dev/null
+++ b/tests/base.py
@@ -0,0 +1,58 @@
+# coding: utf8
+# Author: Rodrigo Bistolfi
+# Date: 03/2013
+
+
+""" Base class for Nikola test cases """
+
+
+__all__ = ["BaseTestCase"]
+
+
+import sys
+import unittest
+
+
+if sys.version_info < (2, 7):
+
+ try:
+ import unittest2
+ _unittest2 = True
+ except ImportError:
+ _unittest2 = False
+
+ if _unittest2:
+ BaseTestCase = unittest2.TestCase
+
+ else:
+
+ class BaseTestCase(unittest.TestCase):
+ """ Base class for providing 2.6 compatibility """
+
+ def assertIs(self, first, second, msg=None):
+ self.assertTrue(first is second)
+
+ def assertIsNot(self, first, second, msg=None):
+ self.assertTrue(first is not second)
+
+ def assertIsNone(self, expr, msg=None):
+ self.assertTrue(expr is None)
+
+ def assertIsNotNone(self, expr, msg=None):
+ self.assertTrue(expr is not None)
+
+ def assertIn(self, first, second, msg=None):
+ self.assertTrue(first in second)
+
+ def assertNotIn(self, first, second, msg=None):
+ self.assertTrue(first not in second)
+
+ def assertIsInstance(self, obj, cls, msg=None):
+ self.assertTrue(isinstance(obj, cls))
+
+ def assertNotIsInstance(self, obj, cls, msg=None):
+ self.assertFalse(isinstance(obj, cls))
+
+
+else:
+ BaseTestCase = unittest.TestCase
diff --git a/tests/test_compile_markdown.py b/tests/test_compile_markdown.py
new file mode 100644
index 0000000..a1f8591
--- /dev/null
+++ b/tests/test_compile_markdown.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+import codecs
+import shutil
+import tempfile
+import unittest
+from os import path
+
+from nikola.plugins.compile_markdown import CompileMarkdown
+
+
+class CompileMarkdownTests(unittest.TestCase):
+ def setUp(self):
+ self.tmp_dir = tempfile.mkdtemp()
+ self.input_path = path.join(self.tmp_dir, 'input.markdown')
+ self.output_path = path.join(self.tmp_dir, 'output.html')
+
+ self.compiler = CompileMarkdown()
+
+ def compile(self, input_string):
+ with codecs.open(self.input_path, "w+", "utf8") as input_file:
+ input_file.write(input_string)
+
+ self.compiler.compile_html(self.input_path, self.output_path)
+
+ output_str = None
+ with codecs.open(self.output_path, "r", "utf8") as output_path:
+ output_str = output_path.read()
+
+ return output_str
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp_dir)
+
+ def test_compile_html_empty(self):
+ input_str = ''
+ actual_output = self.compile(input_str)
+ self.assertEquals(actual_output, '')
+
+ def test_compile_html_heading_tags(self):
+ input_str = '''\
+# header 1
+## header 2
+### header 3
+#### header 4
+##### header 5
+###### header 6
+'''
+ expected_output = '''\
+<h2>header 1</h2>
+<h3>header 2</h3>
+<h4>header 3</h4>
+<h5>header 4</h5>
+<h6>header 5</h6>
+<h7>header 6</h7>
+'''
+
+ actual_output = self.compile(input_str)
+ self.assertEquals(actual_output.strip(), expected_output.strip())
+
+ def test_compile_html_code_hilite(self):
+ input_str = '''\
+ #!python
+ from this
+'''
+ expected_output = '''\
+<table class="codehilitetable"><tr><td class="linenos">\
+<div class="linenodiv"><pre>1</pre></div>\
+</td><td class="code"><div class="code">\
+<pre><span class="kn">from</span> <span class="nn">this</span>
+</pre></div>
+</td></tr></table>
+'''
+
+ actual_output = self.compile(input_str)
+ self.assertEquals(actual_output.strip(), expected_output.strip())
+
+ def test_compile_html_gist(self):
+ input_str = '''\
+Here's a gist file inline:
+[:gist: 4747847 zen.py]
+
+Cool, eh?
+'''
+ expected_output = '''\
+<p>Here's a gist file inline:
+<div class="gist">
+<script src="https://gist.github.com/4747847.js?file=zen.py"></script>
+<noscript>
+<pre>import this</pre>
+</noscript>
+</div>
+</p>
+<p>Cool, eh?</p>
+'''
+ actual_output = self.compile(input_str)
+ self.assertEquals(actual_output.strip(), expected_output.strip())
+
+ def test_compile_html_gist_2(self):
+ input_str = '''\
+Here's a gist file inline, using reStructuredText syntax:
+..gist:: 4747847 zen.py
+
+Cool, eh?
+'''
+ expected_output = '''\
+<p>Here's a gist file inline, using reStructuredText syntax:
+<div class="gist">
+<script src="https://gist.github.com/4747847.js?file=zen.py"></script>
+<noscript>
+<pre>import this</pre>
+</noscript>
+</div>
+</p>
+<p>Cool, eh?</p>
+'''
+ actual_output = self.compile(input_str)
+ self.assertEquals(actual_output.strip(), expected_output.strip())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_integration.py b/tests/test_integration.py
index c940a07..802dcc7 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -3,12 +3,15 @@ from __future__ import unicode_literals, print_function
import codecs
from contextlib import contextmanager
+import locale
import os
import shutil
+import subprocess # NOQA
import tempfile
import unittest
import lxml.html
+from nose.plugins.skip import SkipTest
from context import nikola
from nikola import main
@@ -66,6 +69,7 @@ class EmptyBuildTest(unittest.TestCase):
@classmethod
def tearDownClass(self):
"""Remove the demo site."""
+ shutil.rmtree(self.tmpdir)
def test_build(self):
"""Ensure the build did something."""
@@ -82,6 +86,13 @@ class DemoBuildTest(EmptyBuildTest):
"""Fill the site with demo content."""
self.init_command.copy_sample_site(self.target_dir)
self.init_command.create_configuration(self.target_dir)
+ # File for Issue #374 (empty post text)
+ with codecs.open(os.path.join(self.target_dir, 'posts', 'empty.txt'), "wb+", "utf8") as outf:
+ outf.write(
+ ".. title: foobar\n"
+ ".. slug: foobar\n"
+ ".. date: 2013/03/06 19:08:15\n"
+ )
class TranslatedBuildTest(EmptyBuildTest):
@@ -89,6 +100,13 @@ class TranslatedBuildTest(EmptyBuildTest):
dataname = "translated_titles"
+ def __init__(self, *a, **kw):
+ super(TranslatedBuildTest, self).__init__(*a, **kw)
+ try:
+ locale.setlocale(locale.LC_ALL, ("es", "utf8"))
+ except:
+ raise SkipTest
+
def test_translated_titles(self):
"""Check that translated title is picked up."""
en_file = os.path.join(self.target_dir, "output", "stories", "1.html")
@@ -134,6 +152,37 @@ class RelativeLinkTest(DemoBuildTest):
self.assertTrue(flag)
+#class TestCheck(DemoBuildTest):
+ #"""The demo build should pass 'nikola check'"""
+
+ #def test_check_links(self):
+ #with cd(self.target_dir):
+ #r = subprocess.call("nikola check -l", shell=True)
+ #self.assertEqual(r, 0)
+
+ #def test_check_files(self):
+ #with cd(self.target_dir):
+ #r = subprocess.call("nikola check -f", shell=True)
+ #self.assertEqual(r, 0)
+
+
+#class TestCheckFailure(DemoBuildTest):
+ #"""The demo build should pass 'nikola check'"""
+
+ #def test_check_links_fail(self):
+ #with cd(self.target_dir):
+ #os.unlink(os.path.join("output", "archive.html"))
+ #rv = subprocess.call("nikola check -l", shell=True)
+ #self.assertEqual(rv, 1)
+
+ #def test_check_files_fail(self):
+ #with cd(self.target_dir):
+ #with codecs.open(os.path.join("output", "foobar"), "wb+", "utf8") as outf:
+ #outf.write("foo")
+ #rv = subprocess.call("nikola check -f", shell=True)
+ #self.assertEqual(rv, 1)
+
+
class RelativeLinkTest2(DemoBuildTest):
"""Check that dropping stories to the root doesn't break links."""
diff --git a/tests/test_plugin_importing.py b/tests/test_plugin_importing.py
index 1a610bf..677dde8 100644
--- a/tests/test_plugin_importing.py
+++ b/tests/test_plugin_importing.py
@@ -9,8 +9,8 @@ class ImportPluginsTest(unittest.TestCase):
def test_importing_command_import_wordpress(self):
import nikola.plugins.command_import_wordpress # NOQA
- def test_importing_task_sitemap(self):
- import nikola.plugins.task_sitemap.sitemap_gen # NOQA
-
def test_importing_compile_rest(self):
import nikola.plugins.compile_rest # NOQA
+
+ def test_importing_plugin_compile_markdown(self):
+ import nikola.plugins.compile_markdown # NOQA
diff --git a/tests/test_rst_extensions.py b/tests/test_rst_extensions.py
new file mode 100644
index 0000000..845d6a7
--- /dev/null
+++ b/tests/test_rst_extensions.py
@@ -0,0 +1,223 @@
+# coding: utf8
+# Author: Rodrigo Bistolfi
+# Date: 03/2013
+
+
+""" Test cases for Nikola ReST extensions.
+A base class ReSTExtensionTestCase provides the tests basic behaivor.
+Subclasses must override the "sample" class attribute with the ReST markup.
+The sample will be rendered as HTML using publish_parts() by setUp().
+One method is provided for checking the resulting HTML:
+
+ * assertHTMLContains(element, attributes=None, text=None)
+
+The HTML is parsed with lxml for checking against the data you provide. The
+method takes an element argument, a string representing the *name* of an HTML
+tag, like "script" or "iframe". We will try to find this tag in the document
+and perform the tests on it. You can pass a dictionary to the attributes kwarg
+representing the name and the value of the tag attributes. The text kwarg takes
+a string argument, which will be tested against the contents of the HTML
+element.
+One last caveat: you need to url unquote your urls if you are going to test
+attributes like "src" or "link", since the HTML rendered by docutils will be
+always unquoted.
+
+"""
+
+
+from __future__ import unicode_literals
+
+try:
+ from io import StringIO
+except ImportError:
+ from StringIO import StringIO # NOQA
+
+from docutils.core import publish_parts
+from lxml import html
+import mock
+
+import unittest
+import nikola.plugins.compile_rest
+from nikola.utils import _reload
+from base import BaseTestCase
+
+
+class ReSTExtensionTestCase(BaseTestCase):
+ """ Base class for testing ReST extensions """
+
+ sample = None
+
+ def setUp(self):
+ """ Parse cls.sample into a HTML document tree """
+ super(ReSTExtensionTestCase, self).setUp()
+ self.setHtmlFromRst(self.sample)
+
+ def setHtmlFromRst(self, rst):
+ """ Create html output from rst string """
+ self.html = publish_parts(rst, writer_name="html")["body"]
+ self.html_doc = html.parse(StringIO(self.html))
+
+ def assertHTMLContains(self, element, attributes=None, text=None):
+ """ Test if HTML document includes an element with the given
+ attributes and text content
+
+ """
+ try:
+ tag = next(self.html_doc.iter(element))
+ except StopIteration:
+ raise Exception("<{}> not in {}".format(element, self.html))
+ else:
+ if attributes:
+ arg_attrs = set(attributes.items())
+ tag_attrs = set(tag.items())
+ self.assertTrue(arg_attrs.issubset(tag_attrs))
+ if text:
+ self.assertIn(text, tag.text)
+
+
+class ReSTExtensionTestCaseTestCase(ReSTExtensionTestCase):
+ """ Simple test for our base class :) """
+
+ sample = '.. raw:: html\n\n <iframe src="foo" height="bar">spam</iframe>'
+
+ def test_test(self):
+ self.assertHTMLContains("iframe", attributes={"src": "foo"},
+ text="spam")
+ self.assertRaises(Exception, self.assertHTMLContains, "eggs", {})
+
+
+class GistTestCase(ReSTExtensionTestCase):
+ """ Test GitHubGist.
+ We will replace get_raw_gist() and get_raw_gist_with_filename()
+ monkeypatching the GitHubGist class for avoiding network dependency
+
+ """
+ gist_type = nikola.plugins.compile_rest.GitHubGist
+ sample = '.. gist:: fake_id\n :file: spam.py'
+ sample_without_filename = '.. gist:: fake_id2'
+
+ def setUp(self):
+ """ Patch GitHubGist for avoiding network dependency """
+ self.gist_type.get_raw_gist_with_filename = lambda *_: 'raw_gist_file'
+ self.gist_type.get_raw_gist = lambda *_: "raw_gist"
+ _reload(nikola.plugins.compile_rest)
+
+ def test_gist(self):
+ """ Test the gist directive with filename """
+ self.setHtmlFromRst(self.sample)
+ output = 'https://gist.github.com/fake_id.js?file=spam.py'
+ self.assertHTMLContains("script", attributes={"src": output})
+ self.assertHTMLContains("pre", text="raw_gist_file")
+
+ def test_gist_without_filename(self):
+ """ Test the gist directive without filename """
+ self.setHtmlFromRst(self.sample_without_filename)
+ output = 'https://gist.github.com/fake_id2.js'
+ self.assertHTMLContains("script", attributes={"src": output})
+ self.assertHTMLContains("pre", text="raw_gist")
+
+
+class GistIntegrationTestCase(ReSTExtensionTestCase):
+ """ Test requests integration. The gist plugin uses requests to fetch gist
+ contents and place it in a noscript tag.
+
+ """
+ sample = '.. gist:: 1812835'
+
+ def test_gist_integration(self):
+ """ Fetch contents of the gist from GH and render in a noscript tag """
+ text = ('Be alone, that is the secret of invention: be alone, that is'
+ ' when ideas are born. -- Nikola Tesla')
+ self.assertHTMLContains('pre', text=text)
+
+
+class SlidesTestCase(ReSTExtensionTestCase):
+ """ Slides test case """
+
+ sample = '.. slides:: IMG.jpg\n'
+
+ def test_slides(self):
+ """ Test the slides js generation and img tag creation """
+ self.assertHTMLContains("img", attributes={"src": "IMG.jpg"})
+
+
+class SoundCloudTestCase(ReSTExtensionTestCase):
+ """ SoundCloud test case """
+
+ sample = '.. soundcloud:: SID\n :height: 400\n :width: 600'
+
+ def test_soundcloud(self):
+ """ Test SoundCloud iframe tag generation """
+ self.assertHTMLContains("iframe",
+ attributes={"src": ("https://w.soundcloud.com"
+ "/player/?url=http://"
+ "api.soundcloud.com/"
+ "tracks/SID"),
+ "height": "400", "width": "600"})
+
+
+class VimeoTestCase(ReSTExtensionTestCase):
+ """Vimeo test.
+ Set Vimeo.request_size to False for avoiding querying the Vimeo api
+ over the network
+
+ """
+ sample = '.. vimeo:: VID\n :height: 400\n :width: 600'
+
+ def setUp(self):
+ """ Disable query of the vimeo api over the wire """
+ nikola.plugins.compile_rest.Vimeo.request_size = False
+ super(VimeoTestCase, self).setUp()
+ _reload(nikola.plugins.compile_rest)
+
+ def test_vimeo(self):
+ """ Test Vimeo iframe tag generation """
+ self.assertHTMLContains("iframe",
+ attributes={"src": ("http://player.vimeo.com/"
+ "video/VID"),
+ "height": "400", "width": "600"})
+
+
+class YoutubeTestCase(ReSTExtensionTestCase):
+ """ Youtube test case """
+
+ sample = '.. youtube:: YID\n :height: 400\n :width: 600'
+
+ def test_youtube(self):
+ """ Test Youtube iframe tag generation """
+ self.assertHTMLContains("iframe",
+ attributes={"src": ("http://www.youtube.com/"
+ "embed/YID?rel=0&hd=1&"
+ "wmode=transparent"),
+ "height": "400", "width": "600"})
+
+
+class ListingTestCase(ReSTExtensionTestCase):
+ """ Listing test case and CodeBlock alias tests """
+
+ sample = '.. listing:: nikola.py python'
+ sample2 = '.. code-block:: python\n\n import antigravity'
+ sample3 = '.. sourcecode:: python\n\n import antigravity'
+
+ opener_mock = mock.mock_open(read_data="import antigravity\n")
+ opener_mock.return_value.readlines.return_value = "import antigravity\n"
+
+ def setUp(self):
+ """ Inject a mock open function for not generating a test site """
+ self.f = StringIO("import antigravity\n")
+ #_reload(nikola.plugins.compile_rest)
+
+ def test_listing(self):
+ """ Test that we can render a file object contents without errors """
+ with mock.patch("nikola.plugins.compile_rest.listing.codecs_open", self.opener_mock, create=True):
+ self.setHtmlFromRst(self.sample)
+
+ def test_codeblock_alias(self):
+ """ Test CodeBlock aliases """
+ with mock.patch("nikola.plugins.compile_rest.listing.codecs_open", self.opener_mock, create=True):
+ self.setHtmlFromRst(self.sample2)
+ self.setHtmlFromRst(self.sample3)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/translations/nikola.messages/ca.po b/translations/nikola.messages/ca.po
new file mode 100644
index 0000000..f983fda
--- /dev/null
+++ b/translations/nikola.messages/ca.po
@@ -0,0 +1,74 @@
+# Messages in Nikola
+# Copyright (C) 2012-2013
+# This file is distributed under the same license as the Nikola package.
+#
+# Translators:
+# Translators:
+# Roberto Alsina <ralsina@netmanagers.com.ar>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Nikola\n"
+"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n"
+"POT-Creation-Date: 2013-03-15 12:24-0300\n"
+"PO-Revision-Date: 2013-04-06 18:50+0000\n"
+"Last-Translator: ralsina <ralsina@netmanagers.com.ar>\n"
+"Language-Team: Catalan (http://www.transifex.com/projects/p/nikola/language/ca/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: ca\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "More posts about"
+msgstr "Més entrades sobre"
+
+msgid "LANGUAGE"
+msgstr "Català"
+
+msgid "Tags"
+msgstr "Etiquetes"
+
+msgid "Read more"
+msgstr "Llegeix-ne més"
+
+msgid "Posts about %s"
+msgstr "Entrades sobre %s"
+
+msgid "Next post"
+msgstr "Entrada següent"
+
+msgid "old posts page %d"
+msgstr "entrades antigues pàgina %d"
+
+msgid "Source"
+msgstr "Codi"
+
+msgid "Read in English"
+msgstr "Llegeix-ho en català"
+
+msgid "Posts for year %s"
+msgstr "Entrades de l'any %s"
+
+msgid "Newer posts"
+msgstr "Entrades posteriors"
+
+msgid "Previous post"
+msgstr "Entrada anterior"
+
+msgid "Also available in"
+msgstr "També disponibles en"
+
+msgid "Original site"
+msgstr "Lloc original"
+
+msgid "Older posts"
+msgstr "Entrades anteriors"
+
+msgid "Archive"
+msgstr "Arxiu"
+
+msgid "Posted"
+msgstr "Publicat"
+
+msgid "Posts for {month} {year}"
+msgstr ""
diff --git a/translations/nikola.messages/de.po b/translations/nikola.messages/de.po
new file mode 100644
index 0000000..97712de
--- /dev/null
+++ b/translations/nikola.messages/de.po
@@ -0,0 +1,75 @@
+# Messages in Nikola
+# Copyright (C) 2012-2013
+# This file is distributed under the same license as the Nikola package.
+#
+# Translators:
+# Translators:
+# Roberto Alsina <ralsina@netmanagers.com.ar>, 2013.
+# <wenso@gmx.de>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Nikola\n"
+"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n"
+"POT-Creation-Date: 2013-03-15 12:24-0300\n"
+"PO-Revision-Date: 2013-04-10 06:15+0000\n"
+"Last-Translator: Niko <wenso@gmx.de>\n"
+"Language-Team: German (http://www.transifex.com/projects/p/nikola/language/de/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: de\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "More posts about"
+msgstr "Weitere Einträge über"
+
+msgid "LANGUAGE"
+msgstr "Deutsch"
+
+msgid "Tags"
+msgstr "Tags"
+
+msgid "Read more"
+msgstr "Weiterlesen"
+
+msgid "Posts about %s"
+msgstr "Einträge über %s"
+
+msgid "Next post"
+msgstr "Nächster Eintrag"
+
+msgid "old posts page %d"
+msgstr "Vorherige Einträge %d"
+
+msgid "Source"
+msgstr "Source"
+
+msgid "Read in English"
+msgstr "Auf Deutsch lesen"
+
+msgid "Posts for year %s"
+msgstr "Einträge aus dem Jahr %s"
+
+msgid "Newer posts"
+msgstr "Neuere Einträge"
+
+msgid "Previous post"
+msgstr "Vorheriger Eintrag"
+
+msgid "Also available in"
+msgstr "Auch verfügbar in"
+
+msgid "Original site"
+msgstr "Original-Seite"
+
+msgid "Older posts"
+msgstr "Ältere Einträge"
+
+msgid "Archive"
+msgstr "Archiv"
+
+msgid "Posted"
+msgstr "Veröffentlicht"
+
+msgid "Posts for {month} {year}"
+msgstr "Einträge für {month} {year}"
diff --git a/translations/nikola.messages/el.po b/translations/nikola.messages/el.po
new file mode 100644
index 0000000..a4077b9
--- /dev/null
+++ b/translations/nikola.messages/el.po
@@ -0,0 +1,75 @@
+# Messages in Nikola
+# Copyright (C) 2012-2013
+# This file is distributed under the same license as the Nikola package.
+#
+# Translators:
+# Translators:
+# Panagiotis Mavrogiorgos <otinanai90@gmail.com>, 2013.
+# Roberto Alsina <ralsina@netmanagers.com.ar>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Nikola\n"
+"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n"
+"POT-Creation-Date: 2013-03-15 12:24-0300\n"
+"PO-Revision-Date: 2013-04-06 18:50+0000\n"
+"Last-Translator: ralsina <ralsina@netmanagers.com.ar>\n"
+"Language-Team: Greek (http://www.transifex.com/projects/p/nikola/language/el/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: el\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "More posts about"
+msgstr "Περισσότερες αναρτήσεις για"
+
+msgid "LANGUAGE"
+msgstr "Ελληνικά"
+
+msgid "Tags"
+msgstr "Ετικέτες"
+
+msgid "Read more"
+msgstr "Διαβάστε περισσότερα"
+
+msgid "Posts about %s"
+msgstr "Αναρτήσεις για %s"
+
+msgid "Next post"
+msgstr "Επόμενη ανάρτηση"
+
+msgid "old posts page %d"
+msgstr "σελίδα παλαιότερων αναρτήσεων %d"
+
+msgid "Source"
+msgstr "Πηγαίος κώδικας"
+
+msgid "Read in English"
+msgstr "Διαβάστε στα Ελληνικά"
+
+msgid "Posts for year %s"
+msgstr "Αναρτήσεις για το έτος %s"
+
+msgid "Newer posts"
+msgstr "Νεότερες αναρτήσεις"
+
+msgid "Previous post"
+msgstr "Προηγούμενη ανάρτηση"
+
+msgid "Also available in"
+msgstr "Διαθέσιμο και στα"
+
+msgid "Original site"
+msgstr "Ιστοσελίδα αρχικής ανάρτησης"
+
+msgid "Older posts"
+msgstr "Παλαιότερες αναρτήσεις"
+
+msgid "Archive"
+msgstr "Αρχείο"
+
+msgid "Posted"
+msgstr "Αναρτήθηκε"
+
+msgid "Posts for {month} {year}"
+msgstr ""
diff --git a/translations/nikola.messages/en.po b/translations/nikola.messages/en.po
new file mode 100644
index 0000000..4fa7200
--- /dev/null
+++ b/translations/nikola.messages/en.po
@@ -0,0 +1,72 @@
+# Messages in Nikola
+# Copyright (C) 2012-2013
+# This file is distributed under the same license as the Nikola package.
+#
+# Translators:
+# Roberto Alsina <ralsina@netmanagers.com.ar>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Nikola\n"
+"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n"
+"POT-Creation-Date: 2013-03-15 12:24-0300\n"
+"PO-Revision-Date: 2013-03-15 15:29+0000\n"
+"Last-Translator: ralsina <ralsina@netmanagers.com.ar>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "More posts about"
+msgstr "More posts about"
+
+msgid "LANGUAGE"
+msgstr "English"
+
+msgid "Tags"
+msgstr "Tags"
+
+msgid "Read more"
+msgstr "Read more"
+
+msgid "Posts about %s"
+msgstr "Posts about %s"
+
+msgid "Next post"
+msgstr "Next post"
+
+msgid "old posts page %d"
+msgstr "old posts page %d"
+
+msgid "Source"
+msgstr "Source"
+
+msgid "Read in English"
+msgstr "Read in English"
+
+msgid "Posts for year %s"
+msgstr "Posts for year %s"
+
+msgid "Newer posts"
+msgstr "Newer posts"
+
+msgid "Previous post"
+msgstr "Previous post"
+
+msgid "Also available in"
+msgstr "Also available in"
+
+msgid "Original site"
+msgstr "Original site"
+
+msgid "Older posts"
+msgstr "Older posts"
+
+msgid "Archive"
+msgstr "Archive"
+
+msgid "Posted"
+msgstr "Posted"
+
+msgid "Posts for {month} {year}"
+msgstr "Posts for {month} {year}"
diff --git a/translations/nikola.messages/es.po b/translations/nikola.messages/es.po
new file mode 100644
index 0000000..5d34152
--- /dev/null
+++ b/translations/nikola.messages/es.po
@@ -0,0 +1,74 @@
+# Messages in Nikola
+# Copyright (C) 2012-2013
+# This file is distributed under the same license as the Nikola package.
+#
+# Translators:
+# Translators:
+# Roberto Alsina <ralsina@netmanagers.com.ar>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Nikola\n"
+"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n"
+"POT-Creation-Date: 2013-03-15 12:24-0300\n"
+"PO-Revision-Date: 2013-04-06 18:52+0000\n"
+"Last-Translator: ralsina <ralsina@netmanagers.com.ar>\n"
+"Language-Team: Spanish (http://www.transifex.com/projects/p/nikola/language/es/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: es\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "More posts about"
+msgstr "Más posts sobre"
+
+msgid "LANGUAGE"
+msgstr "Español"
+
+msgid "Tags"
+msgstr "Tags"
+
+msgid "Read more"
+msgstr "Leer más"
+
+msgid "Posts about %s"
+msgstr "Posts sobre %s"
+
+msgid "Next post"
+msgstr "Siguiente post"
+
+msgid "old posts page %d"
+msgstr "posts antiguos página %d"
+
+msgid "Source"
+msgstr "Código"
+
+msgid "Read in English"
+msgstr "Leer en español"
+
+msgid "Posts for year %s"
+msgstr "Posts del año %s"
+
+msgid "Newer posts"
+msgstr "Posts posteriores"
+
+msgid "Previous post"
+msgstr "Post anterior"
+
+msgid "Also available in"
+msgstr "También disponible en"
+
+msgid "Original site"
+msgstr "Sitio original"
+
+msgid "Older posts"
+msgstr "Posts anteriores"
+
+msgid "Archive"
+msgstr "Archivo"
+
+msgid "Posted"
+msgstr "Publicado"
+
+msgid "Posts for {month} {year}"
+msgstr "Posts de {month} {year}"
diff --git a/translations/nikola.messages/fr.po b/translations/nikola.messages/fr.po
new file mode 100644
index 0000000..aeb6ec5
--- /dev/null
+++ b/translations/nikola.messages/fr.po
@@ -0,0 +1,75 @@
+# Messages in Nikola
+# Copyright (C) 2012-2013
+# This file is distributed under the same license as the Nikola package.
+#
+# Translators:
+# Translators:
+# Pablo SEMINARIO <pabluk@gmail.com>, 2013.
+# Roberto Alsina <ralsina@netmanagers.com.ar>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Nikola\n"
+"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n"
+"POT-Creation-Date: 2013-03-15 12:24-0300\n"
+"PO-Revision-Date: 2013-04-13 12:52+0000\n"
+"Last-Translator: pabluk <pabluk@gmail.com>\n"
+"Language-Team: French (http://www.transifex.com/projects/p/nikola/language/fr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: fr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgid "More posts about"
+msgstr "Plus de billets sur"
+
+msgid "LANGUAGE"
+msgstr "Français"
+
+msgid "Tags"
+msgstr "Étiquettes"
+
+msgid "Read more"
+msgstr "Lire la suite"
+
+msgid "Posts about %s"
+msgstr "Billets sur %s"
+
+msgid "Next post"
+msgstr "Billet suivant"
+
+msgid "old posts page %d"
+msgstr "anciens billets page %d"
+
+msgid "Source"
+msgstr "Source"
+
+msgid "Read in English"
+msgstr "Lire en français"
+
+msgid "Posts for year %s"
+msgstr "Billets de l'année %s"
+
+msgid "Newer posts"
+msgstr "Billets récents"
+
+msgid "Previous post"
+msgstr "Billet précédent"
+
+msgid "Also available in"
+msgstr "Disponible aussi en"
+
+msgid "Original site"
+msgstr "Site d'origine"
+
+msgid "Older posts"
+msgstr "Anciens billets"
+
+msgid "Archive"
+msgstr "Archives"
+
+msgid "Posted"
+msgstr "Publié"
+
+msgid "Posts for {month} {year}"
+msgstr "Billets de {month} {year}"
diff --git a/translations/nikola.messages/it.po b/translations/nikola.messages/it.po
new file mode 100644
index 0000000..9b86f15
--- /dev/null
+++ b/translations/nikola.messages/it.po
@@ -0,0 +1,74 @@
+# Messages in Nikola
+# Copyright (C) 2012-2013
+# This file is distributed under the same license as the Nikola package.
+#
+# Translators:
+# Translators:
+# Roberto Alsina <ralsina@netmanagers.com.ar>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Nikola\n"
+"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n"
+"POT-Creation-Date: 2013-03-15 12:24-0300\n"
+"PO-Revision-Date: 2013-04-06 18:50+0000\n"
+"Last-Translator: ralsina <ralsina@netmanagers.com.ar>\n"
+"Language-Team: Italian (http://www.transifex.com/projects/p/nikola/language/it/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: it\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "More posts about"
+msgstr "Altri articoli s"
+
+msgid "LANGUAGE"
+msgstr "Italiano"
+
+msgid "Tags"
+msgstr "Tags"
+
+msgid "Read more"
+msgstr "Espandi"
+
+msgid "Posts about %s"
+msgstr "Articoli su %s"
+
+msgid "Next post"
+msgstr "Articolo successivo"
+
+msgid "old posts page %d"
+msgstr "pagina dei vecchi articoli %d"
+
+msgid "Source"
+msgstr "Source"
+
+msgid "Read in English"
+msgstr "Leggi in italiano"
+
+msgid "Posts for year %s"
+msgstr "Articoli per l'anno %s"
+
+msgid "Newer posts"
+msgstr "Articoli recenti"
+
+msgid "Previous post"
+msgstr "Articolo precedente"
+
+msgid "Also available in"
+msgstr "Anche disponibile in"
+
+msgid "Original site"
+msgstr "Sito originale"
+
+msgid "Older posts"
+msgstr "Articoli vecchi"
+
+msgid "Archive"
+msgstr "Archivio"
+
+msgid "Posted"
+msgstr "Pubblicato"
+
+msgid "Posts for {month} {year}"
+msgstr ""
diff --git a/translations/nikola.messages/ja.po b/translations/nikola.messages/ja.po
new file mode 100644
index 0000000..c439ebe
--- /dev/null
+++ b/translations/nikola.messages/ja.po
@@ -0,0 +1,75 @@
+# Messages in Nikola
+# Copyright (C) 2012-2013
+# This file is distributed under the same license as the Nikola package.
+#
+# Translators:
+# Translators:
+# Roberto Alsina <ralsina@netmanagers.com.ar>, 2013.
+# Yasuhiko Shiga <4wordextinguisher@gmail.com>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Nikola\n"
+"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n"
+"POT-Creation-Date: 2013-03-15 12:24-0300\n"
+"PO-Revision-Date: 2013-04-14 03:31+0000\n"
+"Last-Translator: quoth <4wordextinguisher@gmail.com>\n"
+"Language-Team: Japanese (http://www.transifex.com/projects/p/nikola/language/ja/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: ja\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgid "More posts about"
+msgstr "タグ"
+
+msgid "LANGUAGE"
+msgstr "日本語"
+
+msgid "Tags"
+msgstr "タグ"
+
+msgid "Read more"
+msgstr "続きを読む"
+
+msgid "Posts about %s"
+msgstr "%sについての記事"
+
+msgid "Next post"
+msgstr "次の記事"
+
+msgid "old posts page %d"
+msgstr "前の記事 %dページ目"
+
+msgid "Source"
+msgstr "ソース"
+
+msgid "Read in English"
+msgstr "日本語で読む"
+
+msgid "Posts for year %s"
+msgstr "%s年の記事"
+
+msgid "Newer posts"
+msgstr "新しい記事"
+
+msgid "Previous post"
+msgstr "前の記事"
+
+msgid "Also available in"
+msgstr "他の言語で読む"
+
+msgid "Original site"
+msgstr "元のサイト"
+
+msgid "Older posts"
+msgstr "過去の記事"
+
+msgid "Archive"
+msgstr "過去の記事"
+
+msgid "Posted"
+msgstr "投稿日時"
+
+msgid "Posts for {month} {year}"
+msgstr "{year}年{month}月の記事"
diff --git a/translations/nikola.messages/pl.po b/translations/nikola.messages/pl.po
new file mode 100644
index 0000000..bf796b1
--- /dev/null
+++ b/translations/nikola.messages/pl.po
@@ -0,0 +1,75 @@
+# Messages in Nikola
+# Copyright (C) 2012-2013
+# This file is distributed under the same license as the Nikola package.
+#
+# Translators:
+# Translators:
+# <kwpolska@gmail.com>, 2013.
+# Roberto Alsina <ralsina@netmanagers.com.ar>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Nikola\n"
+"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n"
+"POT-Creation-Date: 2013-03-15 12:24-0300\n"
+"PO-Revision-Date: 2013-04-06 19:00+0000\n"
+"Last-Translator: Kwpolska <kwpolska@gmail.com>\n"
+"Language-Team: Polish (http://www.transifex.com/projects/p/nikola/language/pl/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: pl\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+msgid "More posts about"
+msgstr "Więcej postów o"
+
+msgid "LANGUAGE"
+msgstr "polski"
+
+msgid "Tags"
+msgstr "Tags"
+
+msgid "Read more"
+msgstr "Czytaj więcej"
+
+msgid "Posts about %s"
+msgstr "Posty o %s"
+
+msgid "Next post"
+msgstr "Następny post"
+
+msgid "old posts page %d"
+msgstr "stare posty, strona %d"
+
+msgid "Source"
+msgstr "Źródło"
+
+msgid "Read in English"
+msgstr "Czytaj po polsku"
+
+msgid "Posts for year %s"
+msgstr "Posty z roku %s"
+
+msgid "Newer posts"
+msgstr "Nowsze posty"
+
+msgid "Previous post"
+msgstr "Poprzedni post"
+
+msgid "Also available in"
+msgstr "Również dostępny w"
+
+msgid "Original site"
+msgstr "Oryginalna strona"
+
+msgid "Older posts"
+msgstr "Starsze posty"
+
+msgid "Archive"
+msgstr "Archiwum"
+
+msgid "Posted"
+msgstr "Opublikowany"
+
+msgid "Posts for {month} {year}"
+msgstr "Posty z {month} {year}"
diff --git a/translations/nikola.messages/pt_BR.po b/translations/nikola.messages/pt_BR.po
new file mode 100644
index 0000000..37e7a45
--- /dev/null
+++ b/translations/nikola.messages/pt_BR.po
@@ -0,0 +1,75 @@
+# Messages in Nikola
+# Copyright (C) 2012-2013
+# This file is distributed under the same license as the Nikola package.
+#
+# Translators:
+# Translators:
+# eduardo <schettino72@gmail.com>, 2013.
+# Roberto Alsina <ralsina@netmanagers.com.ar>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Nikola\n"
+"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n"
+"POT-Creation-Date: 2013-03-15 12:24-0300\n"
+"PO-Revision-Date: 2013-04-13 14:29+0000\n"
+"Last-Translator: schettino72 <schettino72@gmail.com>\n"
+"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/nikola/language/pt_BR/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: pt_BR\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgid "More posts about"
+msgstr "Mais posts sobre"
+
+msgid "LANGUAGE"
+msgstr "Português"
+
+msgid "Tags"
+msgstr "Tags"
+
+msgid "Read more"
+msgstr "Leia mais"
+
+msgid "Posts about %s"
+msgstr "Posts sobre %s"
+
+msgid "Next post"
+msgstr "Próximo post"
+
+msgid "old posts page %d"
+msgstr "Posts antigos página %d"
+
+msgid "Source"
+msgstr "Código"
+
+msgid "Read in English"
+msgstr "Ler em português"
+
+msgid "Posts for year %s"
+msgstr "Posts do ano %s"
+
+msgid "Newer posts"
+msgstr "Posts mais recentes"
+
+msgid "Previous post"
+msgstr "Post anterior"
+
+msgid "Also available in"
+msgstr "Também disponível em"
+
+msgid "Original site"
+msgstr "Site original"
+
+msgid "Older posts"
+msgstr "Posts mais antigos"
+
+msgid "Archive"
+msgstr "Arquivo"
+
+msgid "Posted"
+msgstr "Publicado"
+
+msgid "Posts for {month} {year}"
+msgstr "Posts de {month} {year}"
diff --git a/translations/nikola.messages/ru.po b/translations/nikola.messages/ru.po
new file mode 100644
index 0000000..eb9fdc8
--- /dev/null
+++ b/translations/nikola.messages/ru.po
@@ -0,0 +1,74 @@
+# Messages in Nikola
+# Copyright (C) 2012-2013
+# This file is distributed under the same license as the Nikola package.
+#
+# Translators:
+# Translators:
+# Roberto Alsina <ralsina@netmanagers.com.ar>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Nikola\n"
+"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n"
+"POT-Creation-Date: 2013-03-15 12:24-0300\n"
+"PO-Revision-Date: 2013-04-06 18:50+0000\n"
+"Last-Translator: ralsina <ralsina@netmanagers.com.ar>\n"
+"Language-Team: Russian (http://www.transifex.com/projects/p/nikola/language/ru/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: ru\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+msgid "More posts about"
+msgstr "Больше записей о"
+
+msgid "LANGUAGE"
+msgstr "Русский"
+
+msgid "Tags"
+msgstr "Тэги"
+
+msgid "Read more"
+msgstr "Продолжить чтение"
+
+msgid "Posts about %s"
+msgstr "Записи с тэгом %s:"
+
+msgid "Next post"
+msgstr "Следующая запись"
+
+msgid "old posts page %d"
+msgstr "страница со старыми записями %d"
+
+msgid "Source"
+msgstr "Source"
+
+msgid "Read in English"
+msgstr "Прочесть по-русски"
+
+msgid "Posts for year %s"
+msgstr "Записи за %s год"
+
+msgid "Newer posts"
+msgstr "Новые записи"
+
+msgid "Previous post"
+msgstr "Предыдущая запись"
+
+msgid "Also available in"
+msgstr "Также доступно в"
+
+msgid "Original site"
+msgstr "Оригинальный сайт"
+
+msgid "Older posts"
+msgstr "Старые записи"
+
+msgid "Archive"
+msgstr "Архив"
+
+msgid "Posted"
+msgstr "Опубликовано"
+
+msgid "Posts for {month} {year}"
+msgstr ""
diff --git a/translations/nikola.messages/zh_CN.po b/translations/nikola.messages/zh_CN.po
new file mode 100644
index 0000000..da56182
--- /dev/null
+++ b/translations/nikola.messages/zh_CN.po
@@ -0,0 +1,75 @@
+# Messages in Nikola
+# Copyright (C) 2012-2013
+# This file is distributed under the same license as the Nikola package.
+#
+# Translators:
+# Translators:
+# Kade For <kadefor@gmail.com>, 2013.
+# Roberto Alsina <ralsina@netmanagers.com.ar>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Nikola\n"
+"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n"
+"POT-Creation-Date: 2013-03-15 12:24-0300\n"
+"PO-Revision-Date: 2013-04-14 08:24+0000\n"
+"Last-Translator: kadefor <kadefor@gmail.com>\n"
+"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/nikola/language/zh_CN/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: zh_CN\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgid "More posts about"
+msgstr "更多相关文章:"
+
+msgid "LANGUAGE"
+msgstr "简体中文"
+
+msgid "Tags"
+msgstr "标签"
+
+msgid "Read more"
+msgstr "更多"
+
+msgid "Posts about %s"
+msgstr "文章分类:%s"
+
+msgid "Next post"
+msgstr "后一篇"
+
+msgid "old posts page %d"
+msgstr "旧文章页 %d"
+
+msgid "Source"
+msgstr "源代码"
+
+msgid "Read in English"
+msgstr "中文版"
+
+msgid "Posts for year %s"
+msgstr "%s年文章"
+
+msgid "Newer posts"
+msgstr "新一篇"
+
+msgid "Previous post"
+msgstr "前一篇"
+
+msgid "Also available in"
+msgstr "其他语言版本"
+
+msgid "Original site"
+msgstr "原文地址"
+
+msgid "Older posts"
+msgstr "旧一篇"
+
+msgid "Archive"
+msgstr "文章存档"
+
+msgid "Posted"
+msgstr "发表于"
+
+msgid "Posts for {month} {year}"
+msgstr "{year}年{month}月文章"