aboutsummaryrefslogtreecommitdiffstats
path: root/nikola
diff options
context:
space:
mode:
authorLibravatarAgustin Henze <tin@sluc.org.ar>2014-03-09 03:14:40 +0100
committerLibravatarAgustin Henze <tin@sluc.org.ar>2014-03-09 03:14:40 +0100
commitfa50632a9d87c3989566fed3e49c160a132e0d14 (patch)
tree81f58cc0dcfbb34710856b59c034bc47c53d91dc /nikola
parent2828399ba5cbb14502b023d4de1ba02f13dd5055 (diff)
Imported Upstream version 6.4.0upstream/6.4.0
Diffstat (limited to 'nikola')
-rw-r--r--nikola/__init__.py2
-rw-r--r--nikola/__main__.py14
-rw-r--r--nikola/conf.py.in55
-rw-r--r--nikola/data/themes/base/messages/messages_bg.py2
-rw-r--r--nikola/data/themes/base/messages/messages_ca.py2
-rw-r--r--nikola/data/themes/base/messages/messages_cs.py2
-rw-r--r--nikola/data/themes/base/messages/messages_de.py2
-rw-r--r--nikola/data/themes/base/messages/messages_el.py2
-rw-r--r--nikola/data/themes/base/messages/messages_en.py2
-rw-r--r--nikola/data/themes/base/messages/messages_eo.py2
-rw-r--r--nikola/data/themes/base/messages/messages_es.py2
-rw-r--r--nikola/data/themes/base/messages/messages_et.py2
-rw-r--r--nikola/data/themes/base/messages/messages_eu.py2
-rw-r--r--nikola/data/themes/base/messages/messages_fa.py2
-rw-r--r--nikola/data/themes/base/messages/messages_fi.py2
-rw-r--r--nikola/data/themes/base/messages/messages_fr.py2
-rw-r--r--nikola/data/themes/base/messages/messages_hi.py31
-rw-r--r--nikola/data/themes/base/messages/messages_hr.py2
-rw-r--r--nikola/data/themes/base/messages/messages_it.py2
-rw-r--r--nikola/data/themes/base/messages/messages_ja.py2
-rw-r--r--nikola/data/themes/base/messages/messages_nb.py2
-rw-r--r--nikola/data/themes/base/messages/messages_nl.py2
-rw-r--r--nikola/data/themes/base/messages/messages_pl.py2
-rw-r--r--nikola/data/themes/base/messages/messages_pt_br.py2
-rw-r--r--nikola/data/themes/base/messages/messages_ru.py2
-rw-r--r--nikola/data/themes/base/messages/messages_sl.py2
-rw-r--r--nikola/data/themes/base/messages/messages_tr_tr.py2
-rw-r--r--nikola/data/themes/base/messages/messages_ur.py4
-rw-r--r--nikola/data/themes/base/messages/messages_zh_cn.py2
-rw-r--r--nikola/data/themes/base/templates/base.tmpl5
-rw-r--r--nikola/data/themes/base/templates/base_helper.tmpl4
-rw-r--r--nikola/data/themes/base/templates/crumbs.tmpl2
-rw-r--r--nikola/data/themes/base/templates/gallery.tmpl4
-rw-r--r--[-rwxr-xr-x]nikola/data/themes/base/templates/index_helper.tmpl5
-rw-r--r--nikola/data/themes/base/templates/list.tmpl2
-rw-r--r--nikola/data/themes/base/templates/list_post.tmpl2
-rw-r--r--nikola/data/themes/base/templates/listing.tmpl2
-rw-r--r--[-rwxr-xr-x]nikola/data/themes/base/templates/post_helper.tmpl5
-rw-r--r--nikola/data/themes/base/templates/post_list_directive.tmpl2
-rw-r--r--nikola/data/themes/base/templates/tag.tmpl2
-rw-r--r--nikola/data/themes/bootstrap/templates/base.tmpl1
-rw-r--r--nikola/data/themes/bootstrap/templates/bootstrap_helper.tmpl4
-rw-r--r--nikola/data/themes/bootstrap/templates/gallery.tmpl4
-rw-r--r--nikola/nikola.py283
-rw-r--r--nikola/plugin_categories.py4
-rw-r--r--nikola/plugins/basic_import.py8
-rw-r--r--nikola/plugins/command/auto.py18
-rw-r--r--nikola/plugins/command/bootswatch_theme.py9
-rw-r--r--nikola/plugins/command/check.py56
-rw-r--r--nikola/plugins/command/console.py2
-rw-r--r--nikola/plugins/command/deploy.py29
-rw-r--r--nikola/plugins/command/import_blogger.py9
-rw-r--r--nikola/plugins/command/import_feed.py13
-rw-r--r--nikola/plugins/command/import_wordpress.py139
-rw-r--r--nikola/plugins/command/init.py110
-rw-r--r--nikola/plugins/command/install_plugin.py15
-rw-r--r--nikola/plugins/command/install_theme.py11
-rw-r--r--nikola/plugins/command/new_page.plugin9
-rw-r--r--nikola/plugins/command/new_page.py80
-rw-r--r--nikola/plugins/command/new_post.py61
-rw-r--r--nikola/plugins/command/planetoid/__init__.py14
-rw-r--r--nikola/plugins/command/serve.py19
-rw-r--r--nikola/plugins/compile/asciidoc.py11
-rw-r--r--nikola/plugins/compile/bbcode.py11
-rw-r--r--nikola/plugins/compile/html.py11
-rw-r--r--nikola/plugins/compile/ipynb/__init__.py11
-rw-r--r--nikola/plugins/compile/markdown/__init__.py11
-rw-r--r--nikola/plugins/compile/misaka.py11
-rw-r--r--nikola/plugins/compile/pandoc.py11
-rw-r--r--nikola/plugins/compile/php.py11
-rw-r--r--nikola/plugins/compile/rest/__init__.py43
-rw-r--r--nikola/plugins/compile/rest/listing.py19
-rw-r--r--nikola/plugins/compile/textile.py11
-rw-r--r--nikola/plugins/compile/txt2tags.py11
-rw-r--r--nikola/plugins/compile/wiki.py11
-rw-r--r--nikola/plugins/loghandler/stderr.py4
-rw-r--r--nikola/plugins/task/build_less.py16
-rw-r--r--nikola/plugins/task/build_sass.py19
-rw-r--r--nikola/plugins/task/bundles.py4
-rw-r--r--nikola/plugins/task/copy_assets.py4
-rw-r--r--nikola/plugins/task/galleries.py6
-rw-r--r--nikola/plugins/task/listings.py2
-rw-r--r--[-rwxr-xr-x]nikola/plugins/task/localsearch/files/assets/css/img/search.pngbin315 -> 315 bytes
-rw-r--r--[-rwxr-xr-x]nikola/plugins/task/localsearch/files/assets/css/tipuesearch.css0
-rw-r--r--[-rwxr-xr-x]nikola/plugins/task/localsearch/files/tipue_search.html0
-rw-r--r--nikola/plugins/task/rss.py5
-rw-r--r--nikola/plugins/task/sitemap/__init__.py19
-rw-r--r--nikola/plugins/task/tags.py14
-rw-r--r--nikola/plugins/template/jinja.py5
-rw-r--r--nikola/plugins/template/mako.py8
-rw-r--r--nikola/post.py5
-rw-r--r--nikola/utils.py81
92 files changed, 1033 insertions, 429 deletions
diff --git a/nikola/__init__.py b/nikola/__init__.py
index 0a82198..787ce8e 100644
--- a/nikola/__init__.py
+++ b/nikola/__init__.py
@@ -27,7 +27,7 @@
from __future__ import absolute_import
import os
-__version__ = "6.3.0"
+__version__ = "6.4.0"
DEBUG = bool(os.getenv('NIKOLA_DEBUG'))
from .nikola import Nikola # NOQA
diff --git a/nikola/__main__.py b/nikola/__main__.py
index 6b549b4..715f5b3 100644
--- a/nikola/__main__.py
+++ b/nikola/__main__.py
@@ -60,6 +60,16 @@ def main(args):
quiet = True
global config
+ colorful = False
+ if sys.stderr.isatty():
+ colorful = True
+ try:
+ import colorama
+ colorama.init()
+ except ImportError:
+ if os.name == 'nt':
+ colorful = False
+
root = get_root_dir()
if root:
os.chdir(root)
@@ -76,6 +86,8 @@ def main(args):
sys.exit(1)
config = {}
+ config.update({'__colorful__': colorful})
+
site = Nikola(**config)
return DoitNikola(site, quiet).run(args)
@@ -86,7 +98,7 @@ class Help(DoitHelp):
@staticmethod
def print_usage(cmds):
"""print nikola "usage" (basic help) instructions"""
- print("Nikola is a tool to create static websites and blogs. For full documentation and more information, please visit http://getnikola.com\n\n")
+ print("Nikola is a tool to create static websites and blogs. For full documentation and more information, please visit http://getnikola.com/\n\n")
print("Available commands:")
for cmd in sorted(cmds.values(), key=attrgetter('name')):
print(" nikola %-*s %s" % (20, cmd.name, cmd.doc_purpose))
diff --git a/nikola/conf.py.in b/nikola/conf.py.in
index 6ae0e1d..b398ac3 100644
--- a/nikola/conf.py.in
+++ b/nikola/conf.py.in
@@ -4,21 +4,21 @@
from __future__ import unicode_literals
import time
-##############################################
-# Configuration, please edit
-##############################################
+#!! This is the configuration of Nikola. !!#
+#!! You should edit it to your liking. !!#
+
# Data about this site
-BLOG_AUTHOR = "${BLOG_AUTHOR}"
-BLOG_TITLE = "${BLOG_TITLE}"
+BLOG_AUTHOR = ${BLOG_AUTHOR}
+BLOG_TITLE = ${BLOG_TITLE}
# This is the main URL for your site. It will be used
# in a prominent link
-SITE_URL = "${SITE_URL}"
+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}"
-BLOG_EMAIL = "${BLOG_EMAIL}"
-BLOG_DESCRIPTION = "${BLOG_DESCRIPTION}"
+# BASE_URL = ${SITE_URL}
+BLOG_EMAIL = ${BLOG_EMAIL}
+BLOG_DESCRIPTION = ${BLOG_DESCRIPTION}
# Nikola is multilingual!
#
@@ -36,6 +36,7 @@ BLOG_DESCRIPTION = "${BLOG_DESCRIPTION}"
# fa Persian
# fi Finnish
# fr French
+# hi Hindi
# hr Croatian
# it Italian
# ja Japanese [NOT jp!]
@@ -51,12 +52,12 @@ BLOG_DESCRIPTION = "${BLOG_DESCRIPTION}"
#
# If you want to use Nikola with a non-supported language you have to provide
# a module containing the necessary translations
-# (cf. the modules at nikola/data/themes/base/messages/fr.py).
+# (cf. the modules at nikola/data/themes/base/messages/).
# If a specific post is not translated to a language, then the version
# in the default language will be shown instead.
# What is the default language?
-DEFAULT_LANG = "${DEFAULT_LANG}"
+DEFAULT_LANG = ${DEFAULT_LANG}
# What other languages do you have?
# The format is {"translationcode" : "path/to/translation" }
@@ -75,7 +76,7 @@ TRANSLATIONS = {
# this pattern is also used for metadata:
# something.meta -> something.meta.pl
-TRANSLATIONS_PATTERN = "{path}.{ext}.{lang}"
+TRANSLATIONS_PATTERN = ${TRANSLATIONS_PATTERN}
# If you don't want your Polish files to be considered Perl code, use this:
# TRANSLATIONS_PATTERN = "{path}.{lang}.{ext}"
@@ -172,7 +173,8 @@ COMPILERS = ${COMPILERS}
# the posts themselves. If set to False, it will be just a list of links.
# TAG_PAGES_ARE_INDEXES = True
-# Final location is output / TRANSLATION[lang] / INDEX_PATH / index-*.html
+# Final location for the main blog page and sibling paginated pages is
+# output / TRANSLATION[lang] / INDEX_PATH / index-*.html
# INDEX_PATH = ""
# Create per-month archives instead of per-year
@@ -192,7 +194,7 @@ COMPILERS = ${COMPILERS}
# absolute: a complete URL (that includes the SITE_URL)
# URL_TYPE = 'rel_path'
-# Final locations are:
+# Final location for the blog main RSS feed is:
# output / TRANSLATION[lang] / RSS_PATH / rss.xml
# RSS_PATH = ""
@@ -270,9 +272,17 @@ COMPILERS = ${COMPILERS}
# Compiler to process LESS files.
# LESS_COMPILER = 'lessc'
+# A list of options to pass to the LESS compiler.
+# Final command is: LESS_COMPILER LESS_OPTIONS file.less
+# LESS_OPTIONS = []
+
# Compiler to process Sass files.
# SASS_COMPILER = 'sass'
+# A list of options to pass to the Sass compiler.
+# Final command is: SASS_COMPILER SASS_OPTIONS file.s(a|c)ss
+# SASS_OPTIONS = []
+
# #############################################################################
# Image Gallery Options
# #############################################################################
@@ -301,7 +311,7 @@ COMPILERS = ${COMPILERS}
# the main (the newest) index page (index.html)
# Name of the theme to use.
-THEME = "${THEME}"
+THEME = ${THEME}
# Color scheme to be used for code blocks. If your theme provides
# "assets/css/code.css" this is ignored.
@@ -354,7 +364,6 @@ LICENSE = ""
# src="http://i.creativecommons.org/l/by-nc-sa/2.5/ar/88x31.png"></a>"""
# A small copyright notice for the page footer (in HTML).
-# Default is ''
CONTENT_FOOTER = 'Contents &copy; {date} \
<a href="mailto:{email}">{author}</a> - Powered by \
<a href="http://getnikola.com" rel="nofollow">Nikola</a> \
@@ -367,12 +376,12 @@ CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL,
# To use comments, you can choose between different third party comment
# systems, one of "disqus", "livefyre", "intensedebate", "moot",
# "googleplus", "facebook" or "isso"
-# COMMENT_SYSTEM = "disqus"
+# COMMENT_SYSTEM = ${COMMENT_SYSTEM}
# And you also need to add your COMMENT_SYSTEM_ID which
# depends on what comment system you use. The default is
# "nikolademo" which is a test account for Disqus. More information
# is in the manual.
-# COMMENT_SYSTEM_ID = "nikolademo"
+# COMMENT_SYSTEM_ID = ${COMMENT_SYSTEM_ID}
# Enable annotations using annotateit.org?
# If set to False, you can still enable them for individual posts and pages
@@ -572,9 +581,9 @@ CONTENT_FOOTER = CONTENT_FOOTER.format(email=BLOG_EMAIL,
# USE_CDN = False
# Extra things you want in the pages HEAD tag. This will be added right
-# before </HEAD>
+# before </head>
# EXTRA_HEAD_DATA = ""
-# Google analytics or whatever else you use. Added to the bottom of <body>
+# Google Analytics or whatever else you use. Added to the bottom of <body>
# in the default template (base.tmpl).
# BODY_END = ""
@@ -680,7 +689,11 @@ LOGGING_HANDLERS = {
#}
}
+# Templates will use those filters, along with the defaults.
+# Consult your engine's documentation on filters if you need help defining
+# those.
+# TEMPLATE_FILTERS = {}
+
# Put in global_context things you want available on all your templates.
# It can be anything, data, functions, modules, etc.
-
GLOBAL_CONTEXT = {}
diff --git a/nikola/data/themes/base/messages/messages_bg.py b/nikola/data/themes/base/messages/messages_bg.py
index d4881b5..6e85212 100644
--- a/nikola/data/themes/base/messages/messages_bg.py
+++ b/nikola/data/themes/base/messages/messages_bg.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Още публикации относно",
"Newer posts": "Нови публикации",
"Next post": "Следваща публикация",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Стари публикации",
"Original site": "Оригиналния сайт",
"Posted": "Публиковано",
diff --git a/nikola/data/themes/base/messages/messages_ca.py b/nikola/data/themes/base/messages/messages_ca.py
index d3a97b5..220d571 100644
--- a/nikola/data/themes/base/messages/messages_ca.py
+++ b/nikola/data/themes/base/messages/messages_ca.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Més entrades sobre",
"Newer posts": "Entrades posteriors",
"Next post": "Entrada següent",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Entrades anteriors",
"Original site": "Lloc original",
"Posted": "Publicat",
diff --git a/nikola/data/themes/base/messages/messages_cs.py b/nikola/data/themes/base/messages/messages_cs.py
index 33482b5..f66c2c4 100644
--- a/nikola/data/themes/base/messages/messages_cs.py
+++ b/nikola/data/themes/base/messages/messages_cs.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Další příspěvky o",
"Newer posts": "Novější příspěvky",
"Next post": "Další příspěvek",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Starší příspěvky",
"Original site": "Původní stránka",
"Posted": "Zveřejněno",
diff --git a/nikola/data/themes/base/messages/messages_de.py b/nikola/data/themes/base/messages/messages_de.py
index 6795031..41fe015 100644
--- a/nikola/data/themes/base/messages/messages_de.py
+++ b/nikola/data/themes/base/messages/messages_de.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Weitere Einträge über",
"Newer posts": "Neuere Einträge",
"Next post": "Nächster Eintrag",
+ "No posts found.": "Keine einträge gefunden.",
+ "Nothing found.": "Nichts gefunden.",
"Older posts": "Ältere Einträge",
"Original site": "Original-Seite",
"Posted": "Veröffentlicht",
diff --git a/nikola/data/themes/base/messages/messages_el.py b/nikola/data/themes/base/messages/messages_el.py
index 710558b..f658fa0 100644
--- a/nikola/data/themes/base/messages/messages_el.py
+++ b/nikola/data/themes/base/messages/messages_el.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Περισσότερες αναρτήσεις για",
"Newer posts": "Νεότερες αναρτήσεις",
"Next post": "Επόμενη ανάρτηση",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Παλαιότερες αναρτήσεις",
"Original site": "Ιστοσελίδα αρχικής ανάρτησης",
"Posted": "Αναρτήθηκε",
diff --git a/nikola/data/themes/base/messages/messages_en.py b/nikola/data/themes/base/messages/messages_en.py
index 021f5e7..e2bff53 100644
--- a/nikola/data/themes/base/messages/messages_en.py
+++ b/nikola/data/themes/base/messages/messages_en.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "More posts about",
"Newer posts": "Newer posts",
"Next post": "Next post",
+ "No posts found.": "No posts found.",
+ "Nothing found.": "Nothing found.",
"Older posts": "Older posts",
"Original site": "Original site",
"Posted": "Posted",
diff --git a/nikola/data/themes/base/messages/messages_eo.py b/nikola/data/themes/base/messages/messages_eo.py
index fdbea88..f59a441 100644
--- a/nikola/data/themes/base/messages/messages_eo.py
+++ b/nikola/data/themes/base/messages/messages_eo.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Pli artikoloj pri",
"Newer posts": "Pli novaj artikoloj",
"Next post": "Venonta artikolo",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Pli malnovaj artikoloj",
"Original site": "Originala interretejo",
"Posted": "Skribita",
diff --git a/nikola/data/themes/base/messages/messages_es.py b/nikola/data/themes/base/messages/messages_es.py
index 6c48fb9..1923683 100644
--- a/nikola/data/themes/base/messages/messages_es.py
+++ b/nikola/data/themes/base/messages/messages_es.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Más posts sobre",
"Newer posts": "Posts posteriores",
"Next post": "Siguiente post",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Posts anteriores",
"Original site": "Sitio original",
"Posted": "Publicado",
diff --git a/nikola/data/themes/base/messages/messages_et.py b/nikola/data/themes/base/messages/messages_et.py
index 314f3b8..058ab5f 100644
--- a/nikola/data/themes/base/messages/messages_et.py
+++ b/nikola/data/themes/base/messages/messages_et.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Veel postitusi kohta",
"Newer posts": "Uued postitused",
"Next post": "Järgmine postitus",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Vanemad postitused",
"Original site": "Algallikas",
"Posted": "Postitatud",
diff --git a/nikola/data/themes/base/messages/messages_eu.py b/nikola/data/themes/base/messages/messages_eu.py
index 18d7575..a8eb743 100644
--- a/nikola/data/themes/base/messages/messages_eu.py
+++ b/nikola/data/themes/base/messages/messages_eu.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "-ri buruzko post gehiago",
"Newer posts": "Post berrienak",
"Next post": "Hurrengo posta",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Post zaharrenak",
"Original site": "Jatorrizko orria",
"Posted": "Argitaratuta",
diff --git a/nikola/data/themes/base/messages/messages_fa.py b/nikola/data/themes/base/messages/messages_fa.py
index bd278ca..4475e1b 100644
--- a/nikola/data/themes/base/messages/messages_fa.py
+++ b/nikola/data/themes/base/messages/messages_fa.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "ارسال‌های بیشتر دربارهٔ",
"Newer posts": "ارسال‌های جدید‌تر",
"Next post": "ارسال بعدی",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "پست‌های قدیمی‌تر",
"Original site": "سایت اصلی",
"Posted": "ارسال شده",
diff --git a/nikola/data/themes/base/messages/messages_fi.py b/nikola/data/themes/base/messages/messages_fi.py
index b24ee2c..42e6fa2 100644
--- a/nikola/data/themes/base/messages/messages_fi.py
+++ b/nikola/data/themes/base/messages/messages_fi.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Lisää postauksia aiheesta",
"Newer posts": "Uudempia postauksia",
"Next post": "Seuraava postaus",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Vanhempia postauksia",
"Original site": "Alkuperäinen sivusto",
"Posted": "Postattu",
diff --git a/nikola/data/themes/base/messages/messages_fr.py b/nikola/data/themes/base/messages/messages_fr.py
index ad4aea0..484d695 100644
--- a/nikola/data/themes/base/messages/messages_fr.py
+++ b/nikola/data/themes/base/messages/messages_fr.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Plus d'articles sur",
"Newer posts": "Billets récents",
"Next post": "Article suivant",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Anciens articles",
"Original site": "Site d'origine",
"Posted": "Publié",
diff --git a/nikola/data/themes/base/messages/messages_hi.py b/nikola/data/themes/base/messages/messages_hi.py
new file mode 100644
index 0000000..f72d5af
--- /dev/null
+++ b/nikola/data/themes/base/messages/messages_hi.py
@@ -0,0 +1,31 @@
+# -*- encoding:utf-8 -*-
+from __future__ import unicode_literals
+
+MESSAGES = {
+ "Also available in": "उपलब्ध भाषाएँ",
+ "Also available in:": "उपलब्ध भाषाएँ:",
+ "Archive": "आर्काइव",
+ "Categories": "श्रेणियाँ",
+ "LANGUAGE": "हिन्दी",
+ "More posts about %s": "%s के बारे में अौर पोस्टें",
+ "More posts about": " के बारे में अौर पोस्टें",
+ "Newer posts": "नई पोस्टें",
+ "Next post": "अगली पोस्ट",
+ "No posts found.": "",
+ "Nothing found.": "",
+ "Older posts": "पुरानी पोस्टें",
+ "Original site": "असली साइट",
+ "Posted": "पोस्टेड",
+ "Posted:": "पोस्टेड:",
+ "Posts about %s": "%s के बारे में पोस्टें",
+ "Posts for year %s": "साल %s की पोस्टें",
+ "Posts for {month} {year}": "{month} {year} की पोस्टें",
+ "Previous post": "पिछली पोस्ट",
+ "Read in English": "हिन्दी में पढ़िए",
+ "Read more": "और पढ़िए",
+ "Source": "सोर्स",
+ "Tags and Categories": "टैग्स और श्रेणियाँ",
+ "Tags": "टैग्स",
+ "old posts, page %d": "पुरानी पोस्टें, पृष्‍ठ %d",
+ "page %d": "पृष्‍ठ %d",
+}
diff --git a/nikola/data/themes/base/messages/messages_hr.py b/nikola/data/themes/base/messages/messages_hr.py
index ad74078..ee5ce41 100644
--- a/nikola/data/themes/base/messages/messages_hr.py
+++ b/nikola/data/themes/base/messages/messages_hr.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Više postova o",
"Newer posts": "Noviji postovi",
"Next post": "Sljedeći post",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Stariji postovi",
"Original site": "Izvorna stranica",
"Posted": "Objavljeno",
diff --git a/nikola/data/themes/base/messages/messages_it.py b/nikola/data/themes/base/messages/messages_it.py
index 912342e..87e25e5 100644
--- a/nikola/data/themes/base/messages/messages_it.py
+++ b/nikola/data/themes/base/messages/messages_it.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Altri articoli collegati",
"Newer posts": "Articoli recenti",
"Next post": "Articolo successivo",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Articoli precedenti",
"Original site": "Sito originale",
"Posted": "Pubblicato",
diff --git a/nikola/data/themes/base/messages/messages_ja.py b/nikola/data/themes/base/messages/messages_ja.py
index 1bdf168..2df16a4 100644
--- a/nikola/data/themes/base/messages/messages_ja.py
+++ b/nikola/data/themes/base/messages/messages_ja.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "タグ:",
"Newer posts": "新しい記事",
"Next post": "次の記事",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "過去の記事",
"Original site": "元のサイト",
"Posted": "投稿日時",
diff --git a/nikola/data/themes/base/messages/messages_nb.py b/nikola/data/themes/base/messages/messages_nb.py
index 154e329..44fde8a 100644
--- a/nikola/data/themes/base/messages/messages_nb.py
+++ b/nikola/data/themes/base/messages/messages_nb.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Flere innlegg om",
"Newer posts": "Nyere innlegg",
"Next post": "Neste innlegg",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Eldre innlegg",
"Original site": "Opprinnelig side",
"Posted": "Publisert",
diff --git a/nikola/data/themes/base/messages/messages_nl.py b/nikola/data/themes/base/messages/messages_nl.py
index 887e85f..1952d2e 100644
--- a/nikola/data/themes/base/messages/messages_nl.py
+++ b/nikola/data/themes/base/messages/messages_nl.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Meer berichten over",
"Newer posts": "Nieuwere berichten",
"Next post": "Volgend bericht",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Oudere berichten",
"Original site": "Originele site",
"Posted": "Geplaatst",
diff --git a/nikola/data/themes/base/messages/messages_pl.py b/nikola/data/themes/base/messages/messages_pl.py
index 352b0ed..a1183ba 100644
--- a/nikola/data/themes/base/messages/messages_pl.py
+++ b/nikola/data/themes/base/messages/messages_pl.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Więcej postów o",
"Newer posts": "Nowsze posty",
"Next post": "Następny post",
+ "No posts found.": "Nie znaleziono żadnych postów.",
+ "Nothing found.": "Nic nie znaleziono.",
"Older posts": "Starsze posty",
"Original site": "Oryginalna strona",
"Posted": "Opublikowano",
diff --git a/nikola/data/themes/base/messages/messages_pt_br.py b/nikola/data/themes/base/messages/messages_pt_br.py
index 1283a2a..bf515e4 100644
--- a/nikola/data/themes/base/messages/messages_pt_br.py
+++ b/nikola/data/themes/base/messages/messages_pt_br.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Mais posts sobre",
"Newer posts": "Posts mais recentes",
"Next post": "Próximo post",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Posts mais antigos",
"Original site": "Site original",
"Posted": "Publicado",
diff --git a/nikola/data/themes/base/messages/messages_ru.py b/nikola/data/themes/base/messages/messages_ru.py
index 3462292..fb33b85 100644
--- a/nikola/data/themes/base/messages/messages_ru.py
+++ b/nikola/data/themes/base/messages/messages_ru.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Больше записей о",
"Newer posts": "Новые записи",
"Next post": "Следующая запись",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Старые записи",
"Original site": "Оригинальный сайт",
"Posted": "Опубликовано",
diff --git a/nikola/data/themes/base/messages/messages_sl.py b/nikola/data/themes/base/messages/messages_sl.py
index 817bcee..92ad483 100644
--- a/nikola/data/themes/base/messages/messages_sl.py
+++ b/nikola/data/themes/base/messages/messages_sl.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "Več objav o",
"Newer posts": "Novejše objave",
"Next post": "Naslednja objava",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Starejše objave",
"Original site": "Izvorna spletna stran",
"Posted": "Objavljeno",
diff --git a/nikola/data/themes/base/messages/messages_tr_tr.py b/nikola/data/themes/base/messages/messages_tr_tr.py
index 633f057..95c5736 100644
--- a/nikola/data/themes/base/messages/messages_tr_tr.py
+++ b/nikola/data/themes/base/messages/messages_tr_tr.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": " ilgili diğer yazılar",
"Newer posts": "Daha yeni yazılar",
"Next post": "Sonraki yazı",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "Daha eski yazılar",
"Original site": "Orjinal web sayfası",
"Posted": "Yayın tarihi",
diff --git a/nikola/data/themes/base/messages/messages_ur.py b/nikola/data/themes/base/messages/messages_ur.py
index d9c2f2b..794861d 100644
--- a/nikola/data/themes/base/messages/messages_ur.py
+++ b/nikola/data/themes/base/messages/messages_ur.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": " کے بارے میں مزید تحاریر",
"Newer posts": "نئی تحاریر",
"Next post": "اگلی تحریر",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "پرانی تحاریر",
"Original site": "اصلی سائٹ",
"Posted": "اشاعت",
@@ -25,5 +27,5 @@ MESSAGES = {
"Tags and Categories": "ٹیگز اور زمرے",
"Tags": "ٹیگز",
"old posts, page %d": "پرانی تحاریر صفحہ %d",
- "page %d": "",
+ "page %d": "صفحہ %d",
}
diff --git a/nikola/data/themes/base/messages/messages_zh_cn.py b/nikola/data/themes/base/messages/messages_zh_cn.py
index cb9a2f7..2f937c7 100644
--- a/nikola/data/themes/base/messages/messages_zh_cn.py
+++ b/nikola/data/themes/base/messages/messages_zh_cn.py
@@ -11,6 +11,8 @@ MESSAGES = {
"More posts about": "更多相关文章:",
"Newer posts": "新一篇",
"Next post": "后一篇",
+ "No posts found.": "",
+ "Nothing found.": "",
"Older posts": "旧一篇",
"Original site": "原文地址",
"Posted": "发表于",
diff --git a/nikola/data/themes/base/templates/base.tmpl b/nikola/data/themes/base/templates/base.tmpl
index 7c6cc35..8a90349 100644
--- a/nikola/data/themes/base/templates/base.tmpl
+++ b/nikola/data/themes/base/templates/base.tmpl
@@ -30,11 +30,16 @@ lang="${lang}">
<small>${content_footer}</small>
<!--Sidebar content-->
<ul class="unstyled">
+ %if license:
<li>${license}
+ %endif
${base.html_social()}
${base.html_navigation_links()}
+ %if search_form:
<li>${search_form}
+ %endif
</ul>
${base.late_load_js()}
${social_buttons_code}
</body>
+</html>
diff --git a/nikola/data/themes/base/templates/base_helper.tmpl b/nikola/data/themes/base/templates/base_helper.tmpl
index 880a998..501c06e 100644
--- a/nikola/data/themes/base/templates/base_helper.tmpl
+++ b/nikola/data/themes/base/templates/base_helper.tmpl
@@ -21,7 +21,9 @@
<link href="/assets/css/custom.css" rel="stylesheet" type="text/css">
%endif
%endif
- <link rel="canonical" href="${abs_link(permalink)}">
+ %if permalink:
+ <link rel="canonical" href="${abs_link(permalink)}">
+ %endif
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
<![endif]-->
diff --git a/nikola/data/themes/base/templates/crumbs.tmpl b/nikola/data/themes/base/templates/crumbs.tmpl
index c458cbe..8fbafcf 100644
--- a/nikola/data/themes/base/templates/crumbs.tmpl
+++ b/nikola/data/themes/base/templates/crumbs.tmpl
@@ -1,9 +1,11 @@
## -*- coding: utf-8 -*-
<%def name="bar(crumbs)">
+%if crumbs:
<ul class="breadcrumb">
% for link, text in crumbs:
<li><a href="${link}">${text}</a></li>
% endfor
</ul>
+%endif
</%def>
diff --git a/nikola/data/themes/base/templates/gallery.tmpl b/nikola/data/themes/base/templates/gallery.tmpl
index e4eab27..731a75a 100644
--- a/nikola/data/themes/base/templates/gallery.tmpl
+++ b/nikola/data/themes/base/templates/gallery.tmpl
@@ -14,18 +14,22 @@
${text}
</p>
%endif
+ %if folders:
<ul>
% for folder, ftitle in folders:
<li><a href="${folder}"><i
class="icon-folder-open"></i>&nbsp;${ftitle}</a></li>
% endfor
</ul>
+ %endif
+ %if photo_array:
<ul class="thumbnails">
%for image in photo_array:
<li><a href="${image['url']}" class="thumbnail image-reference" title="${image['title']}">
<img src="${image['url_thumb']}" alt="${image['title']}" /></a>
%endfor
</ul>
+ %endif
%if enable_comments:
${comments.comment_form(None, permalink, title)}
%endif
diff --git a/nikola/data/themes/base/templates/index_helper.tmpl b/nikola/data/themes/base/templates/index_helper.tmpl
index 56f5127..c925559 100755..100644
--- a/nikola/data/themes/base/templates/index_helper.tmpl
+++ b/nikola/data/themes/base/templates/index_helper.tmpl
@@ -20,6 +20,11 @@
<%def name="mathjax_script(posts)">
%if any(post.is_mathjax for post in posts):
+ <script type="text/x-mathjax-config">
+ MathJax.Hub.Config({
+ tex2jax: {inlineMath: [['$latex ','$'], ['\\(','\\)']]}
+ });
+ </script>
<script src="/assets/js/mathjax.js"></script>
%endif
</%def>
diff --git a/nikola/data/themes/base/templates/list.tmpl b/nikola/data/themes/base/templates/list.tmpl
index a60b508..4136eb9 100644
--- a/nikola/data/themes/base/templates/list.tmpl
+++ b/nikola/data/themes/base/templates/list.tmpl
@@ -4,11 +4,13 @@
<!--Body content-->
<div class="postbox">
<h1>${title}</h1>
+ %if items:
<ul class="unstyled">
% for text, link in items:
<li><a href="${link}">${text}</a>
% endfor
</ul>
+ %endif
</div>
<!--End of body content-->
</%block>
diff --git a/nikola/data/themes/base/templates/list_post.tmpl b/nikola/data/themes/base/templates/list_post.tmpl
index f0e159d..b27f230 100644
--- a/nikola/data/themes/base/templates/list_post.tmpl
+++ b/nikola/data/themes/base/templates/list_post.tmpl
@@ -4,11 +4,13 @@
<!--Body content-->
<div class="postbox">
<h1>${title}</h1>
+ %if posts:
<ul class="unstyled">
% for post in posts:
<li><a href="${post.permalink()}">[${post.formatted_date(date_format)}] ${post.title()}</a>
% endfor
</ul>
+ %endif
</div>
<!--End of body content-->
</%block>
diff --git a/nikola/data/themes/base/templates/listing.tmpl b/nikola/data/themes/base/templates/listing.tmpl
index b6ca83f..0662360 100644
--- a/nikola/data/themes/base/templates/listing.tmpl
+++ b/nikola/data/themes/base/templates/listing.tmpl
@@ -3,6 +3,7 @@
<%namespace name="ui" file="crumbs.tmpl" import="bar"/>
<%block name="content">
${ui.bar(crumbs)}
+%if folders or files:
<ul class="unstyled">
% for name in folders:
<li><a href="${name}"><i class="icon-folder-open"></i> ${name}</a>
@@ -11,6 +12,7 @@ ${ui.bar(crumbs)}
<li><a href="${name}.html"><i class="icon-file"></i> ${name}</a>
% endfor
</ul>
+%endif
% if code:
${code}
% endif
diff --git a/nikola/data/themes/base/templates/post_helper.tmpl b/nikola/data/themes/base/templates/post_helper.tmpl
index 69784ea..391350d 100755..100644
--- a/nikola/data/themes/base/templates/post_helper.tmpl
+++ b/nikola/data/themes/base/templates/post_helper.tmpl
@@ -85,6 +85,11 @@
<%def name="mathjax_script(post)">
%if post.is_mathjax:
+ <script type="text/x-mathjax-config">
+ MathJax.Hub.Config({
+ tex2jax: {inlineMath: [['$latex ','$'], ['\\(','\\)']]}
+ });
+ </script>
<script src="/assets/js/mathjax.js"></script>
%endif
</%def>
diff --git a/nikola/data/themes/base/templates/post_list_directive.tmpl b/nikola/data/themes/base/templates/post_list_directive.tmpl
index 3345ae4..b31d242 100644
--- a/nikola/data/themes/base/templates/post_list_directive.tmpl
+++ b/nikola/data/themes/base/templates/post_list_directive.tmpl
@@ -1,6 +1,7 @@
## -*- coding: utf-8 -*-
<!-- Begin post-list ${post_list_id} -->
<div id="${post_list_id}" class="post-list">
+ %if posts:
<ul class="post-list">
% for post in posts:
<li class="post-list-item">
@@ -10,5 +11,6 @@
</li>
% endfor
</ul>
+ %endif
</div>
<!-- End post-list ${post_list_id} -->
diff --git a/nikola/data/themes/base/templates/tag.tmpl b/nikola/data/themes/base/templates/tag.tmpl
index 2ca9db4..43afd54 100644
--- a/nikola/data/themes/base/templates/tag.tmpl
+++ b/nikola/data/themes/base/templates/tag.tmpl
@@ -22,11 +22,13 @@
<a href="${_link(kind + "_rss", tag)}">RSS</a>
%endif
<br>
+ %if posts:
<ul class="unstyled">
% for post in posts:
<li><a href="${post.permalink()}">[${post.formatted_date(date_format)}] ${post.title()}</a>
% endfor
</ul>
+ %endif
</div>
<!--End of body content-->
</%block>
diff --git a/nikola/data/themes/bootstrap/templates/base.tmpl b/nikola/data/themes/bootstrap/templates/base.tmpl
index 8cb2e43..65132b7 100644
--- a/nikola/data/themes/bootstrap/templates/base.tmpl
+++ b/nikola/data/themes/bootstrap/templates/base.tmpl
@@ -91,3 +91,4 @@ ${base.html_social()}
% endif
${body_end}
</body>
+</html>
diff --git a/nikola/data/themes/bootstrap/templates/bootstrap_helper.tmpl b/nikola/data/themes/bootstrap/templates/bootstrap_helper.tmpl
index f0d1986..c041e50 100644
--- a/nikola/data/themes/bootstrap/templates/bootstrap_helper.tmpl
+++ b/nikola/data/themes/bootstrap/templates/bootstrap_helper.tmpl
@@ -29,7 +29,9 @@
<link href="/assets/css/custom.css" rel="stylesheet" type="text/css">
%endif
%endif
- <link rel="canonical" href="${abs_link(permalink)}">
+ %if permalink:
+ <link rel="canonical" href="${abs_link(permalink)}">
+ %endif
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
<![endif]-->
diff --git a/nikola/data/themes/bootstrap/templates/gallery.tmpl b/nikola/data/themes/bootstrap/templates/gallery.tmpl
index 0dd5eea..7b0d505 100644
--- a/nikola/data/themes/bootstrap/templates/gallery.tmpl
+++ b/nikola/data/themes/bootstrap/templates/gallery.tmpl
@@ -14,14 +14,17 @@
${text}
</p>
%endif
+ %if folders:
<ul>
% for folder, ftitle in folders:
<li><a href="${folder}"><i
class="icon-folder-open"></i>&nbsp;${ftitle}</a></li>
% endfor
</ul>
+ %endif
<div id="gallery_container"></div>
+ %if photo_array:
<noscript>
<ul class="thumbnails">
%for image in photo_array:
@@ -30,6 +33,7 @@
%endfor
</ul>
</noscript>
+ %endif
%if enable_comments:
${comments.comment_form(None, permalink, title)}
%endif
diff --git a/nikola/nikola.py b/nikola/nikola.py
index 6971c0c..1d59954 100644
--- a/nikola/nikola.py
+++ b/nikola/nikola.py
@@ -25,8 +25,10 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function, unicode_literals
+import codecs
from collections import defaultdict
from copy import copy
+import datetime
import glob
import locale
import os
@@ -41,6 +43,7 @@ try:
import pyphen
except ImportError:
pyphen = None
+import pytz
import logging
from . import DEBUG
@@ -66,11 +69,15 @@ from .plugin_categories import (
SignalHandler,
)
+from .utils import ColorfulStderrHandler
config_changed = utils.config_changed
__all__ = ['Nikola']
+# Default pattern for translation files' names
+DEFAULT_TRANSLATIONS_PATTERN = '{path}.{ext}.{lang}'
+
class Nikola(object):
@@ -92,6 +99,7 @@ class Nikola(object):
self.path_handlers = {
'slug': self.slug_path,
'post_path': self.post_path,
+ 'filename': self.filename_path,
}
self.strict = False
@@ -111,8 +119,15 @@ class Nikola(object):
self.loghandlers = []
if not config:
self.configured = False
+ self.colorful = False
else:
self.configured = True
+ self.colorful = config.pop('__colorful__', False)
+
+ ColorfulStderrHandler._colorful = self.colorful
+
+ # Maintain API
+ utils.generic_rss_renderer = self.generic_rss_renderer
# This is the default config
self.config = {
@@ -173,6 +188,7 @@ class Nikola(object):
'INDEX_PATH': '',
'IPYNB_CONFIG': {},
'LESS_COMPILER': 'lessc',
+ 'LESS_OPTIONS': [],
'LICENSE': '',
'LINK_CHECK_WHITELIST': [],
'LISTINGS_FOLDER': 'listings',
@@ -192,6 +208,7 @@ class Nikola(object):
'RSS_PATH': '',
'RSS_TEASERS': True,
'SASS_COMPILER': 'sass',
+ 'SASS_OPTIONS': [],
'SEARCH_FORM': '',
'SLUG_TAG_PATH': True,
'SOCIAL_BUTTONS_CODE': SOCIAL_BUTTONS_CODE,
@@ -201,6 +218,7 @@ class Nikola(object):
'SITEMAP_INCLUDE_FILELESS_DIRS': True,
'TAG_PATH': 'categories',
'TAG_PAGES_ARE_INDEXES': False,
+ 'TEMPLATE_FILTERS': {},
'THEME': 'bootstrap',
'THEME_REVEAL_CONFIG_SUBTHEME': 'sky',
'THEME_REVEAL_CONFIG_TRANSITION': 'cube',
@@ -217,7 +235,7 @@ class Nikola(object):
'SCHEDULE_FORCE_TODAY': False,
'LOGGING_HANDLERS': {'stderr': {'loglevel': 'WARNING', 'bubble': True}},
'DEMOTE_HEADERS': 1,
- 'TRANSLATIONS_PATTERN': '{path}.{ext}.{lang}',
+ 'TRANSLATIONS_PATTERN': DEFAULT_TRANSLATIONS_PATTERN,
}
self.config.update(config)
@@ -302,10 +320,10 @@ class Nikola(object):
self.config['STRIP_INDEXES'] = config['STRIP_INDEX_HTML']
# PRETTY_URLS defaults to enabling STRIP_INDEXES unless explicitly disabled
- if config.get('PRETTY_URLS', False) and 'STRIP_INDEXES' not in config:
+ if self.config.get('PRETTY_URLS') and 'STRIP_INDEXES' not in config:
self.config['STRIP_INDEXES'] = True
- if config.get('COPY_SOURCES') and not self.config['HIDE_SOURCELINK']:
+ if not self.config.get('COPY_SOURCES'):
self.config['HIDE_SOURCELINK'] = True
self.config['TRANSLATIONS'] = self.config.get('TRANSLATIONS',
@@ -408,6 +426,13 @@ class Nikola(object):
self.plugin_manager.activatePluginByName(plugin_info.name)
plugin_info.plugin_object.set_site(self)
+ # Also add aliases for combinations with TRANSLATIONS_PATTERN
+ self.config['COMPILERS'] = dict([(lang, list(exts) + [
+ utils.get_translation_candidate(self.config, "f" + ext, lang)[1:]
+ for ext in exts
+ for lang in self.config['TRANSLATIONS'].keys()])
+ for lang, exts in list(self.config['COMPILERS'].items())])
+
# Activate all required compiler plugins
for plugin_info in self.plugin_manager.getPluginsOfCategory("PageCompiler"):
if plugin_info.name in self.config["COMPILERS"].keys():
@@ -462,6 +487,11 @@ class Nikola(object):
self._GLOBAL_CONTEXT['navigation_links'] = utils.Functionary(list, self.config['DEFAULT_LANG'])
for k, v in self.config.get('NAVIGATION_LINKS', {}).items():
self._GLOBAL_CONTEXT['navigation_links'][k] = v
+
+ # avoid #1082 by making sure all keys in navigation_links are read once
+ for k in self._GLOBAL_CONTEXT['translations']:
+ self._GLOBAL_CONTEXT['navigation_links'][k]
+
# TODO: remove on v7
# Compatibility alias
self._GLOBAL_CONTEXT['sidebar_links'] = self._GLOBAL_CONTEXT['navigation_links']
@@ -498,7 +528,7 @@ class Nikola(object):
self.config['THEME'] = theme_replacements[self.config['THEME']]
if self.config['THEME'] == 'oldfashioned':
utils.LOGGER.warn('''You may need to install the "oldfashioned" theme '''
- '''from themes.nikola.ralsina.com.ar because it's not '''
+ '''from themes.getnikola.com because it's not '''
'''shipped by default anymore.''')
utils.LOGGER.warn('Please change your THEME setting.')
try:
@@ -560,6 +590,7 @@ class Nikola(object):
for name in self.THEMES]
self._template_system.set_directories(lookup_dirs,
self.config['CACHE_FOLDER'])
+ self._template_system.set_site(self)
return self._template_system
template_system = property(_get_template_system)
@@ -576,9 +607,9 @@ class Nikola(object):
compile_html = self.inverse_compilers[ext]
except KeyError:
# Find the correct compiler for this files extension
- langs = [lang for lang, exts in
- list(self.config['COMPILERS'].items())
- if ext in exts]
+ lang_exts_tab = list(self.config['COMPILERS'].items())
+ langs = [lang for lang, exts in lang_exts_tab if ext in exts or
+ len([ext_ for ext_ in exts if source_name.endswith(ext_)]) > 0]
if len(langs) != 1:
if len(set(langs)) > 1:
exit("Your file extension->compiler definition is"
@@ -618,78 +649,158 @@ class Nikola(object):
# The os.sep is because normpath will change "/" to "\" on windows
src = "/".join(src.split(os.sep))
- parsed_src = urlsplit(src)
- src_elems = parsed_src.path.split('/')[1:]
+ utils.makedirs(os.path.dirname(output_name))
+ doc = lxml.html.document_fromstring(data)
+ doc.rewrite_links(lambda dst: self.url_replacer(src, dst, context['lang']))
+ data = b'<!DOCTYPE html>' + lxml.html.tostring(doc, encoding='utf8')
+ with open(output_name, "wb+") as post_file:
+ post_file.write(data)
- def replacer(dst):
- # Refuse to replace links that are full URLs.
- dst_url = urlparse(dst)
- if dst_url.netloc:
- if dst_url.scheme == 'link': # Magic link
- dst = self.link(dst_url.netloc, dst_url.path.lstrip('/'),
- context['lang'])
- else:
- return dst
+ def url_replacer(self, src, dst, lang=None):
+ """URL mangler.
- # Refuse to replace links that consist of a fragment only
- if ((not dst_url.scheme) and (not dst_url.netloc) and
- (not dst_url.path) and (not dst_url.params) and
- (not dst_url.query) and dst_url.fragment):
- return dst
+ * Replaces link:// URLs with real links
+ * Makes dst relative to src
+ * Leaves fragments unchanged
+ * Leaves full URLs unchanged
+ * Avoids empty links
- # Normalize
- dst = urljoin(src, dst)
+ src is the URL where this link is used
+ dst is the link to be mangled
+ lang is used for language-sensitive URLs in link://
- # Avoid empty links.
- if src == dst:
- if self.config.get('URL_TYPE') == 'absolute':
- dst = urljoin(self.config['BASE_URL'], dst)
- return dst
- elif self.config.get('URL_TYPE') == 'full_path':
- return dst
- else:
- return "#"
+ """
+ parsed_src = urlsplit(src)
+ src_elems = parsed_src.path.split('/')[1:]
+ dst_url = urlparse(dst)
+ if lang is None:
+ lang = self.default_lang
- # Check that link can be made relative, otherwise return dest
- parsed_dst = urlsplit(dst)
- if parsed_src[:2] != parsed_dst[:2]:
- if self.config.get('URL_TYPE') == 'absolute':
- dst = urljoin(self.config['BASE_URL'], dst)
+ # Refuse to replace links that are full URLs.
+ if dst_url.netloc:
+ if dst_url.scheme == 'link': # Magic link
+ dst = self.link(dst_url.netloc, dst_url.path.lstrip('/'), lang)
+ else:
return dst
+ elif dst_url.scheme == 'link': # Magic absolute path link:
+ dst = dst_url.path
+ return dst
- if self.config.get('URL_TYPE') in ('full_path', 'absolute'):
- if self.config.get('URL_TYPE') == 'absolute':
- dst = urljoin(self.config['BASE_URL'], dst)
- return dst
+ # Refuse to replace links that consist of a fragment only
+ if ((not dst_url.scheme) and (not dst_url.netloc) and
+ (not dst_url.path) and (not dst_url.params) and
+ (not dst_url.query) and dst_url.fragment):
+ return dst
- # Now both paths are on the same site and absolute
- dst_elems = parsed_dst.path.split('/')[1:]
+ # Normalize
+ dst = urljoin(src, dst)
- i = 0
- for (i, s), d in zip(enumerate(src_elems), dst_elems):
- if s != d:
- break
- # Now i is the longest common prefix
- result = '/'.join(['..'] * (len(src_elems) - i - 1) +
- dst_elems[i:])
+ # Avoid empty links.
+ if src == dst:
+ if self.config.get('URL_TYPE') == 'absolute':
+ dst = urljoin(self.config['BASE_URL'], dst.lstrip('/'))
+ return dst
+ elif self.config.get('URL_TYPE') == 'full_path':
+ dst = urljoin(self.config['BASE_URL'], dst.lstrip('/'))
+ return urlparse(dst).path
+ else:
+ return "#"
- if not result:
- result = "."
+ # Check that link can be made relative, otherwise return dest
+ parsed_dst = urlsplit(dst)
+ if parsed_src[:2] != parsed_dst[:2]:
+ if self.config.get('URL_TYPE') == 'absolute':
+ dst = urljoin(self.config['BASE_URL'], dst)
+ return dst
- # Don't forget the fragment (anchor) part of the link
- if parsed_dst.fragment:
- result += "#" + parsed_dst.fragment
+ if self.config.get('URL_TYPE') in ('full_path', 'absolute'):
+ dst = urljoin(self.config['BASE_URL'], dst.lstrip('/'))
+ if self.config.get('URL_TYPE') == 'full_path':
+ parsed = urlparse(urljoin(self.config['BASE_URL'], dst.lstrip('/')))
+ if parsed.fragment:
+ dst = '{0}#{1}'.format(parsed.path, parsed.fragment)
+ else:
+ dst = parsed.path
+ return dst
- assert result, (src, dst, i, src_elems, dst_elems)
+ # Now both paths are on the same site and absolute
+ dst_elems = parsed_dst.path.split('/')[1:]
- return result
+ i = 0
+ for (i, s), d in zip(enumerate(src_elems), dst_elems):
+ if s != d:
+ break
+ # Now i is the longest common prefix
+ result = '/'.join(['..'] * (len(src_elems) - i - 1) + dst_elems[i:])
+
+ if not result:
+ result = "."
+
+ # Don't forget the fragment (anchor) part of the link
+ if parsed_dst.fragment:
+ result += "#" + parsed_dst.fragment
+
+ assert result, (src, dst, i, src_elems, dst_elems)
+
+ return result
+
+ def generic_rss_renderer(self, lang, title, link, description, timeline, output_path,
+ rss_teasers, feed_length=10, feed_url=None):
+ """Takes all necessary data, and renders a RSS feed in output_path."""
+ items = []
+ for post in timeline[:feed_length]:
+ # Massage the post's HTML
+ data = post.text(lang, teaser_only=rss_teasers, really_absolute=True)
+ if feed_url is not None and data:
+ # FIXME: this is duplicated with code in Post.text()
+ try:
+ doc = lxml.html.document_fromstring(data)
+ doc.rewrite_links(lambda dst: self.url_replacer(feed_url, dst, lang))
+ try:
+ body = doc.body
+ data = (body.text or '') + ''.join(
+ [lxml.html.tostring(child, encoding='unicode')
+ for child in body.iterchildren()])
+ except IndexError: # No body there, it happens sometimes
+ data = ''
+ except lxml.etree.ParserError as e:
+ if str(e) == "Document is empty":
+ data = ""
+ else: # let other errors raise
+ raise(e)
+
+ args = {
+ 'title': post.title(lang),
+ 'link': post.permalink(lang, absolute=True),
+ 'description': data,
+ 'guid': post.permalink(lang, absolute=True),
+ # PyRSS2Gen's pubDate is GMT time.
+ 'pubDate': (post.date if post.date.tzinfo is None else
+ post.date.astimezone(pytz.timezone('UTC'))),
+ 'categories': post._tags.get(lang, []),
+ 'author': post.meta('author'),
+ }
- utils.makedirs(os.path.dirname(output_name))
- doc = lxml.html.document_fromstring(data)
- doc.rewrite_links(replacer)
- data = b'<!DOCTYPE html>' + lxml.html.tostring(doc, encoding='utf8')
- with open(output_name, "wb+") as post_file:
- post_file.write(data)
+ items.append(utils.ExtendedItem(**args))
+ rss_obj = utils.ExtendedRSS2(
+ title=title,
+ link=link,
+ description=description,
+ lastBuildDate=datetime.datetime.now(),
+ items=items,
+ generator='Nikola <http://getnikola.com/>',
+ language=lang
+ )
+ rss_obj.self_url = feed_url
+ rss_obj.rss_attrs["xmlns:atom"] = "http://www.w3.org/2005/Atom"
+ rss_obj.rss_attrs["xmlns:dc"] = "http://purl.org/dc/elements/1.1/"
+ dst_dir = os.path.dirname(output_path)
+ utils.makedirs(dst_dir)
+ with codecs.open(output_path, "wb+", "utf-8") as rss_file:
+ data = rss_obj.to_xml(encoding='utf-8')
+ if isinstance(data, utils.bytes_str):
+ data = data.decode('utf-8')
+ rss_file.write(data)
def path(self, kind, name, lang=None, is_link=False):
"""Build the path to a certain kind of page.
@@ -712,6 +823,7 @@ class Nikola(object):
* listing (name is the source code file name)
* post_path (name is 1st element in a POSTS/PAGES tuple)
* slug (name is the slug of a post or story)
+ * filename (name is the source filename of a post/story, in DEFAULT_LANG, relative to conf.py)
The returned value is always a path relative to output, like
"categories/whatever.html"
@@ -727,6 +839,7 @@ class Nikola(object):
lang = utils.LocaleBorg().current_lang
path = self.path_handlers[kind](name, lang)
+ path = [os.path.normpath(p) for p in path if p != '.'] # Fix Issue #1028
if is_link:
link = '/' + ('/'.join(path))
@@ -755,6 +868,16 @@ class Nikola(object):
utils.LOGGER.warning('Ambiguous path request for slug: {0}'.format(name))
return [_f for _f in results[0].permalink(lang).split('/') if _f]
+ def filename_path(self, name, lang):
+ """filename path handler"""
+ results = [p for p in self.timeline if p.source_path == name]
+ if not results:
+ utils.LOGGER.warning("Can't resolve path request for filename: {0}".format(name))
+ else:
+ if len(results) > 1:
+ utils.LOGGER.error("Ambiguous path request for filename: {0}".format(name))
+ return [_f for _f in results[0].permalink(lang).split('/') if _f]
+
def register_path_handler(self, kind, f):
if kind in self.path_handlers:
utils.LOGGER.warning('Conflicting path handlers for kind: {0}'.format(kind))
@@ -766,8 +889,10 @@ class Nikola(object):
def abs_link(self, dst):
# Normalize
- dst = urljoin(self.config['BASE_URL'], dst)
-
+ if dst: # Mako templates and empty strings evaluate to False
+ dst = urljoin(self.config['BASE_URL'], dst.lstrip('/'))
+ else:
+ dst = self.config['BASE_URL']
return urlparse(dst).geturl()
def rel_link(self, src, dst):
@@ -848,7 +973,8 @@ class Nikola(object):
return
seen = set([])
print("Scanning posts", end='', file=sys.stderr)
- lower_case_tags = set([])
+ slugged_tags = set([])
+ quit = False
for wildcard, destination, template_name, use_in_feeds in \
self.config['post_pages']:
print(".", end='', file=sys.stderr)
@@ -864,11 +990,16 @@ class Nikola(object):
translated_list = glob.glob(lang_glob)
# dir_glob could have put it already in full_list
full_list = list(set(full_list + translated_list))
- # Eliminate translations from full_list (even from dir_glob)
- for fname in full_list:
+
+ # Eliminate translations from full_list if they are not the primary,
+ # or a secondary with no primary
+ limited_list = full_list[:]
+ for fname in full_list:
+ for lang in self.config['TRANSLATIONS'].keys():
translation = utils.get_translation_candidate(self.config, fname, lang)
if translation in full_list:
- full_list.remove(translation)
+ limited_list.remove(translation)
+ full_list = limited_list
# We eliminate from the list the files inside any .ipynb folder
full_list = [p for p in full_list
@@ -897,16 +1028,16 @@ class Nikola(object):
self.posts_per_month[
'{0}/{1:02d}'.format(post.date.year, post.date.month)].append(post.source_path)
for tag in post.alltags:
- if tag.lower() in lower_case_tags:
+ if utils.slugify(tag) in slugged_tags:
if tag not in self.posts_per_tag:
# Tags that differ only in case
other_tag = [k for k in self.posts_per_tag.keys() if k.lower() == tag.lower()][0]
- utils.LOGGER.error('You have cases that differ only in upper/lower case: {0} and {1}'.format(tag, other_tag))
+ utils.LOGGER.error('You have tags that are too similar: {0} and {1}'.format(tag, other_tag))
utils.LOGGER.error('Tag {0} is used in: {1}'.format(tag, post.source_path))
utils.LOGGER.error('Tag {0} is used in: {1}'.format(other_tag, ', '.join(self.posts_per_tag[other_tag])))
- sys.exit(1)
+ quit = True
else:
- lower_case_tags.add(tag.lower())
+ slugged_tags.add(utils.slugify(tag))
self.posts_per_tag[tag].append(post.source_path)
self.posts_per_category[post.meta('category')].append(post.source_path)
else:
@@ -925,6 +1056,8 @@ class Nikola(object):
p.prev_post = post_timeline[i + 1]
self._scanned = True
print("done!", file=sys.stderr)
+ if quit:
+ sys.exit(1)
def generic_page_renderer(self, lang, post, filters):
"""Render post fragments to final HTML pages."""
diff --git a/nikola/plugin_categories.py b/nikola/plugin_categories.py
index 68327aa..516df92 100644
--- a/nikola/plugin_categories.py
+++ b/nikola/plugin_categories.py
@@ -151,6 +151,10 @@ class TemplateSystem(BasePlugin):
"""Sets the list of folders where templates are located and cache."""
raise NotImplementedError()
+ def set_site(self, site):
+ """Sets the site."""
+ self.site = site
+
def template_deps(self, template_name):
"""Returns filenames which are dependencies for a template."""
raise NotImplementedError()
diff --git a/nikola/plugins/basic_import.py b/nikola/plugins/basic_import.py
index 0d94d16..27c0eb4 100644
--- a/nikola/plugins/basic_import.py
+++ b/nikola/plugins/basic_import.py
@@ -48,8 +48,8 @@ class ImportMixin(object):
name = "import_mixin"
needs_config = False
- doc_usage = "[options] wordpress_export_file"
- doc_purpose = "import a wordpress dump."
+ doc_usage = "[options] export_file"
+ doc_purpose = "import a dump from a different engine."
cmd_options = [
{
'name': 'output_folder',
@@ -93,7 +93,7 @@ class ImportMixin(object):
else:
self.import_into_existing_site = True
utils.LOGGER.notice('The folder {0} already exists - assuming that this is a '
- 'already existing nikola site.'.format(self.output_folder))
+ 'already existing Nikola site.'.format(self.output_folder))
filename = os.path.join(os.path.dirname(utils.__file__), 'conf.py.in')
# The 'strict_undefined=True' will give the missing symbol name if any,
@@ -151,7 +151,7 @@ class ImportMixin(object):
time=datetime.datetime.now().strftime('%Y%m%d_%H%M%S'),
name=self.name)
config_output_path = os.path.join(self.output_folder, filename)
- utils.LOGGER.notice('Configuration will be written to: {0}'.format(config_output_path))
+ utils.LOGGER.info('Configuration will be written to: {0}'.format(config_output_path))
return config_output_path
diff --git a/nikola/plugins/command/auto.py b/nikola/plugins/command/auto.py
index 01116d1..d707d53 100644
--- a/nikola/plugins/command/auto.py
+++ b/nikola/plugins/command/auto.py
@@ -34,7 +34,7 @@ from nikola.plugin_categories import Command
from nikola.utils import req_missing
-class Auto(Command):
+class CommandAuto(Command):
"""Start debugging console."""
name = "auto"
doc_purpose = "automatically detect site changes, rebuild and optionally refresh a browser"
@@ -61,26 +61,26 @@ class Auto(Command):
try:
from livereload import Server
except ImportError:
- req_missing(['livereload>=2.0.0'], 'use the "auto" command')
+ req_missing(['livereload==2.1.0'], 'use the "auto" command')
return
- # Run an initial build so we are uptodate
+ # Run an initial build so we are up-to-date
subprocess.call(("nikola", "build"))
port = options and options.get('port')
server = Server()
- server.watch('conf.py')
- server.watch('themes/')
- server.watch('templates/')
+ server.watch('conf.py', 'nikola build')
+ server.watch('themes/', 'nikola build')
+ server.watch('templates/', 'nikola build')
server.watch(self.site.config['GALLERY_PATH'])
for item in self.site.config['post_pages']:
- server.watch(os.path.dirname(item[0]))
+ server.watch(os.path.dirname(item[0]), 'nikola build')
for item in self.site.config['FILES_FOLDERS']:
- server.watch(os.path.dirname(item))
+ server.watch(os.path.dirname(item), 'nikola build')
out_folder = self.site.config['OUTPUT_FOLDER']
if options and options.get('browser'):
webbrowser.open('http://localhost:{0}'.format(port))
- server.serve(port, out_folder)
+ server.serve(port, None, out_folder)
diff --git a/nikola/plugins/command/bootswatch_theme.py b/nikola/plugins/command/bootswatch_theme.py
index 94f37f2..82c47d2 100644
--- a/nikola/plugins/command/bootswatch_theme.py
+++ b/nikola/plugins/command/bootswatch_theme.py
@@ -86,12 +86,14 @@ class CommandBootswatchTheme(Command):
version = '2'
elif 'bootstrap' not in themes:
LOGGER.warn('"bootswatch_theme" only makes sense for themes that use bootstrap')
+ elif 'bootstrap3-gradients' in themes or 'bootstrap3-gradients-jinja' in themes:
+ LOGGER.warn('"bootswatch_theme" doesn\'t work well with the bootstrap3-gradients family')
- LOGGER.notice("Creating '{0}' theme from '{1}' and '{2}'".format(name, swatch, parent))
+ LOGGER.info("Creating '{0}' theme from '{1}' and '{2}'".format(name, swatch, parent))
utils.makedirs(os.path.join('themes', name, 'assets', 'css'))
for fname in ('bootstrap.min.css', 'bootstrap.css'):
url = '/'.join(('http://bootswatch.com', version, swatch, fname))
- LOGGER.notice("Downloading: " + url)
+ LOGGER.info("Downloading: " + url)
data = requests.get(url).text
with open(os.path.join('themes', name, 'assets', 'css', fname),
'wb+') as output:
@@ -99,5 +101,4 @@ class CommandBootswatchTheme(Command):
with open(os.path.join('themes', name, 'parent'), 'wb+') as output:
output.write(parent.encode('utf-8'))
- LOGGER.notice('Theme created. Change the THEME setting to "{0}" to use '
- 'it.'.format(name))
+ LOGGER.notice('Theme created. Change the THEME setting to "{0}" to use it.'.format(name))
diff --git a/nikola/plugins/command/check.py b/nikola/plugins/command/check.py
index a7e8c13..26db321 100644
--- a/nikola/plugins/command/check.py
+++ b/nikola/plugins/command/check.py
@@ -102,6 +102,14 @@ class CommandCheck(Command):
'default': False,
'help': 'List possible source files for files with broken links.',
},
+ {
+ 'name': 'verbose',
+ 'long': 'verbose',
+ 'short': 'v',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Be more verbose.',
+ },
]
def _execute(self, options, args):
@@ -112,6 +120,10 @@ class CommandCheck(Command):
if not options['links'] and not options['files'] and not options['clean']:
print(self.help())
return False
+ if options['verbose']:
+ self.logger.level = 1
+ else:
+ self.logger.level = 4
if options['links']:
failure = self.scan_links(options['find_sources'])
if options['files']:
@@ -126,6 +138,8 @@ class CommandCheck(Command):
def analyze(self, task, find_sources=False):
rv = False
self.whitelist = [re.compile(x) for x in self.site.config['LINK_CHECK_WHITELIST']]
+ base_url = urlparse(self.site.config['BASE_URL'])
+ url_type = self.site.config['URL_TYPE']
try:
filename = task.split(":")[-1]
d = lxml.html.fromstring(open(filename).read())
@@ -134,31 +148,51 @@ class CommandCheck(Command):
if target == "#":
continue
parsed = urlparse(target)
- if parsed.scheme or target.startswith('//'):
+
+ # Absolute links when using only paths, skip.
+ if (parsed.scheme or target.startswith('//')) and url_type in ('rel_path', 'full_path'):
continue
+
+ # Absolute links to other domains, skip
+ if (parsed.scheme or target.startswith('//')) and parsed.netloc != base_url.netloc:
+ continue
+
if parsed.fragment:
target = target.split('#')[0]
- target_filename = os.path.abspath(
- os.path.join(os.path.dirname(filename), unquote(target)))
+ if url_type == 'rel_path':
+ target_filename = os.path.abspath(
+ os.path.join(os.path.dirname(filename), unquote(target)))
+
+ elif url_type in ('full_path', 'absolute'):
+ target_filename = os.path.abspath(
+ os.path.join(os.path.dirname(filename), parsed.path))
+ if parsed.path.endswith('/'): # abspath removes trailing slashes
+ target_filename += '/{0}'.format(self.site.config['INDEX_FILE'])
+ if target_filename.startswith(base_url.path):
+ target_filename = target_filename[len(base_url.path):]
+ target_filename = os.path.join(self.site.config['OUTPUT_FOLDER'], target_filename)
+
if any(re.match(x, target_filename) for x in self.whitelist):
continue
elif target_filename not in self.existing_targets:
if os.path.exists(target_filename):
+ self.logger.notice("Good link {0} => {1}".format(target, target_filename))
self.existing_targets.add(target_filename)
else:
rv = True
- self.logger.warn("Broken link in {0}: ".format(filename), target)
+ self.logger.warn("Broken link in {0}: {1}".format(filename, target))
if find_sources:
self.logger.warn("Possible sources:")
self.logger.warn(os.popen('nikola list --deps ' + task, 'r').read())
self.logger.warn("===============================\n")
except Exception as exc:
- self.logger.error("Error with:", filename, exc)
+ self.logger.error("Error with: {0} {1}".format(filename, exc))
return rv
def scan_links(self, find_sources=False):
- self.logger.notice("Checking Links:")
- self.logger.notice("===============")
+ self.logger.info("Checking Links:")
+ self.logger.info("===============\n")
+ self.logger.notice("{0} mode".format(self.site.config['URL_TYPE']))
failure = False
for task in os.popen('nikola list --all', 'r').readlines():
task = task.strip()
@@ -170,13 +204,13 @@ class CommandCheck(Command):
if self.analyze(task, find_sources):
failure = True
if not failure:
- self.logger.notice("All links checked.")
+ self.logger.info("All links checked.")
return failure
def scan_files(self):
failure = False
- self.logger.notice("Checking Files:")
- self.logger.notice("===============\n")
+ self.logger.info("Checking Files:")
+ self.logger.info("===============\n")
only_on_output, only_on_input = real_scan_files(self.site)
# Ignore folders
@@ -195,7 +229,7 @@ class CommandCheck(Command):
for f in only_on_input:
self.logger.warn(f)
if not failure:
- self.logger.notice("All files checked.")
+ self.logger.info("All files checked.")
return failure
def clean_files(self):
diff --git a/nikola/plugins/command/console.py b/nikola/plugins/command/console.py
index e66b650..b0a8958 100644
--- a/nikola/plugins/command/console.py
+++ b/nikola/plugins/command/console.py
@@ -35,7 +35,7 @@ from nikola.utils import get_logger, STDERR_HANDLER
LOGGER = get_logger('console', STDERR_HANDLER)
-class Console(Command):
+class CommandConsole(Command):
"""Start debugging console."""
name = "console"
shells = ['ipython', 'bpython', 'plain']
diff --git a/nikola/plugins/command/deploy.py b/nikola/plugins/command/deploy.py
index eb5787e..bd1c15f 100644
--- a/nikola/plugins/command/deploy.py
+++ b/nikola/plugins/command/deploy.py
@@ -25,7 +25,6 @@
# 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
@@ -40,8 +39,8 @@ from nikola.plugin_categories import Command
from nikola.utils import remove_file, get_logger
-class Deploy(Command):
- """Deploy site. """
+class CommandDeploy(Command):
+ """Deploy site."""
name = "deploy"
doc_usage = ""
@@ -76,7 +75,7 @@ class Deploy(Command):
undeployed_posts.append(post)
for command in self.site.config['DEPLOY_COMMANDS']:
- self.logger.notice("==> {0}".format(command))
+ self.logger.info("==> {0}".format(command))
try:
subprocess.check_call(command, shell=True)
except subprocess.CalledProcessError as e:
@@ -84,26 +83,22 @@ class Deploy(Command):
'returned {1}'.format(e.cmd, e.returncode))
sys.exit(e.returncode)
- self.logger.notice("Successful deployment")
- tzinfo = pytz.timezone(self.site.config['TIMEZONE'])
+ self.logger.info("Successful deployment")
try:
- with open(timestamp_path, 'rb') as inf:
- last_deploy = literal_eval(inf.read().strip())
- if tzinfo:
- last_deploy = last_deploy.replace(tzinfo=tzinfo)
+ with codecs.open(timestamp_path, 'rb', 'utf8') as inf:
+ last_deploy = datetime.strptime(inf.read().strip(), "%Y-%m-%dT%H:%M:%S.%f")
clean = False
- except Exception:
+ except (IOError, Exception) as e:
+ self.logger.debug("Problem when reading `{0}`: {1}".format(timestamp_path, e))
last_deploy = datetime(1970, 1, 1)
- if tzinfo:
- last_deploy = last_deploy.replace(tzinfo=tzinfo)
clean = True
- new_deploy = datetime.now()
+ new_deploy = datetime.utcnow()
self._emit_deploy_event(last_deploy, new_deploy, clean, undeployed_posts)
# Store timestamp of successful deployment
with codecs.open(timestamp_path, 'wb+', 'utf8') as outf:
- outf.write(repr(new_deploy))
+ outf.write(new_deploy.isoformat())
def _emit_deploy_event(self, last_deploy, new_deploy, clean=False, undeployed=None):
""" Emit events for all timeline entries newer than last deploy.
@@ -129,9 +124,11 @@ class Deploy(Command):
'undeployed': undeployed
}
+ tzinfo = pytz.timezone(self.site.config['TIMEZONE'])
+
deployed = [
entry for entry in self.site.timeline
- if entry.date > last_deploy and entry not in undeployed
+ if entry.date > (last_deploy.replace(tzinfo=tzinfo) if tzinfo else last_deploy) and entry not in undeployed
]
event['deployed'] = deployed
diff --git a/nikola/plugins/command/import_blogger.py b/nikola/plugins/command/import_blogger.py
index ea12b4a..dd629c4 100644
--- a/nikola/plugins/command/import_blogger.py
+++ b/nikola/plugins/command/import_blogger.py
@@ -43,6 +43,7 @@ from nikola.plugin_categories import Command
from nikola import utils
from nikola.utils import req_missing
from nikola.plugins.basic_import import ImportMixin
+from nikola.plugins.command.init import SAMPLE_CONF, prepare_config
LOGGER = utils.get_logger('import_blogger', utils.STDERR_HANDLER)
@@ -95,8 +96,8 @@ class CommandImportBlogger(Command, ImportMixin):
conf_out_path = self.get_configuration_output_path()
# if it tracebacks here, look a comment in
# basic_import.Import_Mixin.generate_base_site
- conf_termplate_render = conf_template.render(**self.context)
- self.write_configuration(conf_out_path, conf_termplate_render)
+ conf_template_render = conf_template.render(**prepare_config(self.context))
+ self.write_configuration(conf_out_path, conf_template_render)
@classmethod
def get_channel_from_file(cls, filename):
@@ -106,8 +107,7 @@ class CommandImportBlogger(Command, ImportMixin):
@staticmethod
def populate_context(channel):
- # may need changes when the template conf.py.in changes
- context = {}
+ context = SAMPLE_CONF.copy()
context['DEFAULT_LANG'] = 'en' # blogger doesn't include the language
# in the dump
context['BLOG_TITLE'] = channel.feed.title
@@ -131,7 +131,6 @@ class CommandImportBlogger(Command, ImportMixin):
"html": ('.html', '.htm')
}
'''
- context['THEME'] = 'bootstrap3'
return context
diff --git a/nikola/plugins/command/import_feed.py b/nikola/plugins/command/import_feed.py
index 70a5cd5..ee59277 100644
--- a/nikola/plugins/command/import_feed.py
+++ b/nikola/plugins/command/import_feed.py
@@ -43,6 +43,7 @@ from nikola.plugin_categories import Command
from nikola import utils
from nikola.utils import req_missing
from nikola.plugins.basic_import import ImportMixin
+from nikola.plugins.command.init import SAMPLE_CONF, prepare_config
LOGGER = utils.get_logger('import_feed', utils.STDERR_HANDLER)
@@ -82,7 +83,7 @@ class CommandImportFeed(Command, ImportMixin):
self.import_posts(channel)
self.write_configuration(self.get_configuration_output_path(
- ), conf_template.render(**self.context))
+ ), conf_template.render(**prepare_config(self.context)))
@classmethod
def get_channel_from_file(cls, filename):
@@ -90,7 +91,7 @@ class CommandImportFeed(Command, ImportMixin):
@staticmethod
def populate_context(channel):
- context = {}
+ context = SAMPLE_CONF.copy()
context['DEFAULT_LANG'] = channel.feed.title_detail.language \
if channel.feed.title_detail.language else 'en'
context['BLOG_TITLE'] = channel.feed.title
@@ -100,9 +101,11 @@ class CommandImportFeed(Command, ImportMixin):
context['BLOG_EMAIL'] = channel.feed.author_detail.get('email', '') if 'author_detail' in channel.feed else ''
context['BLOG_AUTHOR'] = channel.feed.author_detail.get('name', '') if 'author_detail' in channel.feed else ''
- context['POST_PAGES'] = '''(
- ("posts/*.html", "posts", "post.tmpl", True),
- ("stories/*.html", "stories", "story.tmpl", False),
+ context['POSTS'] = '''(
+ ("posts/*.html", "posts", "post.tmpl"),
+ )'''
+ context['PAGES'] = '''(
+ ("stories/*.html", "stories", "story.tmpl"),
)'''
context['COMPILERS'] = '''{
"rest": ('.txt', '.rst'),
diff --git a/nikola/plugins/command/import_wordpress.py b/nikola/plugins/command/import_wordpress.py
index 0c9915a..b567c77 100644
--- a/nikola/plugins/command/import_wordpress.py
+++ b/nikola/plugins/command/import_wordpress.py
@@ -50,6 +50,8 @@ from nikola.plugin_categories import Command
from nikola import utils
from nikola.utils import req_missing
from nikola.plugins.basic_import import ImportMixin, links
+from nikola.nikola import DEFAULT_TRANSLATIONS_PATTERN
+from nikola.plugins.command.init import SAMPLE_CONF, prepare_config
LOGGER = utils.get_logger('import_wordpress', utils.STDERR_HANDLER)
@@ -84,6 +86,23 @@ class CommandImportWordpress(Command, ImportMixin):
'type': bool,
'help': "Do not try to download files for the import",
},
+ {
+ 'name': 'separate_qtranslate_content',
+ 'long': 'qtranslate',
+ 'default': False,
+ 'type': bool,
+ 'help': "Look for translations generated by qtranslate plugin",
+ # WARNING: won't recover translated titles that actually
+ # don't seem to be part of the wordpress XML export at the
+ # time of writing :(
+ },
+ {
+ 'name': 'translations_pattern',
+ 'long': 'translations_pattern',
+ 'default': None,
+ 'type': str,
+ 'help': "The pattern for translation files names",
+ },
]
def _execute(self, options={}, args=[]):
@@ -114,6 +133,9 @@ class CommandImportWordpress(Command, ImportMixin):
self.exclude_drafts = options.get('exclude_drafts', False)
self.no_downloads = options.get('no_downloads', False)
+ self.separate_qtranslate_content = options.get('separate_qtranslate_content')
+ self.translations_pattern = options.get('translations_pattern')
+
if not self.no_downloads:
def show_info_about_mising_module(modulename):
LOGGER.error(
@@ -135,15 +157,21 @@ class CommandImportWordpress(Command, ImportMixin):
self.context = self.populate_context(channel)
conf_template = self.generate_base_site()
+ # If user has specified a custom pattern for translation files we
+ # need to fix the config
+ if self.translations_pattern:
+ self.context['TRANSLATIONS_PATTERN'] = self.translations_pattern
+
self.import_posts(channel)
self.context['REDIRECTIONS'] = self.configure_redirections(
self.url_map)
self.write_urlmap_csv(
os.path.join(self.output_folder, 'url_map.csv'), self.url_map)
- rendered_template = conf_template.render(**self.context)
+ rendered_template = conf_template.render(**prepare_config(self.context))
rendered_template = re.sub('# REDIRECTIONS = ', 'REDIRECTIONS = ',
rendered_template)
+
if self.timezone:
rendered_template = re.sub('# TIMEZONE = \'UTC\'',
'TIMEZONE = \'' + self.timezone + '\'',
@@ -194,8 +222,9 @@ class CommandImportWordpress(Command, ImportMixin):
def populate_context(channel):
wordpress_namespace = channel.nsmap['wp']
- context = {}
+ context = SAMPLE_CONF.copy()
context['DEFAULT_LANG'] = get_text_tag(channel, 'language', 'en')[:2]
+ context['TRANSLATIONS_PATTERN'] = DEFAULT_TRANSLATIONS_PATTERN
context['BLOG_TITLE'] = get_text_tag(channel, 'title',
'PUT TITLE HERE')
context['BLOG_DESCRIPTION'] = get_text_tag(
@@ -205,9 +234,10 @@ class CommandImportWordpress(Command, ImportMixin):
base_site_url = channel.find('{{{0}}}author'.format(wordpress_namespace))
context['BASE_URL'] = get_text_tag(base_site_url,
None,
- "http://foo.com")
+ "http://foo.com/")
+ if not context['BASE_URL'].endswith('/'):
+ context['BASE_URL'] += '/'
context['SITE_URL'] = context['BASE_URL']
- context['THEME'] = 'bootstrap3'
author = channel.find('{{{0}}}author'.format(wordpress_namespace))
context['BLOG_EMAIL'] = get_text_tag(
@@ -253,7 +283,7 @@ class CommandImportWordpress(Command, ImportMixin):
+ list(path.split('/'))))
dst_dir = os.path.dirname(dst_path)
utils.makedirs(dst_dir)
- LOGGER.notice("Downloading {0} => {1}".format(url, dst_path))
+ LOGGER.info("Downloading {0} => {1}".format(url, dst_path))
self.download_url_content_to_file(url, dst_path)
dst_url = '/'.join(dst_path.split(os.sep)[2:])
links[link] = '/' + dst_url
@@ -288,7 +318,7 @@ class CommandImportWordpress(Command, ImportMixin):
# your blogging into another site or system its not.
# Why don't they just use JSON?
if sys.version_info[0] == 2:
- metadata = phpserialize.loads(meta_value.text)
+ metadata = phpserialize.loads(utils.sys_encode(meta_value.text))
size_key = 'sizes'
file_key = 'file'
else:
@@ -307,7 +337,7 @@ class CommandImportWordpress(Command, ImportMixin):
+ list(path.split('/'))))
dst_dir = os.path.dirname(dst_path)
utils.makedirs(dst_dir)
- LOGGER.notice("Downloading {0} => {1}".format(url, dst_path))
+ LOGGER.info("Downloading {0} => {1}".format(url, dst_path))
self.download_url_content_to_file(url, dst_path)
dst_url = '/'.join(dst_path.split(os.sep)[2:])
links[url] = '/' + dst_url
@@ -350,14 +380,17 @@ class CommandImportWordpress(Command, ImportMixin):
# 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)
- path = unquote(urlparse(link).path)
+ path = unquote(urlparse(link).path.strip('/'))
# In python 2, path is a str. slug requires a unicode
# object. According to wikipedia, unquoted strings will
# usually be UTF8
if isinstance(path, utils.bytes_str):
path = path.decode('utf8')
- slug = utils.slugify(path)
+ pathlist = path.split('/')
+ if len(pathlist) > 1:
+ out_folder = os.path.join(*([out_folder] + pathlist[:-1]))
+ slug = utils.slugify(pathlist[-1])
if not slug: # it happens if the post has no "nice" URL
slug = get_text_tag(
item, '{{{0}}}post_name'.format(wordpress_namespace), None)
@@ -395,21 +428,43 @@ class CommandImportWordpress(Command, ImportMixin):
continue
tags.append(text)
+ if '$latex' in content:
+ tags.append('mathjax')
+
if is_draft and self.exclude_drafts:
LOGGER.notice('Draft "{0}" will not be imported.'.format(title))
elif content.strip():
# If no content is found, no files are written.
- self.url_map[link] = self.context['SITE_URL'] + '/' + \
- out_folder + '/' + slug + '.html'
-
- content = self.transform_content(content)
-
- self.write_metadata(os.path.join(self.output_folder, out_folder,
- slug + '.meta'),
- title, slug, post_date, description, tags)
- self.write_content(
- os.path.join(self.output_folder, out_folder, slug + '.wp'),
- content)
+ self.url_map[link] = (self.context['SITE_URL'] + out_folder + '/'
+ + slug + '.html')
+ if hasattr(self, "separate_qtranslate_content") \
+ and self.separate_qtranslate_content:
+ content_translations = separate_qtranslate_content(content)
+ else:
+ content_translations = {"": content}
+ default_language = self.context["DEFAULT_LANG"]
+ for lang, content in content_translations.items():
+ if lang:
+ out_meta_filename = slug + '.meta'
+ if lang == default_language:
+ out_content_filename = slug + '.wp'
+ else:
+ out_content_filename \
+ = utils.get_translation_candidate(self.context,
+ slug + ".wp", lang)
+ meta_slug = slug
+ else:
+ out_meta_filename = slug + '.meta'
+ out_content_filename = slug + '.wp'
+ meta_slug = slug
+ content = self.transform_content(content)
+ self.write_metadata(os.path.join(self.output_folder, out_folder,
+ out_meta_filename),
+ title, meta_slug, post_date, description, tags)
+ self.write_content(
+ os.path.join(self.output_folder,
+ out_folder, out_content_filename),
+ content)
else:
LOGGER.warn('Not going to import "{0}" because it seems to contain'
' no content.'.format(title))
@@ -441,3 +496,47 @@ def get_text_tag(tag, name, default):
return t.text
else:
return default
+
+
+def separate_qtranslate_content(text):
+ """Parse the content of a wordpress post or page and separate
+ the various language specific contents when they are delimited
+ with qtranslate tags: <!--:LL-->blabla<!--:-->"""
+ # TODO: uniformize qtranslate tags <!--/en--> => <!--:-->
+ qt_start = "<!--:"
+ qt_end = "-->"
+ qt_end_with_lang_len = 5
+ qt_chunks = text.split(qt_start)
+ content_by_lang = {}
+ common_txt_list = []
+ for c in qt_chunks:
+ if not c.strip():
+ continue
+ if c.startswith(qt_end):
+ # just after the end of a language specific section, there may
+ # be some piece of common text or tags, or just nothing
+ lang = "" # default language
+ c = c.lstrip(qt_end)
+ if not c:
+ continue
+ elif c[2:].startswith(qt_end):
+ # a language specific section (with language code at the begining)
+ lang = c[:2]
+ c = c[qt_end_with_lang_len:]
+ else:
+ # nowhere specific (maybe there is no language section in the
+ # currently parsed content)
+ lang = "" # default language
+ if not lang:
+ common_txt_list.append(c)
+ for l in content_by_lang.keys():
+ content_by_lang[l].append(c)
+ else:
+ content_by_lang[lang] = content_by_lang.get(lang, common_txt_list) + [c]
+ # in case there was no language specific section, just add the text
+ if common_txt_list and not content_by_lang:
+ content_by_lang[""] = common_txt_list
+ # Format back the list to simple text
+ for l in content_by_lang.keys():
+ content_by_lang[l] = " ".join(content_by_lang[l])
+ return content_by_lang
diff --git a/nikola/plugins/command/init.py b/nikola/plugins/command/init.py
index 96caad8..d7eeed7 100644
--- a/nikola/plugins/command/init.py
+++ b/nikola/plugins/command/init.py
@@ -28,18 +28,71 @@ from __future__ import print_function
import os
import shutil
import codecs
+import json
from mako.template import Template
import nikola
+from nikola.nikola import DEFAULT_TRANSLATIONS_PATTERN
from nikola.plugin_categories import Command
from nikola.utils import get_logger, makedirs, STDERR_HANDLER
from nikola.winutils import fix_git_symlinked
LOGGER = get_logger('init', STDERR_HANDLER)
+SAMPLE_CONF = {
+ 'BLOG_AUTHOR': "Your Name",
+ 'BLOG_TITLE': "Demo Site",
+ 'SITE_URL': "http://getnikola.com/",
+ 'BLOG_EMAIL': "joe@demo.site",
+ 'BLOG_DESCRIPTION': "This is a demo site for Nikola.",
+ 'DEFAULT_LANG': "en",
+ 'THEME': 'bootstrap3',
+ 'COMMENT_SYSTEM': 'disqus',
+ 'COMMENT_SYSTEM_ID': 'nikolademo',
+ 'TRANSLATIONS_PATTERN': DEFAULT_TRANSLATIONS_PATTERN,
+ 'POSTS': """(
+("posts/*.rst", "posts", "post.tmpl"),
+("posts/*.txt", "posts", "post.tmpl"),
+)""",
+ 'PAGES': """(
+("stories/*.rst", "stories", "story.tmpl"),
+("stories/*.txt", "stories", "story.tmpl"),
+)""",
+ 'COMPILERS': """{
+"rest": ('.rst', '.txt'),
+"markdown": ('.md', '.mdown', '.markdown'),
+"textile": ('.textile',),
+"txt2tags": ('.t2t',),
+"bbcode": ('.bb',),
+"wiki": ('.wiki',),
+"ipynb": ('.ipynb',),
+"html": ('.html', '.htm'),
+# PHP files are rendered the usual way (i.e. with the full templates).
+# The resulting files have .php extensions, making it possible to run
+# them without reconfiguring your server to recognize them.
+"php": ('.php',),
+# Pandoc detects the input from the source filename
+# but is disabled by default as it would conflict
+# with many of the others.
+# "pandoc": ('.rst', '.md', '.txt'),
+}""",
+ 'REDIRECTIONS': [],
+}
+
+
+# In order to ensure proper escaping, all variables but the three
+# pre-formatted ones are handled by json.dumps().
+def prepare_config(config):
+ """Parse sample config with JSON."""
+ p = config.copy()
+ p.update(dict((k, json.dumps(v)) for k, v in p.items()
+ if k not in ('POSTS', 'PAGES', 'COMPILERS')))
+ return p
+
class CommandInit(Command):
+
"""Create a new site."""
name = "init"
@@ -57,40 +110,6 @@ class CommandInit(Command):
}
]
- SAMPLE_CONF = {
- 'BLOG_AUTHOR': "Your Name",
- 'BLOG_TITLE': "Demo Site",
- 'SITE_URL': "http://getnikola.com/",
- 'BLOG_EMAIL': "joe@demo.site",
- 'BLOG_DESCRIPTION': "This is a demo site for Nikola.",
- 'DEFAULT_LANG': "en",
- 'THEME': 'bootstrap3',
-
- 'POSTS': """(
- ("posts/*.rst", "posts", "post.tmpl"),
- ("posts/*.txt", "posts", "post.tmpl"),
-)""",
- 'PAGES': """(
- ("stories/*.rst", "stories", "story.tmpl"),
- ("stories/*.txt", "stories", "story.tmpl"),
-)""",
- 'COMPILERS': """{
- "rest": ('.rst', '.txt'),
- "markdown": ('.md', '.mdown', '.markdown'),
- "textile": ('.textile',),
- "txt2tags": ('.t2t',),
- "bbcode": ('.bb',),
- "wiki": ('.wiki',),
- "ipynb": ('.ipynb',),
- "html": ('.html', '.htm'),
- # Pandoc detects the input from the source filename
- # but is disabled by default as it would conflict
- # with many of the others.
- # "pandoc": ('.rst', '.md', '.txt'),
-}""",
- 'REDIRECTIONS': '[]',
- }
-
@classmethod
def copy_sample_site(cls, target):
lib_path = cls.get_path_to_nikola_modules()
@@ -105,7 +124,7 @@ class CommandInit(Command):
conf_template = Template(filename=template_path)
conf_path = os.path.join(target, 'conf.py')
with codecs.open(conf_path, 'w+', 'utf8') as fd:
- fd.write(conf_template.render(**cls.SAMPLE_CONF))
+ fd.write(conf_template.render(**prepare_config(SAMPLE_CONF)))
@classmethod
def create_empty_site(cls, target):
@@ -122,16 +141,13 @@ class CommandInit(Command):
print("Usage: nikola init folder [options]")
return False
target = args[0]
- if target is None:
- print(self.usage)
+ if not options or not options.get('demo'):
+ self.create_empty_site(target)
+ LOGGER.info('Created empty site at {0}.'.format(target))
else:
- if not options or not options.get('demo'):
- self.create_empty_site(target)
- LOGGER.notice('Created empty site at {0}.'.format(target))
- else:
- self.copy_sample_site(target)
- LOGGER.notice("A new site with example data has been created at "
- "{0}.".format(target))
- LOGGER.notice("See README.txt in that folder for more information.")
-
- self.create_configuration(target)
+ self.copy_sample_site(target)
+ LOGGER.info("A new site with example data has been created at "
+ "{0}.".format(target))
+ LOGGER.info("See README.txt in that folder for more information.")
+
+ self.create_configuration(target)
diff --git a/nikola/plugins/command/install_plugin.py b/nikola/plugins/command/install_plugin.py
index 1d6584d..34223c0 100644
--- a/nikola/plugins/command/install_plugin.py
+++ b/nikola/plugins/command/install_plugin.py
@@ -27,7 +27,6 @@
from __future__ import print_function
import codecs
import os
-import sys
import json
import shutil
import subprocess
@@ -123,10 +122,10 @@ class CommandInstallPlugin(Command):
def do_install(self, name, data):
if name in data:
utils.makedirs(self.output_dir)
- LOGGER.notice('Downloading: ' + data[name])
+ LOGGER.info('Downloading: ' + data[name])
zip_file = BytesIO()
zip_file.write(requests.get(data[name]).content)
- LOGGER.notice('Extracting: {0} into plugins'.format(name))
+ LOGGER.info('Extracting: {0} into plugins'.format(name))
utils.extract_all(zip_file, 'plugins')
dest_path = os.path.join('plugins', name)
else:
@@ -142,13 +141,13 @@ class CommandInstallPlugin(Command):
LOGGER.error("{0} is already installed".format(name))
return False
- LOGGER.notice('Copying {0} into plugins'.format(plugin_path))
+ LOGGER.info('Copying {0} into plugins'.format(plugin_path))
shutil.copytree(plugin_path, dest_path)
reqpath = os.path.join(dest_path, 'requirements.txt')
if os.path.exists(reqpath):
LOGGER.notice('This plugin has Python dependencies.')
- LOGGER.notice('Installing dependencies with pip...')
+ LOGGER.info('Installing dependencies with pip...')
try:
subprocess.check_call(('pip', 'install', '-r', reqpath))
except subprocess.CalledProcessError:
@@ -159,7 +158,7 @@ class CommandInstallPlugin(Command):
print('You have to install those yourself or through a '
'package manager.')
else:
- LOGGER.notice('Dependency installation succeeded.')
+ LOGGER.info('Dependency installation succeeded.')
reqnpypath = os.path.join(dest_path, 'requirements-nonpy.txt')
if os.path.exists(reqnpypath):
LOGGER.notice('This plugin has third-party '
@@ -177,10 +176,10 @@ class CommandInstallPlugin(Command):
'manager.')
confpypath = os.path.join(dest_path, 'conf.py.sample')
if os.path.exists(confpypath):
- LOGGER.notice('This plugin has a sample config file.')
+ LOGGER.notice('This plugin has a sample config file. Integrate it with yours in order to make this plugin work!')
print('Contents of the conf.py.sample file:\n')
with codecs.open(confpypath, 'rb', 'utf-8') as fh:
- if sys.platform == 'win32':
+ if self.site.colorful:
print(indent(pygments.highlight(
fh.read(), PythonLexer(), TerminalFormatter()),
4 * ' '))
diff --git a/nikola/plugins/command/install_theme.py b/nikola/plugins/command/install_theme.py
index 569397b..47c73b4 100644
--- a/nikola/plugins/command/install_theme.py
+++ b/nikola/plugins/command/install_theme.py
@@ -26,7 +26,6 @@
from __future__ import print_function
import os
-import sys
import codecs
import json
import shutil
@@ -137,10 +136,10 @@ class CommandInstallTheme(Command):
def do_install(self, name, data):
if name in data:
utils.makedirs(self.output_dir)
- LOGGER.notice('Downloading: ' + data[name])
+ LOGGER.info('Downloading: ' + data[name])
zip_file = BytesIO()
zip_file.write(requests.get(data[name]).content)
- LOGGER.notice('Extracting: {0} into themes'.format(name))
+ LOGGER.info('Extracting: {0} into themes'.format(name))
utils.extract_all(zip_file)
dest_path = os.path.join('themes', name)
else:
@@ -156,14 +155,14 @@ class CommandInstallTheme(Command):
LOGGER.error("{0} is already installed".format(name))
return False
- LOGGER.notice('Copying {0} into themes'.format(theme_path))
+ LOGGER.info('Copying {0} into themes'.format(theme_path))
shutil.copytree(theme_path, dest_path)
confpypath = os.path.join(dest_path, 'conf.py.sample')
if os.path.exists(confpypath):
- LOGGER.notice('This plugin has a sample config file. Integrate it with yours in order to make this theme work!')
+ LOGGER.notice('This theme has a sample config file. Integrate it with yours in order to make this theme work!')
print('Contents of the conf.py.sample file:\n')
with codecs.open(confpypath, 'rb', 'utf-8') as fh:
- if sys.platform == 'win32':
+ if self.site.colorful:
print(indent(pygments.highlight(
fh.read(), PythonLexer(), TerminalFormatter()),
4 * ' '))
diff --git a/nikola/plugins/command/new_page.plugin b/nikola/plugins/command/new_page.plugin
new file mode 100644
index 0000000..1f1c84c
--- /dev/null
+++ b/nikola/plugins/command/new_page.plugin
@@ -0,0 +1,9 @@
+[Core]
+Name = new_page
+Module = new_page
+
+[Documentation]
+Author = Roberto Alsina, Chris Warrick
+Version = 0.1
+Website = http://getnikola.com
+Description = Create a new page.
diff --git a/nikola/plugins/command/new_page.py b/nikola/plugins/command/new_page.py
new file mode 100644
index 0000000..39c0c1d
--- /dev/null
+++ b/nikola/plugins/command/new_page.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2014 Roberto Alsina, Chris Warrick and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import unicode_literals, print_function
+
+from nikola.plugin_categories import Command
+
+
+class CommandNewPage(Command):
+ """Create a new page."""
+
+ name = "new_page"
+ doc_usage = "[options] [path]"
+ doc_purpose = "create a new page in the site"
+ cmd_options = [
+ {
+ 'name': 'title',
+ 'short': 't',
+ 'long': 'title',
+ 'type': str,
+ 'default': '',
+ 'help': 'Title for the page.'
+ },
+ {
+ 'name': 'onefile',
+ 'short': '1',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Create the page with embedded metadata (single file format)'
+ },
+ {
+ 'name': 'twofile',
+ 'short': '2',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Create the page with separate metadata (two file format)'
+ },
+ {
+ 'name': 'content_format',
+ 'short': 'f',
+ 'long': 'format',
+ 'type': str,
+ 'default': '',
+ 'help': 'Markup format for the page, one of rest, markdown, wiki, '
+ 'bbcode, html, textile, txt2tags',
+ },
+ ]
+
+ def _execute(self, options, args):
+ """Create a new page."""
+ options['tags'] = ''
+ options['schedule'] = False
+ options['is_page'] = True
+ # Even though stuff was split into `new_page`, it’s easier to do it
+ # there not to duplicate the code.
+ p = self.site.plugin_manager.getPluginByName('new_post', 'Command').plugin_object
+ return p.execute(options, args)
diff --git a/nikola/plugins/command/new_post.py b/nikola/plugins/command/new_post.py
index a5c551d..cd37a75 100644
--- a/nikola/plugins/command/new_post.py
+++ b/nikola/plugins/command/new_post.py
@@ -35,7 +35,9 @@ from blinker import signal
from nikola.plugin_categories import Command
from nikola import utils
-LOGGER = utils.get_logger('new_post', utils.STDERR_HANDLER)
+POSTLOGGER = utils.get_logger('new_post', utils.STDERR_HANDLER)
+PAGELOGGER = utils.get_logger('new_page', utils.STDERR_HANDLER)
+LOGGER = POSTLOGGER
def filter_post_pages(compiler, is_post, compilers, post_pages):
@@ -57,8 +59,8 @@ def filter_post_pages(compiler, is_post, compilers, post_pages):
type_name = "post" if is_post else "page"
raise Exception("Can't find a way, using your configuration, to create "
"a {0} in format {1}. You may want to tweak "
- "COMPILERS or POSTS/PAGES in conf.py".format(
- type_name, compiler))
+ "COMPILERS or {2}S in conf.py".format(
+ type_name, compiler, type_name.upper()))
return filtered[0]
@@ -134,7 +136,7 @@ class CommandNewPost(Command):
'long': 'page',
'type': bool,
'default': False,
- 'help': 'Create a page instead of a blog post.'
+ 'help': 'Create a page instead of a blog post. (see also: `nikola new_page`)'
},
{
'name': 'title',
@@ -142,36 +144,36 @@ class CommandNewPost(Command):
'long': 'title',
'type': str,
'default': '',
- 'help': 'Title for the page/post.'
+ 'help': 'Title for the post.'
},
{
'name': 'tags',
'long': 'tags',
'type': str,
'default': '',
- 'help': 'Comma-separated tags for the page/post.'
+ 'help': 'Comma-separated tags for the post.'
},
{
'name': 'onefile',
'short': '1',
'type': bool,
'default': False,
- 'help': 'Create post with embedded metadata (single file format)'
+ 'help': 'Create the post with embedded metadata (single file format)'
},
{
'name': 'twofile',
'short': '2',
'type': bool,
'default': False,
- 'help': 'Create post with separate metadata (two file format)'
+ 'help': 'Create the post with separate metadata (two file format)'
},
{
- 'name': 'post_format',
+ 'name': 'content_format',
'short': 'f',
'long': 'format',
'type': str,
'default': '',
- 'help': 'Markup format for post, one of rest, markdown, wiki, '
+ 'help': 'Markup format for the post, one of rest, markdown, wiki, '
'bbcode, html, textile, txt2tags',
},
{
@@ -179,13 +181,14 @@ class CommandNewPost(Command):
'short': 's',
'type': bool,
'default': False,
- 'help': 'Schedule post based on recurrence rule'
+ 'help': 'Schedule the post based on recurrence rule'
},
]
def _execute(self, options, args):
"""Create a new post or page."""
+ global LOGGER
compiler_names = [p.name for p in
self.site.plugin_manager.getPluginsOfCategory(
"PageCompiler")]
@@ -198,38 +201,46 @@ class CommandNewPost(Command):
else:
path = None
+ # Even though stuff was split into `new_page`, it’s easier to do it
+ # here not to duplicate the code.
is_page = options.get('is_page', False)
is_post = not is_page
+ content_type = 'page' if is_page else 'post'
title = options['title'] or None
tags = options['tags']
onefile = options['onefile']
twofile = options['twofile']
+ if is_page:
+ LOGGER = PAGELOGGER
+ else:
+ LOGGER = POSTLOGGER
+
if twofile:
onefile = False
if not onefile and not twofile:
onefile = self.site.config.get('ONE_FILE_POSTS', True)
- post_format = options['post_format']
+ content_format = options['content_format']
- if not post_format: # Issue #400
- post_format = get_default_compiler(
+ if not content_format: # Issue #400
+ content_format = get_default_compiler(
is_post,
self.site.config['COMPILERS'],
self.site.config['post_pages'])
- if post_format not in compiler_names:
- LOGGER.error("Unknown post format " + post_format)
+ if content_format not in compiler_names:
+ LOGGER.error("Unknown {0} format {1}".format(content_type, content_format))
return
compiler_plugin = self.site.plugin_manager.getPluginByName(
- post_format, "PageCompiler").plugin_object
+ content_format, "PageCompiler").plugin_object
# Guess where we should put this
- entry = filter_post_pages(post_format, is_post,
+ entry = filter_post_pages(content_format, is_post,
self.site.config['COMPILERS'],
self.site.config['post_pages'])
- print("Creating New Post")
+ print("Creating New {0}".format(content_type.title()))
print("-----------------\n")
if title is None:
print("Enter title: ", end='')
@@ -247,7 +258,7 @@ class CommandNewPost(Command):
if isinstance(path, utils.bytes_str):
path = path.decode(sys.stdin.encoding)
slug = utils.slugify(os.path.splitext(os.path.basename(path))[0])
- # Calculate the date to use for the post
+ # Calculate the date to use for the content
schedule = options['schedule'] or self.site.config['SCHEDULE_ALL']
rule = self.site.config['SCHEDULE_RULE']
force_today = self.site.config['SCHEDULE_FORCE_TODAY']
@@ -275,7 +286,7 @@ class CommandNewPost(Command):
metadata = self.site.config['ADDITIONAL_METADATA']
compiler_plugin.create_post(
txt_path, onefile, title=title,
- slug=slug, date=date, tags=tags, **metadata)
+ slug=slug, date=date, tags=tags, is_page=is_page, **metadata)
event = dict(path=txt_path)
@@ -283,9 +294,9 @@ class CommandNewPost(Command):
with codecs.open(meta_path, "wb+", "utf8") as fd:
fd.write('\n'.join(data))
with codecs.open(txt_path, "wb+", "utf8") as fd:
- fd.write("Write your post here.")
- LOGGER.notice("Your post's metadata is at: {0}".format(meta_path))
+ fd.write("Write your {0} here.".format(content_type))
+ LOGGER.info("Your {0}'s metadata is at: {1}".format(content_type, meta_path))
event['meta_path'] = meta_path
- LOGGER.notice("Your post's text is at: {0}".format(txt_path))
+ LOGGER.info("Your {0}'s text is at: {1}".format(content_type, txt_path))
- signal('new_post').send(self, **event)
+ signal('new_' + content_type).send(self, **event)
diff --git a/nikola/plugins/command/planetoid/__init__.py b/nikola/plugins/command/planetoid/__init__.py
index ff5dd13..fe1a59b 100644
--- a/nikola/plugins/command/planetoid/__init__.py
+++ b/nikola/plugins/command/planetoid/__init__.py
@@ -162,12 +162,12 @@ class Planetoid(Command, Task):
# TODO: log failure
return
if parsed.feed.get('title'):
- LOGGER.notice(parsed.feed.title)
+ LOGGER.info(parsed.feed.title)
else:
- LOGGER.notice(feed.url)
+ LOGGER.info(feed.url)
feed.etag = parsed.get('etag', 'foo')
modified = tuple(parsed.get('date_parsed', (1970, 1, 1)))[:6]
- LOGGER.notice("==========>", modified)
+ LOGGER.info("==========>", modified)
modified = datetime.datetime(*modified)
feed.last_modified = modified
feed.save()
@@ -176,14 +176,14 @@ class Planetoid(Command, Task):
# TODO log failure
return
for entry_data in parsed.entries:
- LOGGER.notice("=========================================")
+ LOGGER.info("=========================================")
date = entry_data.get('published_parsed', None)
if date is None:
date = entry_data.get('updated_parsed', None)
if date is None:
LOGGER.error("Can't parse date from:\n", entry_data)
return False
- LOGGER.notice("DATE:===>", date)
+ LOGGER.info("DATE:===>", date)
date = datetime.datetime(*(date[:6]))
title = "%s: %s" % (feed.name, entry_data.get('title', 'Sin título'))
content = entry_data.get('content', None)
@@ -195,9 +195,9 @@ class Planetoid(Command, Task):
content = entry_data.get('summary', 'Sin contenido')
guid = str(entry_data.get('guid', entry_data.link))
link = entry_data.link
- LOGGER.notice(repr([date, title]))
+ LOGGER.info(repr([date, title]))
e = list(Entry.select().where(Entry.guid == guid))
- LOGGER.notice(
+ LOGGER.info(
repr(dict(
date=date,
title=title,
diff --git a/nikola/plugins/command/serve.py b/nikola/plugins/command/serve.py
index 2dd15c1..f27d1f7 100644
--- a/nikola/plugins/command/serve.py
+++ b/nikola/plugins/command/serve.py
@@ -26,6 +26,7 @@
from __future__ import print_function
import os
+import webbrowser
try:
from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
@@ -37,7 +38,7 @@ from nikola.plugin_categories import Command
from nikola.utils import get_logger
-class CommandBuild(Command):
+class CommandServe(Command):
"""Start test server."""
name = "serve"
@@ -57,11 +58,19 @@ class CommandBuild(Command):
{
'name': 'address',
'short': 'a',
- 'long': '--address',
+ 'long': 'address',
'type': str,
'default': '127.0.0.1',
'help': 'Address to bind (default: 127.0.0.1)',
},
+ {
+ 'name': 'browser',
+ 'short': 'b',
+ 'long': 'browser',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Open the test server in a web browser',
+ }
)
def _execute(self, options, args):
@@ -75,7 +84,11 @@ class CommandBuild(Command):
httpd = HTTPServer((options['address'], options['port']),
OurHTTPRequestHandler)
sa = httpd.socket.getsockname()
- self.logger.notice("Serving HTTP on {0} port {1} ...".format(*sa))
+ self.logger.info("Serving HTTP on {0} port {1} ...".format(*sa))
+ if options['browser']:
+ server_url = "http://{0}:{1}/".format(options['address'], options['port'])
+ self.logger.info("Opening {0} in the default web browser ...".format(server_url))
+ webbrowser.open(server_url)
httpd.serve_forever()
diff --git a/nikola/plugins/compile/asciidoc.py b/nikola/plugins/compile/asciidoc.py
index 12cb4bf..68f96d9 100644
--- a/nikola/plugins/compile/asciidoc.py
+++ b/nikola/plugins/compile/asciidoc.py
@@ -40,7 +40,7 @@ from nikola.utils import makedirs, req_missing
try:
from collections import OrderedDict
except ImportError:
- OrderedDict = None # NOQA
+ OrderedDict = dict # NOQA
class CompileAsciiDoc(PageCompiler):
@@ -57,11 +57,8 @@ class CompileAsciiDoc(PageCompiler):
if e.strreror == 'No such file or directory':
req_missing(['asciidoc'], 'build this site (compile with asciidoc)', python=False)
- def create_post(self, path, onefile=False, **kw):
- if OrderedDict is not None:
- metadata = OrderedDict()
- else:
- metadata = {}
+ def create_post(self, path, onefile=False, is_page=False, **kw):
+ metadata = OrderedDict()
metadata.update(self.default_metadata)
metadata.update(kw)
makedirs(os.path.dirname(path))
@@ -71,4 +68,4 @@ class CompileAsciiDoc(PageCompiler):
for k, v in metadata.items():
fd.write('.. {0}: {1}\n'.format(k, v))
fd.write("/////////////////////////////////////////////\n")
- fd.write("\nWrite your post here.")
+ fd.write("\nWrite your {0} here.".format('page' if is_page else 'post'))
diff --git a/nikola/plugins/compile/bbcode.py b/nikola/plugins/compile/bbcode.py
index 5345be3..0961ffe 100644
--- a/nikola/plugins/compile/bbcode.py
+++ b/nikola/plugins/compile/bbcode.py
@@ -40,7 +40,7 @@ from nikola.utils import makedirs, req_missing
try:
from collections import OrderedDict
except ImportError:
- OrderedDict = None # NOQA
+ OrderedDict = dict # NOQA
class CompileBbcode(PageCompiler):
@@ -66,11 +66,8 @@ class CompileBbcode(PageCompiler):
output = self.parser.format(data)
out_file.write(output)
- def create_post(self, path, onefile=False, **kw):
- if OrderedDict is not None:
- metadata = OrderedDict()
- else:
- metadata = {}
+ def create_post(self, path, onefile=False, is_page=False, **kw):
+ metadata = OrderedDict()
metadata.update(self.default_metadata)
metadata.update(kw)
makedirs(os.path.dirname(path))
@@ -80,4 +77,4 @@ class CompileBbcode(PageCompiler):
for k, v in metadata.items():
fd.write('.. {0}: {1}\n'.format(k, v))
fd.write('-->[/note]\n\n')
- fd.write("Write your post here.")
+ fd.write("Write your {0} here.".format('page' if is_page else 'post'))
diff --git a/nikola/plugins/compile/html.py b/nikola/plugins/compile/html.py
index 5352f00..09a9756 100644
--- a/nikola/plugins/compile/html.py
+++ b/nikola/plugins/compile/html.py
@@ -36,7 +36,7 @@ from nikola.utils import makedirs
try:
from collections import OrderedDict
except ImportError:
- OrderedDict = None # NOQA
+ OrderedDict = dict # NOQA
_META_SEPARATOR = '(' + os.linesep * 2 + '|' + ('\n' * 2) + '|' + ("\r\n" * 2) + ')'
@@ -56,11 +56,8 @@ class CompileHtml(PageCompiler):
out_file.write(data)
return True
- def create_post(self, path, onefile=False, **kw):
- if OrderedDict is not None:
- metadata = OrderedDict()
- else:
- metadata = {}
+ def create_post(self, path, onefile=False, is_page=False, **kw):
+ metadata = OrderedDict()
metadata.update(self.default_metadata)
metadata.update(kw)
makedirs(os.path.dirname(path))
@@ -70,4 +67,4 @@ class CompileHtml(PageCompiler):
for k, v in metadata.items():
fd.write('.. {0}: {1}\n'.format(k, v))
fd.write('-->\n\n')
- fd.write("\n<p>Write your post here.</p>\n")
+ fd.write("\n<p>Write your {0} here.</p>\n".format('page' if is_page else 'post'))
diff --git a/nikola/plugins/compile/ipynb/__init__.py b/nikola/plugins/compile/ipynb/__init__.py
index 5f2f0b3..2b1fd28 100644
--- a/nikola/plugins/compile/ipynb/__init__.py
+++ b/nikola/plugins/compile/ipynb/__init__.py
@@ -44,7 +44,7 @@ from nikola.utils import makedirs, req_missing
try:
from collections import OrderedDict
except ImportError:
- OrderedDict = None # NOQA
+ OrderedDict = dict # NOQA
class CompileIPynb(PageCompiler):
@@ -66,11 +66,8 @@ class CompileIPynb(PageCompiler):
(body, resources) = exportHtml.from_notebook_node(nb_json)
out_file.write(body)
- def create_post(self, path, onefile=False, **kw):
- if OrderedDict is not None:
- metadata = OrderedDict()
- else:
- metadata = {}
+ def create_post(self, path, onefile=False, is_page=False, **kw):
+ metadata = OrderedDict()
metadata.update(self.default_metadata)
metadata.update(kw)
d_name = os.path.dirname(path)
@@ -81,7 +78,7 @@ class CompileIPynb(PageCompiler):
metadata['date'], metadata['tags'],
metadata['link'],
metadata['description'], metadata['type'])))
- print("Your post's metadata is at: ", meta_path)
+ print("Your {0}'s metadata is at: {1}".format('page' if is_page else 'post', meta_path))
with codecs.open(path, "wb+", "utf8") as fd:
fd.write("""{
"metadata": {
diff --git a/nikola/plugins/compile/markdown/__init__.py b/nikola/plugins/compile/markdown/__init__.py
index 1376b11..d0fa66a 100644
--- a/nikola/plugins/compile/markdown/__init__.py
+++ b/nikola/plugins/compile/markdown/__init__.py
@@ -54,7 +54,7 @@ except ImportError:
try:
from collections import OrderedDict
except ImportError:
- OrderedDict = None # NOQA
+ OrderedDict = dict # NOQA
from nikola.plugin_categories import PageCompiler
from nikola.utils import makedirs, req_missing
@@ -81,11 +81,8 @@ class CompileMarkdown(PageCompiler):
output = markdown(data, self.extensions)
out_file.write(output)
- def create_post(self, path, onefile=False, **kw):
- if OrderedDict is not None:
- metadata = OrderedDict()
- else:
- metadata = {}
+ def create_post(self, path, onefile=False, is_page=False, **kw):
+ metadata = OrderedDict()
metadata.update(self.default_metadata)
metadata.update(kw)
makedirs(os.path.dirname(path))
@@ -95,4 +92,4 @@ class CompileMarkdown(PageCompiler):
for k, v in metadata.items():
fd.write('.. {0}: {1}\n'.format(k, v))
fd.write('-->\n\n')
- fd.write("Write your post here.")
+ fd.write("Write your {0} here.".format('page' if is_page else 'post'))
diff --git a/nikola/plugins/compile/misaka.py b/nikola/plugins/compile/misaka.py
index 8777ffc..4951c9f 100644
--- a/nikola/plugins/compile/misaka.py
+++ b/nikola/plugins/compile/misaka.py
@@ -40,7 +40,7 @@ except ImportError:
try:
from collections import OrderedDict
except ImportError:
- OrderedDict = None # NOQA
+ OrderedDict = dict # NOQA
gist_extension = None
podcast_extension = None
@@ -73,11 +73,8 @@ class CompileMisaka(PageCompiler):
output = misaka.html(data, extensions=self.ext)
out_file.write(output)
- def create_post(self, path, onefile=False, **kw):
- if OrderedDict is not None:
- metadata = OrderedDict()
- else:
- metadata = {}
+ def create_post(self, path, onefile=False, is_page=False, **kw):
+ metadata = OrderedDict()
metadata.update(self.default_metadata)
metadata.update(kw)
makedirs(os.path.dirname(path))
@@ -87,4 +84,4 @@ class CompileMisaka(PageCompiler):
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("\nWrite your {0} here.".format('page' if is_page else 'post'))
diff --git a/nikola/plugins/compile/pandoc.py b/nikola/plugins/compile/pandoc.py
index 57c7d71..654c7c8 100644
--- a/nikola/plugins/compile/pandoc.py
+++ b/nikola/plugins/compile/pandoc.py
@@ -40,7 +40,7 @@ from nikola.utils import req_missing, makedirs
try:
from collections import OrderedDict
except ImportError:
- OrderedDict = None # NOQA
+ OrderedDict = dict # NOQA
class CompilePandoc(PageCompiler):
@@ -56,11 +56,8 @@ class CompilePandoc(PageCompiler):
if e.strreror == 'No such file or directory':
req_missing(['pandoc'], 'build this site (compile with pandoc)', python=False)
- def create_post(self, path, onefile=False, **kw):
- if OrderedDict is not None:
- metadata = OrderedDict()
- else:
- metadata = {}
+ def create_post(self, path, onefile=False, is_page=False, **kw):
+ metadata = OrderedDict()
metadata.update(self.default_metadata)
metadata.update(kw)
makedirs(os.path.dirname(path))
@@ -70,4 +67,4 @@ class CompilePandoc(PageCompiler):
for k, v in metadata.items():
fd.write('.. {0}: {1}\n'.format(k, v))
fd.write('-->\n\n')
- fd.write("Write your post here.")
+ fd.write("Write your {0} here.".format('page' if is_page else 'post'))
diff --git a/nikola/plugins/compile/php.py b/nikola/plugins/compile/php.py
index 14b80e8..0a652a6 100644
--- a/nikola/plugins/compile/php.py
+++ b/nikola/plugins/compile/php.py
@@ -38,7 +38,7 @@ from nikola.utils import makedirs
try:
from collections import OrderedDict
except ImportError:
- OrderedDict = None # NOQA
+ OrderedDict = dict # NOQA
class CompilePhp(PageCompiler):
@@ -50,11 +50,8 @@ class CompilePhp(PageCompiler):
makedirs(os.path.dirname(dest))
shutil.copyfile(source, dest)
- def create_post(self, path, onefile=False, **kw):
- if OrderedDict is not None:
- metadata = OrderedDict()
- else:
- metadata = {}
+ def create_post(self, path, onefile=False, is_page=False, **kw):
+ metadata = OrderedDict()
metadata.update(self.default_metadata)
metadata.update(kw)
os.makedirs(os.path.dirname(path))
@@ -64,7 +61,7 @@ class CompilePhp(PageCompiler):
for k, v in metadata.items():
fd.write('.. {0}: {1}\n'.format(k, v))
fd.write('-->\n\n')
- fd.write("\n<p>Write your post here.</p>")
+ fd.write("\n<p>Write your {0} here.</p>".format('page' if is_page else 'post'))
def extension(self):
return ".php"
diff --git a/nikola/plugins/compile/rest/__init__.py b/nikola/plugins/compile/rest/__init__.py
index 50b37cf..9a4e19b 100644
--- a/nikola/plugins/compile/rest/__init__.py
+++ b/nikola/plugins/compile/rest/__init__.py
@@ -43,7 +43,7 @@ except ImportError:
try:
from collections import OrderedDict
except ImportError:
- OrderedDict = None # NOQA
+ OrderedDict = dict # NOQA
from nikola.plugin_categories import PageCompiler
from nikola.utils import get_logger, makedirs, req_missing
@@ -102,11 +102,8 @@ class CompileRest(PageCompiler):
else:
return False
- def create_post(self, path, onefile=False, **kw):
- if OrderedDict is not None:
- metadata = OrderedDict()
- else:
- metadata = {}
+ def create_post(self, path, onefile=False, is_page=False, **kw):
+ metadata = OrderedDict()
metadata.update(self.default_metadata)
metadata.update(kw)
makedirs(os.path.dirname(path))
@@ -114,7 +111,7 @@ class CompileRest(PageCompiler):
if onefile:
for k, v in metadata.items():
fd.write('.. {0}: {1}\n'.format(k, v))
- fd.write("\nWrite your post here.")
+ fd.write("\nWrite your {0} here.".format('page' if is_page else 'post'))
def set_site(self, site):
for plugin_info in site.plugin_manager.getPluginsOfCategory("RestExtension"):
@@ -174,6 +171,38 @@ class NikolaReader(docutils.readers.standalone.Reader):
def add_node(node, visit_function=None, depart_function=None):
+ """
+ Register a Docutils node class.
+ This function is completely optional. It is a same concept as
+ `Sphinx add_node function <http://sphinx-doc.org/ext/appapi.html#sphinx.application.Sphinx.add_node>`_.
+
+ For example::
+
+ class Plugin(RestExtension):
+
+ name = "rest_math"
+
+ def set_site(self, site):
+ self.site = site
+ directives.register_directive('math', MathDirective)
+ add_node(MathBlock, visit_Math, depart_Math)
+ return super(Plugin, self).set_site(site)
+
+ class MathDirective(Directive):
+ def run(self):
+ node = MathBlock()
+ return [node]
+
+ class Math(docutils.nodes.Element): pass
+
+ def visit_Math(self, node):
+ self.body.append(self.starttag(node, 'math'))
+
+ def depart_Math(self, node):
+ self.body.append('</math>')
+
+ For full example, you can refer to `Microdata plugin <http://plugins.getnikola.com/#microdata>`_
+ """
docutils.nodes._add_node_class_names([node.__name__])
if visit_function:
setattr(docutils.writers.html4css1.HTMLTranslator, 'visit_' + node.__name__, visit_function)
diff --git a/nikola/plugins/compile/rest/listing.py b/nikola/plugins/compile/rest/listing.py
index ecf885f..d70e02d 100644
--- a/nikola/plugins/compile/rest/listing.py
+++ b/nikola/plugins/compile/rest/listing.py
@@ -56,6 +56,18 @@ except ImportError: # docutils < 0.9 (Debian Sid For The Loss)
from nikola.plugin_categories import RestExtension
+# Add sphinx compatibility option
+CodeBlock.option_spec['linenos'] = directives.unchanged
+
+
+class FlexibleCodeBlock(CodeBlock):
+
+ def run(self):
+ if 'linenos' in self.options:
+ self.options['number-lines'] = self.options['linenos']
+ return super(FlexibleCodeBlock, self).run()
+CodeBlock = FlexibleCodeBlock
+
class Plugin(RestExtension):
@@ -71,6 +83,10 @@ class Plugin(RestExtension):
directives.register_directive('listing', Listing)
return super(Plugin, self).set_site(site)
+# Add sphinx compatibility option
+listing_spec = Include.option_spec
+listing_spec['linenos'] = directives.unchanged
+
class Listing(Include):
""" listing directive: create a highlighted block of code from a file in listings/
@@ -84,6 +100,7 @@ class Listing(Include):
has_content = False
required_arguments = 1
optional_arguments = 1
+ option_spec = listing_spec
def run(self):
fname = self.arguments.pop(0)
@@ -91,6 +108,8 @@ class Listing(Include):
fpath = os.path.join('listings', fname)
self.arguments.insert(0, fpath)
self.options['code'] = lang
+ if 'linenos' in self.options:
+ self.options['number-lines'] = self.options['linenos']
with codecs_open(fpath, 'rb+', 'utf8') as fileobject:
self.content = fileobject.read().splitlines()
self.state.document.settings.record_dependencies.add(fpath)
diff --git a/nikola/plugins/compile/textile.py b/nikola/plugins/compile/textile.py
index 73f35c0..1679831 100644
--- a/nikola/plugins/compile/textile.py
+++ b/nikola/plugins/compile/textile.py
@@ -41,7 +41,7 @@ from nikola.utils import makedirs, req_missing
try:
from collections import OrderedDict
except ImportError:
- OrderedDict = None # NOQA
+ OrderedDict = dict # NOQA
class CompileTextile(PageCompiler):
@@ -62,11 +62,8 @@ class CompileTextile(PageCompiler):
output = textile(data, head_offset=1)
out_file.write(output)
- def create_post(self, path, onefile=False, **kw):
- if OrderedDict is not None:
- metadata = OrderedDict()
- else:
- metadata = {}
+ def create_post(self, path, onefile=False, is_page=False, **kw):
+ metadata = OrderedDict()
metadata.update(self.default_metadata)
metadata.update(kw)
makedirs(os.path.dirname(path))
@@ -76,4 +73,4 @@ class CompileTextile(PageCompiler):
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.")
+ fd.write("\nWrite your {0} here.".format('page' if is_page else 'post'))
diff --git a/nikola/plugins/compile/txt2tags.py b/nikola/plugins/compile/txt2tags.py
index 8c9724e..bb6afa5 100644
--- a/nikola/plugins/compile/txt2tags.py
+++ b/nikola/plugins/compile/txt2tags.py
@@ -43,7 +43,7 @@ except ImportError:
try:
from collections import OrderedDict
except ImportError:
- OrderedDict = None # NOQA
+ OrderedDict = dict # NOQA
from nikola.plugin_categories import PageCompiler
from nikola.utils import makedirs, req_missing
@@ -62,11 +62,8 @@ class CompileTxt2tags(PageCompiler):
cmd = ["-t", "html", "--no-headers", "--outfile", dest, source]
txt2tags(cmd)
- def create_post(self, path, onefile=False, **kw):
- if OrderedDict is not None:
- metadata = OrderedDict()
- else:
- metadata = {}
+ def create_post(self, path, onefile=False, is_page=False, **kw):
+ metadata = OrderedDict()
metadata.update(self.default_metadata)
metadata.update(kw)
makedirs(os.path.dirname(path))
@@ -76,4 +73,4 @@ class CompileTxt2tags(PageCompiler):
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("\nWrite your {0} here.".format('page' if is_page else 'post'))
diff --git a/nikola/plugins/compile/wiki.py b/nikola/plugins/compile/wiki.py
index 9a365fa..f4858c7 100644
--- a/nikola/plugins/compile/wiki.py
+++ b/nikola/plugins/compile/wiki.py
@@ -40,7 +40,7 @@ from nikola.plugin_categories import PageCompiler
try:
from collections import OrderedDict
except ImportError:
- OrderedDict = None # NOQA
+ OrderedDict = dict # NOQA
from nikola.utils import makedirs, req_missing
@@ -62,11 +62,8 @@ class CompileWiki(PageCompiler):
output = HtmlEmitter(document).emit()
out_file.write(output)
- def create_post(self, path, onefile=False, **kw):
- if OrderedDict is not None:
- metadata = OrderedDict()
- else:
- metadata = {}
+ def create_post(self, path, onefile=False, is_page=False, **kw):
+ metadata = OrderedDict()
metadata.update(self.default_metadata)
metadata.update(kw)
makedirs(os.path.dirname(path))
@@ -75,4 +72,4 @@ class CompileWiki(PageCompiler):
'one-file format is not possible, use the -2 '
'option.')
with codecs.open(path, "wb+", "utf8") as fd:
- fd.write("Write your post here.")
+ fd.write("Write your {0} here.".format('page' if is_page else 'post'))
diff --git a/nikola/plugins/loghandler/stderr.py b/nikola/plugins/loghandler/stderr.py
index 75acffc..fdc892e 100644
--- a/nikola/plugins/loghandler/stderr.py
+++ b/nikola/plugins/loghandler/stderr.py
@@ -26,10 +26,10 @@
from nikola.plugin_categories import SignalHandler
from blinker import signal
-import logbook
import os
from nikola import DEBUG
+from nikola.utils import ColorfulStderrHandler
class StderrHandler(SignalHandler):
@@ -40,7 +40,7 @@ class StderrHandler(SignalHandler):
"""Attach the handler to the logger."""
conf = self.site.config.get('LOGGING_HANDLERS').get('stderr')
if conf or os.getenv('NIKOLA_DEBUG'):
- self.site.loghandlers.append(logbook.StderrHandler(
+ self.site.loghandlers.append(ColorfulStderrHandler(
level='DEBUG' if DEBUG else conf.get('loglevel', 'WARNING').upper(),
format_string=u'[{record.time:%Y-%m-%dT%H:%M:%SZ}] {record.level_name}: {record.channel}: {record.message}'
))
diff --git a/nikola/plugins/task/build_less.py b/nikola/plugins/task/build_less.py
index 14a53f9..a672282 100644
--- a/nikola/plugins/task/build_less.py
+++ b/nikola/plugins/task/build_less.py
@@ -46,22 +46,34 @@ class BuildLess(Task):
def gen_tasks(self):
"""Generate CSS out of LESS sources."""
self.compiler_name = self.site.config['LESS_COMPILER']
+ self.compiler_options = self.site.config['LESS_OPTIONS']
kw = {
'cache_folder': self.site.config['CACHE_FOLDER'],
'themes': self.site.THEMES,
}
+ tasks = {}
# Find where in the theme chain we define the LESS targets
# There can be many *.less in the folder, but we only will build
# the ones listed in less/targets
- targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES)
+ if os.path.isfile(os.path.join(self.sources_folder, "targets")):
+ targets_path = os.path.join(self.sources_folder, "targets")
+ else:
+ targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES)
try:
with codecs.open(targets_path, "rb", "utf-8") as inf:
targets = [x.strip() for x in inf.readlines()]
except Exception:
targets = []
+ for task in utils.copy_tree(self.sources_folder, os.path.join(kw['cache_folder'], self.sources_folder)):
+ if task['name'] in tasks:
+ continue
+ task['basename'] = 'prepare_less_sources'
+ tasks[task['name']] = task
+ yield task
+
for theme_name in kw['themes']:
src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder)
for task in utils.copy_tree(src, os.path.join(kw['cache_folder'], self.sources_folder)):
@@ -82,7 +94,7 @@ class BuildLess(Task):
src = os.path.join(kw['cache_folder'], self.sources_folder, target)
run_in_shell = sys.platform == 'win32'
try:
- compiled = subprocess.check_output([self.compiler_name, src], shell=run_in_shell)
+ compiled = subprocess.check_output([self.compiler_name] + self.compiler_options + [src], shell=run_in_shell)
except OSError:
utils.req_missing([self.compiler_name],
'build LESS files (and use this theme)',
diff --git a/nikola/plugins/task/build_sass.py b/nikola/plugins/task/build_sass.py
index 7575505..becc843 100644
--- a/nikola/plugins/task/build_sass.py
+++ b/nikola/plugins/task/build_sass.py
@@ -47,26 +47,41 @@ class BuildSass(Task):
"""Generate CSS out of Sass sources."""
self.logger = utils.get_logger('build_sass', self.site.loghandlers)
self.compiler_name = self.site.config['SASS_COMPILER']
+ self.compiler_options = self.site.config['SASS_OPTIONS']
kw = {
'cache_folder': self.site.config['CACHE_FOLDER'],
'themes': self.site.THEMES,
}
+ tasks = {}
# Find where in the theme chain we define the Sass targets
# There can be many *.sass/*.scss in the folder, but we only
# will build the ones listed in sass/targets
- targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES)
+ if os.path.isfile(os.path.join(self.sources_folder, "targets")):
+ targets_path = os.path.join(self.sources_folder, "targets")
+ else:
+ targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES)
try:
with codecs.open(targets_path, "rb", "utf-8") as inf:
targets = [x.strip() for x in inf.readlines()]
except Exception:
targets = []
+ for task in utils.copy_tree(self.sources_folder, os.path.join(kw['cache_folder'], self.sources_folder)):
+ if task['name'] in tasks:
+ continue
+ task['basename'] = 'prepare_sass_sources'
+ tasks[task['name']] = task
+ yield task
+
for theme_name in kw['themes']:
src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder)
for task in utils.copy_tree(src, os.path.join(kw['cache_folder'], self.sources_folder)):
+ if task['name'] in tasks:
+ continue
task['basename'] = 'prepare_sass_sources'
+ tasks[task['name']] = task
yield task
# Build targets and write CSS files
@@ -83,7 +98,7 @@ class BuildSass(Task):
run_in_shell = sys.platform == 'win32'
src = os.path.join(kw['cache_folder'], self.sources_folder, target)
try:
- compiled = subprocess.check_output([self.compiler_name, src], shell=run_in_shell)
+ compiled = subprocess.check_output([self.compiler_name] + self.compiler_options + [src], shell=run_in_shell)
except OSError:
utils.req_missing([self.compiler_name],
'build Sass files (and use this theme)',
diff --git a/nikola/plugins/task/bundles.py b/nikola/plugins/task/bundles.py
index b035b97..fcfaf42 100644
--- a/nikola/plugins/task/bundles.py
+++ b/nikola/plugins/task/bundles.py
@@ -87,8 +87,8 @@ class BuildBundles(LateTask):
output_path = os.path.join(kw['output_folder'], name)
dname = os.path.dirname(name)
file_dep = [os.path.join(kw['output_folder'], dname, fname)
- for fname in files]
- file_dep = filter(os.path.isfile, file_dep) # removes missing files
+ for fname in files if
+ utils.get_asset_path(fname, self.site.THEMES, self.site.config['FILES_FOLDERS'])]
task = {
'file_dep': list(file_dep),
'task_dep': ['copy_assets'],
diff --git a/nikola/plugins/task/copy_assets.py b/nikola/plugins/task/copy_assets.py
index 21f1f85..93b7fb3 100644
--- a/nikola/plugins/task/copy_assets.py
+++ b/nikola/plugins/task/copy_assets.py
@@ -75,8 +75,8 @@ class CopyAssets(Task):
formatter = get_formatter_by_name('html', style=kw["code_color_scheme"])
utils.makedirs(os.path.dirname(code_css_path))
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;}")
+ outf.write(formatter.get_style_defs(['pre.code', 'div.code pre']))
+ outf.write("\ntable.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}\n")
task = {
'basename': self.name,
diff --git a/nikola/plugins/task/galleries.py b/nikola/plugins/task/galleries.py
index 6977eab..880d47c 100644
--- a/nikola/plugins/task/galleries.py
+++ b/nikola/plugins/task/galleries.py
@@ -176,6 +176,7 @@ class Galleries(Task):
thumbs = ['.thumbnail'.join(os.path.splitext(p)) for p in image_list]
thumbs = [os.path.join(self.kw['output_folder'], t) for t in thumbs]
+ dest_img_list = [os.path.join(self.kw['output_folder'], t) for t in image_list]
folders = []
@@ -193,7 +194,8 @@ class Galleries(Task):
context["folders"] = folders
context["crumbs"] = crumbs
context["permalink"] = self.site.link(
- "gallery", os.path.basename(gallery), lang)
+ "gallery", os.path.basename(
+ os.path.relpath(gallery, self.kw['gallery_path'])), lang)
# FIXME: use kw
context["enable_comments"] = (
self.site.config["COMMENTS_IN_GALLERIES"])
@@ -219,7 +221,7 @@ class Galleries(Task):
template_name,
dst,
context,
- image_list,
+ dest_img_list,
thumbs,
file_dep))],
'clean': True,
diff --git a/nikola/plugins/task/listings.py b/nikola/plugins/task/listings.py
index d8ed43b..86be6c4 100644
--- a/nikola/plugins/task/listings.py
+++ b/nikola/plugins/task/listings.py
@@ -55,7 +55,7 @@ class Listings(Task):
}
# Things to ignore in listings
- ignored_extensions = (".pyc",)
+ ignored_extensions = (".pyc", ".pyo")
def render_listing(in_name, out_name, folders=[], files=[]):
if in_name:
diff --git a/nikola/plugins/task/localsearch/files/assets/css/img/search.png b/nikola/plugins/task/localsearch/files/assets/css/img/search.png
index 9ab0f2c..9ab0f2c 100755..100644
--- a/nikola/plugins/task/localsearch/files/assets/css/img/search.png
+++ b/nikola/plugins/task/localsearch/files/assets/css/img/search.png
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
index 2230193..2230193 100755..100644
--- a/nikola/plugins/task/localsearch/files/assets/css/tipuesearch.css
+++ b/nikola/plugins/task/localsearch/files/assets/css/tipuesearch.css
diff --git a/nikola/plugins/task/localsearch/files/tipue_search.html b/nikola/plugins/task/localsearch/files/tipue_search.html
index 789fbe5..789fbe5 100755..100644
--- a/nikola/plugins/task/localsearch/files/tipue_search.html
+++ b/nikola/plugins/task/localsearch/files/tipue_search.html
diff --git a/nikola/plugins/task/rss.py b/nikola/plugins/task/rss.py
index e5f7548..9e4204c 100644
--- a/nikola/plugins/task/rss.py
+++ b/nikola/plugins/task/rss.py
@@ -58,6 +58,11 @@ class GenerateRSS(Task):
"feed_length": self.site.config['FEED_LENGTH'],
}
self.site.scan_posts()
+ # Check for any changes in the state of use_in_feeds for any post.
+ # Issue #934
+ kw['use_in_feeds_status'] = ''.join(
+ ['T' if x.use_in_feeds else 'F' for x in self.site.timeline]
+ )
yield self.group_task()
for lang in kw["translations"]:
output_name = os.path.join(kw['output_folder'],
diff --git a/nikola/plugins/task/sitemap/__init__.py b/nikola/plugins/task/sitemap/__init__.py
index 0164000..147bd50 100644
--- a/nikola/plugins/task/sitemap/__init__.py
+++ b/nikola/plugins/task/sitemap/__init__.py
@@ -144,26 +144,35 @@ class Sitemap(LateTask):
def write_sitemap():
# Have to rescan, because files may have been added between
# task dep scanning and task execution
- scan_locs()
with codecs.open(sitemap_path, 'wb+', 'utf8') as outf:
outf.write(header)
for k in sorted(locs.keys()):
outf.write(locs[k])
outf.write("</urlset>")
- # Other tasks can depend on this output, instead of having
- # to scan locations.
+
+ # Yield a task to calculate the dependencies of the sitemap
+ # Other tasks can depend on this output, instead of having
+ # to scan locations.
+ def scan_locs_task():
+ scan_locs()
return {'locations': list(locs.keys())}
- scan_locs()
+ yield {
+ "basename": "_scan_locs",
+ "name": "sitemap",
+ "actions": [(scan_locs_task)]
+ }
+
yield self.group_task()
task = {
"basename": "sitemap",
"name": sitemap_path,
"targets": [sitemap_path],
"actions": [(write_sitemap,)],
- "uptodate": [config_changed({1: kw, 2: locs})],
+ "uptodate": [config_changed(kw)],
"clean": True,
"task_dep": ["render_site"],
+ "calc_dep": ["_scan_locs:sitemap"],
}
yield task
diff --git a/nikola/plugins/task/tags.py b/nikola/plugins/task/tags.py
index a2444ec..f6b8234 100644
--- a/nikola/plugins/task/tags.py
+++ b/nikola/plugins/task/tags.py
@@ -311,9 +311,17 @@ class RenderTags(Task):
self.site.config['INDEX_FILE']] if _f]
def tag_path(self, name, lang):
- return [_f for _f in [self.site.config['TRANSLATIONS'][lang],
- self.site.config['TAG_PATH'], self.slugify_name(name) + ".html"] if
- _f]
+ if self.site.config['PRETTY_URLS']:
+ return [_f for _f in [
+ self.site.config['TRANSLATIONS'][lang],
+ self.site.config['TAG_PATH'],
+ self.slugify_name(name),
+ self.site.config['INDEX_FILE']] if _f]
+ else:
+ return [_f for _f in [
+ self.site.config['TRANSLATIONS'][lang],
+ self.site.config['TAG_PATH'],
+ self.slugify_name(name) + ".html"] if _f]
def tag_rss_path(self, name, lang):
return [_f for _f in [self.site.config['TRANSLATIONS'][lang],
diff --git a/nikola/plugins/template/jinja.py b/nikola/plugins/template/jinja.py
index 17c33d4..f14adfe 100644
--- a/nikola/plugins/template/jinja.py
+++ b/nikola/plugins/template/jinja.py
@@ -61,6 +61,11 @@ class JinjaTemplates(TemplateSystem):
self.lookup.loader = jinja2.FileSystemLoader(directories,
encoding='utf-8')
+ def set_site(self, site):
+ """Sets the site."""
+ self.site = site
+ self.lookup.filters.update(self.site.config['TEMPLATE_FILTERS'])
+
def render_template(self, template_name, output_name, context):
"""Render the template into output_name using context."""
if jinja2 is None:
diff --git a/nikola/plugins/template/mako.py b/nikola/plugins/template/mako.py
index 45f4335..5a23230 100644
--- a/nikola/plugins/template/mako.py
+++ b/nikola/plugins/template/mako.py
@@ -49,6 +49,7 @@ class MakoTemplates(TemplateSystem):
lookup = None
cache = {}
+ filters = {}
def get_deps(self, filename):
text = util.read_file(filename)
@@ -81,6 +82,11 @@ class MakoTemplates(TemplateSystem):
module_directory=cache_dir,
output_encoding='utf-8')
+ def set_site(self, site):
+ """Sets the site."""
+ self.site = site
+ self.filters.update(self.site.config['TEMPLATE_FILTERS'])
+
def render_template(self, template_name, output_name, context):
"""Render the template into output_name using context."""
context['striphtml'] = striphtml
@@ -95,6 +101,8 @@ class MakoTemplates(TemplateSystem):
def render_template_to_string(self, template, context):
""" Render template to a string using context. """
+ context = context.update(self.filters)
+
return Template(template).render(**context)
def template_deps(self, template_name):
diff --git a/nikola/post.py b/nikola/post.py
index 810474b..5cf7236 100644
--- a/nikola/post.py
+++ b/nikola/post.py
@@ -194,6 +194,9 @@ class Post(object):
# If mathjax is a tag, then enable mathjax rendering support
self.is_mathjax = 'mathjax' in self.tags
+ def __repr__(self):
+ return '<Post: {0}>'.format(self.source_path)
+
def _has_pretty_url(self, lang):
if self.pretty_urls and \
self.meta[lang].get('pretty_url', '') != 'False' and \
@@ -493,7 +496,7 @@ class Post(object):
pieces = [_f for _f in pieces if _f and _f != '.']
link = '/' + '/'.join(pieces)
if absolute:
- link = urljoin(self.base_url, link)
+ link = urljoin(self.base_url, link[1:])
index_len = len(self.index_file)
if self.strip_indexes and link[-(1 + index_len):] == '/' + self.index_file:
return link[:-index_len]
diff --git a/nikola/utils.py b/nikola/utils.py
index 0c3b4c0..46e159e 100644
--- a/nikola/utils.py
+++ b/nikola/utils.py
@@ -35,7 +35,6 @@ import locale
import logging
import os
import re
-import codecs
import json
import shutil
import subprocess
@@ -47,7 +46,7 @@ except ImportError:
pass
import logbook
-from logbook.more import ExceptionHandler
+from logbook.more import ExceptionHandler, ColorizedStderrHandler
import pytz
from . import DEBUG
@@ -57,6 +56,15 @@ class ApplicationWarning(Exception):
pass
+class ColorfulStderrHandler(ColorizedStderrHandler):
+ """Stream handler with colors."""
+ _colorful = False
+
+ def should_colorize(self, record):
+ """Inform about colorization using the value obtained from Nikola."""
+ return self._colorful
+
+
def get_logger(name, handlers):
"""Get a logger with handlers attached."""
l = logbook.Logger(name)
@@ -68,7 +76,7 @@ def get_logger(name, handlers):
return l
-STDERR_HANDLER = [logbook.StderrHandler(
+STDERR_HANDLER = [ColorfulStderrHandler(
level=logbook.NOTICE if not DEBUG else logbook.DEBUG,
format_string=u'[{record.time:%Y-%m-%dT%H:%M:%SZ}] {record.level_name}: {record.channel}: {record.message}'
)]
@@ -126,11 +134,11 @@ from unidecode import unidecode
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',
- 'get_tzname', 'get_asset_path', '_reload', 'unicode_str', 'bytes_str',
- 'unichr', 'Functionary', 'LocaleBorg', 'sys_encode', 'sys_decode',
- 'makedirs', 'get_parent_theme_name', 'ExtendedRSS2', 'demote_headers',
+ 'copy_file', 'slugify', 'unslugify', 'to_datetime', 'apply_filters',
+ 'config_changed', 'get_crumbs', 'get_tzname', 'get_asset_path',
+ '_reload', 'unicode_str', 'bytes_str', 'unichr', 'Functionary',
+ 'LocaleBorg', 'sys_encode', 'sys_decode', 'makedirs',
+ 'get_parent_theme_name', 'ExtendedRSS2', 'demote_headers',
'get_translation_candidate']
@@ -313,10 +321,14 @@ def copy_tree(src, dst, link_cutoff=None):
dst_dir = os.path.join(dst, *root_parts[base_len:])
makedirs(dst_dir)
for src_name in files:
- if src_name == '.DS_Store':
+ if src_name in ('.DS_Store', 'Thumbs.db'):
continue
dst_file = os.path.join(dst_dir, src_name)
src_file = os.path.join(root, src_name)
+ if sys.version_info[0] == 2:
+ # Python2 prefers encoded str here
+ dst_file = sys_encode(dst_file)
+ src_file = sys_encode(src_file)
yield {
'name': str(dst_file),
'file_dep': [src_file],
@@ -326,45 +338,6 @@ def copy_tree(src, dst, link_cutoff=None):
}
-def generic_rss_renderer(lang, title, link, description, timeline, output_path,
- rss_teasers, feed_length=10, feed_url=None):
- """Takes all necessary data, and renders a RSS feed in output_path."""
- items = []
- for post in timeline[:feed_length]:
- args = {
- 'title': post.title(lang),
- 'link': post.permalink(lang, absolute=True),
- 'description': post.text(lang, teaser_only=rss_teasers, really_absolute=True),
- 'guid': post.permalink(lang, absolute=True),
- # PyRSS2Gen's pubDate is GMT time.
- 'pubDate': (post.date if post.date.tzinfo is None else
- post.date.astimezone(pytz.timezone('UTC'))),
- 'categories': post._tags.get(lang, []),
- 'author': post.meta('author'),
- }
-
- items.append(ExtendedItem(**args))
- rss_obj = ExtendedRSS2(
- title=title,
- link=link,
- description=description,
- lastBuildDate=datetime.datetime.now(),
- items=items,
- generator='Nikola <http://getnikola.com/>',
- language=lang
- )
- rss_obj.self_url = feed_url
- rss_obj.rss_attrs["xmlns:atom"] = "http://www.w3.org/2005/Atom"
- rss_obj.rss_attrs["xmlns:dc"] = "http://purl.org/dc/elements/1.1/"
- dst_dir = os.path.dirname(output_path)
- makedirs(dst_dir)
- with codecs.open(output_path, "wb+", "utf-8") as rss_file:
- data = rss_obj.to_xml(encoding='utf-8')
- if isinstance(data, bytes_str):
- data = data.decode('utf-8')
- rss_file.write(data)
-
-
def copy_file(source, dest, cutoff=None):
dst_dir = os.path.dirname(dest)
makedirs(dst_dir)
@@ -518,9 +491,11 @@ def get_tzname(dt):
def current_time(tzinfo=None):
- dt = datetime.datetime.now()
+ dt = datetime.datetime.utcnow()
if tzinfo is not None:
- dt = tzinfo.localize(dt)
+ dt = tzinfo.fromutc(dt)
+ else:
+ dt = pytz.UTC.localize(dt)
return dt
@@ -749,7 +724,11 @@ class LocaleBorg(object):
else: # Python 2
with calendar.TimeEncoding(self.locales[lang]):
s = calendar.month_name[month_no]
- s = s.decode(self.encodings[lang])
+ enc = self.encodings[lang]
+ if not enc:
+ enc = 'UTF-8'
+
+ s = s.decode(enc)
# paranoid about calendar ending in the wrong locale (windows)
self.set_locale(self.current_lang)
return s