summaryrefslogtreecommitdiffstats
path: root/nikola/post.py
diff options
context:
space:
mode:
Diffstat (limited to 'nikola/post.py')
-rw-r--r--nikola/post.py88
1 files changed, 68 insertions, 20 deletions
diff --git a/nikola/post.py b/nikola/post.py
index f8039e0..37e4241 100644
--- a/nikola/post.py
+++ b/nikola/post.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright © 2012-2015 Roberto Alsina and others.
+# Copyright © 2012-2016 Roberto Alsina and others.
# Permission is hereby granted, free of charge, to any
# person obtaining a copy of this software and associated
@@ -43,6 +43,7 @@ except ImportError:
from . import utils
+from blinker import signal
import dateutil.tz
import lxml.html
import natsort
@@ -51,7 +52,7 @@ try:
except ImportError:
pyphen = None
-from math import ceil
+from math import ceil # for reading time feature
# for tearDown with _reload we cannot use 'from import' to get forLocaleBorg
import nikola.utils
@@ -109,7 +110,6 @@ class Post(object):
self.base_url = self.config['BASE_URL']
self.is_draft = False
self.is_private = False
- self.is_mathjax = False
self.strip_indexes = self.config['STRIP_INDEXES']
self.index_file = self.config['INDEX_FILE']
self.pretty_urls = self.config['PRETTY_URLS']
@@ -136,6 +136,7 @@ class Post(object):
self._dependency_file_page = defaultdict(list)
self._dependency_uptodate_fragment = defaultdict(list)
self._dependency_uptodate_page = defaultdict(list)
+ self._depfile = defaultdict(list)
default_metadata, self.newstylemeta = get_meta(self, self.config['FILE_METADATA_REGEXP'], self.config['UNSLUGIFY_TITLES'])
@@ -160,8 +161,14 @@ class Post(object):
for lang in sorted(self.translated_to):
default_metadata.update(self.meta[lang])
+ # Load data field from metadata
+ self.data = Functionary(lambda: None, self.default_lang)
+ for lang in self.translations:
+ if self.meta[lang].get('data') is not None:
+ self.data[lang] = utils.load_data(self.meta[lang]['data'])
+
if 'date' not in default_metadata and not use_in_feeds:
- # For stories we don't *really* need a date
+ # For pages we don't *really* need a date
if self.config['__invariant__']:
default_metadata['date'] = datetime.datetime(2013, 12, 31, 23, 59, 59, tzinfo=tzinfo)
else:
@@ -169,7 +176,10 @@ class Post(object):
os.stat(self.source_path).st_ctime).replace(tzinfo=dateutil.tz.tzutc()).astimezone(tzinfo)
# If time zone is set, build localized datetime.
- self.date = to_datetime(self.meta[self.default_lang]['date'], tzinfo)
+ try:
+ self.date = to_datetime(self.meta[self.default_lang]['date'], tzinfo)
+ except ValueError:
+ raise ValueError("Invalid date '{0}' in file {1}".format(self.meta[self.default_lang]['date'], source_path))
if 'updated' not in default_metadata:
default_metadata['updated'] = default_metadata.get('date', None)
@@ -178,12 +188,11 @@ class Post(object):
if 'title' not in default_metadata or 'slug' not in default_metadata \
or 'date' not in default_metadata:
- raise OSError("You must set a title (found '{0}'), a slug (found "
- "'{1}') and a date (found '{2}')! [in file "
- "{3}]".format(default_metadata.get('title', None),
- default_metadata.get('slug', None),
- default_metadata.get('date', None),
- source_path))
+ raise ValueError("You must set a title (found '{0}'), a slug (found '{1}') and a date (found '{2}')! "
+ "[in file {3}]".format(default_metadata.get('title', None),
+ default_metadata.get('slug', None),
+ default_metadata.get('date', None),
+ source_path))
if 'type' not in default_metadata:
# default value is 'text'
@@ -223,9 +232,6 @@ class Post(object):
self.use_in_feeds = use_in_feeds and not is_draft and not is_private \
and not self.publish_later
- # If mathjax is a tag, or it's a ipynb post, then enable mathjax rendering support
- self.is_mathjax = ('mathjax' in self.tags) or (self.compiler.name == 'ipynb')
-
# Register potential extra dependencies
self.compiler.register_extra_dependencies(self)
@@ -259,6 +265,17 @@ class Post(object):
return False
@property
+ def is_mathjax(self):
+ """True if this post has the mathjax tag in the current language or is a python notebook."""
+ if self.compiler.name == 'ipynb':
+ return True
+ lang = nikola.utils.LocaleBorg().current_lang
+ if self.is_translation_available(lang):
+ return 'mathjax' in self.tags_for_language(lang)
+ # If it has math in ANY other language, enable it. Better inefficient than broken.
+ return 'mathjax' in self.alltags
+
+ @property
def alltags(self):
"""Return ALL the tags for this post."""
tags = []
@@ -416,6 +433,24 @@ class Post(object):
if add == 'page' or add == 'both':
self._dependency_uptodate_page[lang].append((is_callable, dependency))
+ def register_depfile(self, dep, dest=None, lang=None):
+ """Register a dependency in the dependency file."""
+ if not dest:
+ dest = self.translated_base_path(lang)
+ self._depfile[dest].append(dep)
+
+ @staticmethod
+ def write_depfile(dest, deps_list):
+ """Write a depfile for a given language."""
+ deps_path = dest + '.dep'
+ if deps_list:
+ deps_list = [p for p in deps_list if p != dest] # Don't depend on yourself (#1671)
+ with io.open(deps_path, "w+", encoding="utf8") as deps_file:
+ deps_file.write('\n'.join(deps_list))
+ else:
+ if os.path.isfile(deps_path):
+ os.unlink(deps_path)
+
def _get_dependencies(self, deps_list):
deps = []
for dep in deps_list:
@@ -448,6 +483,8 @@ class Post(object):
cand_3 = get_translation_candidate(self.config, self.metadata_path, lang)
if os.path.exists(cand_3):
deps.append(cand_3)
+ if self.meta('data', lang):
+ deps.append(self.meta('data', lang))
deps += self._get_dependencies(self._dependency_file_page[lang])
deps += self._get_dependencies(self._dependency_file_page[None])
return sorted(deps)
@@ -482,7 +519,15 @@ class Post(object):
self.compile_html(
self.translated_source_path(lang),
dest,
- self.is_two_file),
+ self.is_two_file)
+ Post.write_depfile(dest, self._depfile[dest])
+
+ signal('compiled').send({
+ 'source': self.translated_source_path(lang),
+ 'dest': dest,
+ 'post': self,
+ })
+
if self.meta('password'):
# TODO: get rid of this feature one day (v8?; warning added in v7.3.0.)
LOGGER.warn("The post {0} is using the `password` attribute, which may stop working in the future.")
@@ -788,7 +833,7 @@ class Post(object):
slug = slug[0]
else:
slug = self.meta[lang]['section'].split(',')[0] if 'section' in self.meta[lang] else self.messages[lang]["Uncategorized"]
- return utils.slugify(slug)
+ return utils.slugify(slug, lang)
def permalink(self, lang=None, absolute=False, extension='.html', query=None):
"""Return permalink for a post."""
@@ -864,7 +909,7 @@ def re_meta(line, match=None):
return (None,)
-def _get_metadata_from_filename_by_regex(filename, metadata_regexp, unslugify_titles):
+def _get_metadata_from_filename_by_regex(filename, metadata_regexp, unslugify_titles, lang):
"""Try to reed the metadata from the filename based on the given re.
This requires to use symbolic group names in the pattern.
@@ -879,7 +924,7 @@ def _get_metadata_from_filename_by_regex(filename, metadata_regexp, unslugify_ti
for key, value in match.groupdict().items():
k = key.lower().strip() # metadata must be lowercase
if k == 'title' and unslugify_titles:
- meta[k] = unslugify(value, discard_numbers=False)
+ meta[k] = unslugify(value, lang, discard_numbers=False)
else:
meta[k] = value
@@ -1043,7 +1088,8 @@ def get_meta(post, file_metadata_regexp=None, unslugify_titles=False, lang=None)
if file_metadata_regexp is not None:
meta.update(_get_metadata_from_filename_by_regex(post.source_path,
file_metadata_regexp,
- unslugify_titles))
+ unslugify_titles,
+ post.default_lang))
compiler_meta = {}
@@ -1062,7 +1108,7 @@ def get_meta(post, file_metadata_regexp=None, unslugify_titles=False, lang=None)
if 'slug' not in meta:
# If no slug is found in the metadata use the filename
meta['slug'] = slugify(unicode_str(os.path.splitext(
- os.path.basename(post.source_path))[0]))
+ os.path.basename(post.source_path))[0]), post.default_lang)
if 'title' not in meta:
# If no title is found, use the filename without extension
@@ -1081,6 +1127,7 @@ def hyphenate(dom, _lang):
lang = LEGAL_VALUES['PYPHEN_LOCALES'].get(_lang, pyphen.language_fallback(_lang))
else:
utils.req_missing(['pyphen'], 'hyphenate texts', optional=True)
+ hyphenator = None
if pyphen is not None and lang is not None:
# If pyphen does exist, we tell the user when configuring the site.
# If it does not support a language, we ignore it quietly.
@@ -1089,6 +1136,7 @@ def hyphenate(dom, _lang):
except KeyError:
LOGGER.error("Cannot find hyphenation dictoniaries for {0} (from {1}).".format(lang, _lang))
LOGGER.error("Pyphen cannot be installed to ~/.local (pip install --user).")
+ if hyphenator is not None:
for tag in ('p', 'li', 'span'):
for node in dom.xpath("//%s[not(parent::pre)]" % tag):
skip_node = False