Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge branch 'master' into generalization-of-taxonomies
  • Loading branch information
felixfontein committed Oct 26, 2016
2 parents 650affe + 9a09d5d commit b71a818
Show file tree
Hide file tree
Showing 17 changed files with 125 additions and 49 deletions.
9 changes: 9 additions & 0 deletions CHANGES.txt
Expand Up @@ -4,12 +4,21 @@ New in master
Bugfixes
--------

* Make "data" from global context available to templated shortcodes (Issue #2488)
* Don't crash if plugins is a file (Issue #2539)

Features
--------

* ``render_template`` and ``generic_renderer`` can now create HTML
fragments.
* Allow posts to set custom ``URL_TYPE`` by using the ``url_type``
meta tag (useful for HTML fragments inserted using JavaScript)
* Plugins can depend on other plugins being installed (Issue #2533)
* The destination folder in ``POSTS`` and ``PAGES`` can now be
translated (Issue #2116)
* Pass ``post`` object and ``lang`` to post compilers (Issue #2531)
* Pass ``url_type`` into template's context.

New in v7.8.1
=============
Expand Down
5 changes: 5 additions & 0 deletions docs/manual.txt
Expand Up @@ -1086,6 +1086,10 @@ In that case, the template engine used will be your theme's and the arguments yo
as well as the global context from your ``conf.py``, are available to the template you
are creating.

You can use anything defined in your confguration's ``GLOBAL_CONTEXT`` as variables in your
shortcode template, with a caveat: Because of an unfortunate implementation detail, data is called
"global_data" when used in a shortcode.

The Global Context and Data files
---------------------------------

Expand Down Expand Up @@ -1115,6 +1119,7 @@ JSON/YAML/TOML files and Nikola generates a large page with data from all data
files. (This is especially useful with some automatic rebuild feature, like
those documented in `Deployment`_)


Redirections
------------

Expand Down
2 changes: 1 addition & 1 deletion nikola/__main__.py
Expand Up @@ -155,7 +155,7 @@ def main(args=None):
req_missing(['freezegun'], 'perform invariant builds')

if config:
if os.path.exists('plugins') and not os.path.exists('plugins/__init__.py'):
if os.path.isdir('plugins') and not os.path.exists('plugins/__init__.py'):
with open('plugins/__init__.py', 'w') as fh:
fh.write('# Plugin modules go here.')

Expand Down
2 changes: 1 addition & 1 deletion nikola/data/themes/base-jinja/templates/base_helper.tmpl
Expand Up @@ -60,7 +60,7 @@ lang="{{ lang }}">
{% if use_cdn %}
<!--[if lt IE 9]><script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
{% else %}
<!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang) }}"></script><![endif]-->
<!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang, url_type) }}"></script><![endif]-->
{% endif %}

{{ extra_head_data }}
Expand Down
2 changes: 1 addition & 1 deletion nikola/data/themes/base/templates/base_helper.tmpl
Expand Up @@ -60,7 +60,7 @@ lang="${lang}">
%if use_cdn:
<!--[if lt IE 9]><script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
%else:
<!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang)}"></script><![endif]-->
<!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang, url_type)}"></script><![endif]-->
%endif

${extra_head_data}
Expand Down
Expand Up @@ -65,7 +65,7 @@ lang="{{ lang }}">
{% if use_cdn %}
<!--[if lt IE 9]><script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
{% else %}
<!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang) }}"></script><![endif]-->
<!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang, url_type) }}"></script><![endif]-->
{% endif %}

{{ extra_head_data }}
Expand Down
2 changes: 1 addition & 1 deletion nikola/data/themes/bootstrap3/templates/base_helper.tmpl
Expand Up @@ -65,7 +65,7 @@ lang="${lang}">
%if use_cdn:
<!--[if lt IE 9]><script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
%else:
<!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang)}"></script><![endif]-->
<!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang, url_type)}"></script><![endif]-->
%endif

${extra_head_data}
Expand Down
40 changes: 31 additions & 9 deletions nikola/nikola.py
Expand Up @@ -985,7 +985,7 @@ def init_plugins(self, commands_only=False, load_all=False):
self.disabled_compilers[p[-1].name] = p
utils.LOGGER.debug('Not loading unneeded compiler {}', p[-1].name)
if p[-1].name not in self.config['COMPILERS'] and \
p[-1].details.has_option('Nikola', 'plugincategory') and p[-1].details.get('Nikola', 'PluginCategory') == 'Compiler':
p[-1].details.has_option('Nikola', 'plugincategory') and p[-1].details.get('Nikola', 'PluginCategory') in ('Compiler', 'PageCompiler'):
bad_candidates.add(p)
self.disabled_compilers[p[-1].name] = p
utils.LOGGER.debug('Not loading unneeded compiler {}', p[-1].name)
Expand Down Expand Up @@ -1290,7 +1290,7 @@ def get_compiler(self, source_name):

return compiler

def render_template(self, template_name, output_name, context, url_type=None):
def render_template(self, template_name, output_name, context, url_type=None, is_fragment=False):
"""Render a template with the global context.
If ``output_name`` is None, will return a string and all URL
Expand All @@ -1301,6 +1301,9 @@ def render_template(self, template_name, output_name, context, url_type=None):
The argument ``url_type`` allows to override the ``URL_TYPE``
configuration.
If ``is_fragment`` is set to ``True``, a HTML fragment will
be rendered and not a whole HTML document.
"""
local_context = {}
local_context["template_name"] = template_name
Expand All @@ -1309,6 +1312,7 @@ def render_template(self, template_name, output_name, context, url_type=None):
for k in self._GLOBAL_CONTEXT_TRANSLATABLE:
local_context[k] = local_context[k](local_context['lang'])
local_context['is_rtl'] = local_context['lang'] in LEGAL_VALUES['RTL_LANGUAGES']
local_context['url_type'] = self.config['URL_TYPE'] if url_type is None else url_type
# string, arguments
local_context["formatmsg"] = lambda s, *a: s % a
for h in local_context['template_hooks'].values():
Expand Down Expand Up @@ -1336,9 +1340,18 @@ def render_template(self, template_name, output_name, context, url_type=None):

utils.makedirs(os.path.dirname(output_name))
parser = lxml.html.HTMLParser(remove_blank_text=True)
doc = lxml.html.document_fromstring(data, parser)
if is_fragment:
doc = lxml.html.fragment_fromstring(data, parser)
else:
doc = lxml.html.document_fromstring(data, parser)
self.rewrite_links(doc, src, context['lang'], url_type)
data = b'<!DOCTYPE html>\n' + lxml.html.tostring(doc, encoding='utf8', method='html', pretty_print=True)
if is_fragment:
# doc.text contains text before the first HTML, or None if there was no text
# The text after HTML elements is added by tostring() (because its implicit
# argument with_tail has default value True).
data = (doc.text or '').encode('utf-8') + b''.join([lxml.html.tostring(child, encoding='utf-8', method='html') for child in doc.iterchildren()])
else:
data = lxml.html.tostring(doc, encoding='utf8', method='html', pretty_print=True, doctype='<!DOCTYPE html>')
with open(output_name, "wb+") as post_file:
post_file.write(data)

Expand All @@ -1348,7 +1361,7 @@ def rewrite_links(self, doc, src, lang, url_type=None):
doc.rewrite_links(lambda dst: self.url_replacer(src, dst, lang, url_type), resolve_base_href=False)

# lxml ignores srcset in img and source elements, so do that by hand
objs = list(doc.xpath('(*//img|*//source)'))
objs = list(doc.xpath('(//img|//source)'))
for obj in objs:
if 'srcset' in obj.attrib:
urls = [u.strip() for u in obj.attrib['srcset'].split(',')]
Expand Down Expand Up @@ -1479,9 +1492,16 @@ def _make_renderfunc(self, t_data, fname=None):
keyword argument dict and then the latter provides the template
context.
Global context keys are made available as part of the context,
respecting locale.
As a special quirk, the "data" key from global_context is made
available as "global_data" because of name clobbering.
"""
def render_shortcode(*args, **kw):
context = self.GLOBAL_CONTEXT.copy()
context['global_data'] = context['data']
context.update(kw)
context['_args'] = args
context['lang'] = utils.LocaleBorg().current_lang
Expand Down Expand Up @@ -1999,7 +2019,7 @@ def scan_posts(self, really=False, ignore_quit=False, quiet=False):
sys.exit(1)
signal('scanned').send(self)

def generic_renderer(self, lang, output_name, template_name, filters, file_deps=None, uptodate_deps=None, context=None, context_deps_remove=None, post_deps_dict=None, url_type=None):
def generic_renderer(self, lang, output_name, template_name, filters, file_deps=None, uptodate_deps=None, context=None, context_deps_remove=None, post_deps_dict=None, url_type=None, is_fragment=False):
"""Helper function for rendering pages and post lists and other related pages.
lang is the current language.
Expand All @@ -2011,7 +2031,8 @@ def generic_renderer(self, lang, output_name, template_name, filters, file_deps=
context (optional) a dict used as a basis for the template context. The lang parameter will always be added.
context_deps_remove (optional) is a list of keys to remove from the context after using it as an uptodate dependency. This should name all keys containing non-trivial Python objects; they can be replaced by adding JSON-style dicts in post_deps_dict.
post_deps_dict (optional) is a dict merged into the copy of context which is used as an uptodate dependency.
url_type (optional) allows to override the ``URL_TYPE`` configuration
url_type (optional) allows to override the ``URL_TYPE`` configuration.
is_fragment (optional) allows to write a HTML fragment instead of a HTML document.
"""
utils.LocaleBorg().set_locale(lang)

Expand Down Expand Up @@ -2045,7 +2066,7 @@ def generic_renderer(self, lang, output_name, template_name, filters, file_deps=
'targets': [output_name],
'file_dep': file_deps,
'actions': [(self.render_template, [template_name, output_name,
context, url_type])],
context, url_type, is_fragment])],
'clean': True,
'uptodate': [config_changed(deps_dict, 'nikola.nikola.Nikola.generic_renderer')] + ([] if uptodate_deps is None else uptodate_deps)
}
Expand Down Expand Up @@ -2088,7 +2109,8 @@ def generic_page_renderer(self, lang, post, filters, context=None):
uptodate_deps=uptodate_deps,
context=context,
context_deps_remove=['post'],
post_deps_dict=deps_dict)
post_deps_dict=deps_dict,
url_type=post.url_type)

def generic_post_list_renderer(self, lang, posts, output_name, template_name, filters, extra_context):
"""Render pages with lists of posts."""
Expand Down
19 changes: 19 additions & 0 deletions nikola/plugins/command/plugin.py
Expand Up @@ -265,6 +265,7 @@ def do_install(self, url, name, show_install_notes=True):
'package manager.')
else:
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 '
Expand All @@ -280,6 +281,24 @@ def do_install(self, url, name, show_install_notes=True):

print('You have to install those yourself or through a package '
'manager.')

req_plug_path = os.path.join(dest_path, 'requirements-plugins.txt')
if os.path.exists(req_plug_path):
LOGGER.notice('This plugin requires other Nikola plugins.')
LOGGER.info('Installing plugins...')
try:
with io.open(req_plug_path, 'r', encoding='utf-8') as inf:
for plugname in inf.readlines():
self.do_install(url, plugname, show_install_notes)
except subprocess.CalledProcessError:
LOGGER.error('Could not install a plugin.')
print('Contents of the requirements-plugins.txt file:\n')
with io.open(req_plug_path, 'r', encoding='utf-8') as fh:
print(utils.indent(fh.read(), 4 * ' '))
print('You have to install those yourself manually.')
else:
LOGGER.info('Dependency installation succeeded.')

confpypath = os.path.join(dest_path, 'conf.py.sample')
if os.path.exists(confpypath) and show_install_notes:
LOGGER.notice('This plugin has a sample config file. Integrate it with yours in order to make this plugin work!')
Expand Down
4 changes: 4 additions & 0 deletions nikola/post.py
Expand Up @@ -249,6 +249,10 @@ def __init__(
self.use_in_feeds = use_in_feeds and not is_draft and not is_private \
and not self.publish_later

# Allow overriding URL_TYPE via meta
# The check is done here so meta dicts won’t change inside of
# generic_post_rendere
self.url_type = self.meta('url_type') or None
# Register potential extra dependencies
self.compiler.register_extra_dependencies(self)

Expand Down
File renamed without changes.
7 changes: 3 additions & 4 deletions snapcraft/edge/snapcraft.yaml
Expand Up @@ -11,11 +11,10 @@ apps:

parts:
nikola:
plugin: copy
files:
nikola.sh: nikola.sh
plugin: dump
source: script/
nikola-source:
plugin: python3
plugin: python
source: git://github.com/getnikola/nikola.git
requirements: requirements.txt
stage-packages:
Expand Down
3 changes: 3 additions & 0 deletions snapcraft/requirements.txt
Expand Up @@ -25,3 +25,6 @@ natsort>=3.5.2
requests>=2.2.0
husl>=4.0.2
piexif>=1.0.3
phpserialize==1.3
webassets==0.12.0

3 changes: 0 additions & 3 deletions snapcraft/stable/build.sh
@@ -1,6 +1,3 @@
#!/bin/sh
snapcraft
cp ../nikola.py prime/usr/bin/nikola
find prime/ -name '*.a' -exec rm {} \;
snapcraft

Expand Up @@ -18,4 +18,4 @@ export LC_ALL=$APPLOC
export LANG=$APPLOC
export LANGUAGE=${APPLANG%_*}

$SNAP/usr/bin/nikola "$@"
$SNAP/bin/nikola "$@"
51 changes: 36 additions & 15 deletions snapcraft/stable/snapcraft.yaml
Expand Up @@ -3,31 +3,52 @@ version: 7.8.1
summary: A static website generator
description: A static website generator
confinement: strict
grade: stable

apps:
nikola:
command: nikola.sh
plugs: [network, network-bind, home]

parts:
nikola-script:
plugin: dump
source: script
nikola:
plugin: copy
files:
nikola.sh: nikola.sh
nikola-source:
plugin: python3
source: git://github.com/getnikola/nikola.git
source-tag: v7.8.1
requirements: requirements.txt
stage-packages:
- locales
- libc-bin
- python3-lxml
- python3-pil
build-packages:
- zlib1g-dev
- libjpeg-turbo8-dev
- libpng12-dev
- libxslt1-dev
- libxml2-dev
- gcc
plugin: python
python-packages:
- Markdown>=2.4.0
- Jinja2>=2.7.2
- pyphen>=0.9.1
- micawber>=0.3.0
- pygal>=2.0.0
- typogrify>=2.0.4
- phpserialize>=1.3
- webassets>=0.10.1
- ghp-import2>=1.0.0
- ws4py==0.3.5
- watchdog==0.8.3
- doit>=0.28.0,<=0.29.0
- Pygments>=1.6
- python-dateutil>=2.4.0
- docutils>=0.12
- mako>=1.0.0
- unidecode>=0.04.16
- lxml>=3.3.5
- Yapsy>=1.11.223
- PyRSS2Gen>=1.1
- logbook>=0.7.0
- blinker>=1.3
- setuptools>=5.4.1
- natsort>=3.5.2
- requests>=2.2.0
- husl>=4.0.2
- piexif>=1.0.3
- notebook>=4.0.0
- ipykernel>=4.0.0
build-packages: [libjpeg-dev]
21 changes: 9 additions & 12 deletions tests/test_rss_feeds.py
Expand Up @@ -3,12 +3,9 @@
from __future__ import unicode_literals, absolute_import

import os
import sys


from collections import defaultdict
from io import StringIO
import os
import re
import unittest

Expand Down Expand Up @@ -50,15 +47,15 @@ def setUp(self):

with mock.patch('nikola.post.get_meta',
mock.Mock(return_value=(
({'title': 'post title',
'slug': 'awesome_article',
'date': '2012-10-01 22:41',
'author': None,
'tags': 'tags',
'link': 'link',
'description': 'description',
'enclosure': 'http://www.example.org/foo.mp3',
'enclosure_length': '5'},
(defaultdict(str, {'title': 'post title',
'slug': 'awesome_article',
'date': '2012-10-01 22:41',
'author': None,
'tags': 'tags',
'link': 'link',
'description': 'description',
'enclosure': 'http://www.example.org/foo.mp3',
'enclosure_length': '5'}),
True)
))):
with mock.patch('nikola.nikola.utils.os.path.isdir',
Expand Down

0 comments on commit b71a818

Please sign in to comment.