Skip to content

Commit b71a818

Browse files
committedOct 26, 2016
Merge branch 'master' into generalization-of-taxonomies
2 parents 650affe + 9a09d5d commit b71a818

File tree

17 files changed

+125
-49
lines changed

17 files changed

+125
-49
lines changed
 

‎CHANGES.txt

+9
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,21 @@ New in master
44
Bugfixes
55
--------
66

7+
* Make "data" from global context available to templated shortcodes (Issue #2488)
8+
* Don't crash if plugins is a file (Issue #2539)
9+
710
Features
811
--------
912

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

1423
New in v7.8.1
1524
=============

‎docs/manual.txt

+5
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,10 @@ In that case, the template engine used will be your theme's and the arguments yo
10861086
as well as the global context from your ``conf.py``, are available to the template you
10871087
are creating.
10881088

1089+
You can use anything defined in your confguration's ``GLOBAL_CONTEXT`` as variables in your
1090+
shortcode template, with a caveat: Because of an unfortunate implementation detail, data is called
1091+
"global_data" when used in a shortcode.
1092+
10891093
The Global Context and Data files
10901094
---------------------------------
10911095

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

1122+
11181123
Redirections
11191124
------------
11201125

‎nikola/__main__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def main(args=None):
155155
req_missing(['freezegun'], 'perform invariant builds')
156156

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

‎nikola/data/themes/base-jinja/templates/base_helper.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ lang="{{ lang }}">
6060
{% if use_cdn %}
6161
<!--[if lt IE 9]><script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
6262
{% else %}
63-
<!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang) }}"></script><![endif]-->
63+
<!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang, url_type) }}"></script><![endif]-->
6464
{% endif %}
6565

6666
{{ extra_head_data }}

‎nikola/data/themes/base/templates/base_helper.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ lang="${lang}">
6060
%if use_cdn:
6161
<!--[if lt IE 9]><script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
6262
%else:
63-
<!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang)}"></script><![endif]-->
63+
<!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang, url_type)}"></script><![endif]-->
6464
%endif
6565

6666
${extra_head_data}

‎nikola/data/themes/bootstrap3-jinja/templates/base_helper.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ lang="{{ lang }}">
6565
{% if use_cdn %}
6666
<!--[if lt IE 9]><script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
6767
{% else %}
68-
<!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang) }}"></script><![endif]-->
68+
<!--[if lt IE 9]><script src="{{ url_replacer(permalink, '/assets/js/html5.js', lang, url_type) }}"></script><![endif]-->
6969
{% endif %}
7070

7171
{{ extra_head_data }}

‎nikola/data/themes/bootstrap3/templates/base_helper.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ lang="${lang}">
6565
%if use_cdn:
6666
<!--[if lt IE 9]><script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
6767
%else:
68-
<!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang)}"></script><![endif]-->
68+
<!--[if lt IE 9]><script src="${url_replacer(permalink, '/assets/js/html5.js', lang, url_type)}"></script><![endif]-->
6969
%endif
7070

7171
${extra_head_data}

‎nikola/nikola.py

+31-9
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,7 @@ def init_plugins(self, commands_only=False, load_all=False):
985985
self.disabled_compilers[p[-1].name] = p
986986
utils.LOGGER.debug('Not loading unneeded compiler {}', p[-1].name)
987987
if p[-1].name not in self.config['COMPILERS'] and \
988-
p[-1].details.has_option('Nikola', 'plugincategory') and p[-1].details.get('Nikola', 'PluginCategory') == 'Compiler':
988+
p[-1].details.has_option('Nikola', 'plugincategory') and p[-1].details.get('Nikola', 'PluginCategory') in ('Compiler', 'PageCompiler'):
989989
bad_candidates.add(p)
990990
self.disabled_compilers[p[-1].name] = p
991991
utils.LOGGER.debug('Not loading unneeded compiler {}', p[-1].name)
@@ -1290,7 +1290,7 @@ def get_compiler(self, source_name):
12901290

12911291
return compiler
12921292

1293-
def render_template(self, template_name, output_name, context, url_type=None):
1293+
def render_template(self, template_name, output_name, context, url_type=None, is_fragment=False):
12941294
"""Render a template with the global context.
12951295
12961296
If ``output_name`` is None, will return a string and all URL
@@ -1301,6 +1301,9 @@ def render_template(self, template_name, output_name, context, url_type=None):
13011301
13021302
The argument ``url_type`` allows to override the ``URL_TYPE``
13031303
configuration.
1304+
1305+
If ``is_fragment`` is set to ``True``, a HTML fragment will
1306+
be rendered and not a whole HTML document.
13041307
"""
13051308
local_context = {}
13061309
local_context["template_name"] = template_name
@@ -1309,6 +1312,7 @@ def render_template(self, template_name, output_name, context, url_type=None):
13091312
for k in self._GLOBAL_CONTEXT_TRANSLATABLE:
13101313
local_context[k] = local_context[k](local_context['lang'])
13111314
local_context['is_rtl'] = local_context['lang'] in LEGAL_VALUES['RTL_LANGUAGES']
1315+
local_context['url_type'] = self.config['URL_TYPE'] if url_type is None else url_type
13121316
# string, arguments
13131317
local_context["formatmsg"] = lambda s, *a: s % a
13141318
for h in local_context['template_hooks'].values():
@@ -1336,9 +1340,18 @@ def render_template(self, template_name, output_name, context, url_type=None):
13361340

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

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

13501363
# lxml ignores srcset in img and source elements, so do that by hand
1351-
objs = list(doc.xpath('(*//img|*//source)'))
1364+
objs = list(doc.xpath('(//img|//source)'))
13521365
for obj in objs:
13531366
if 'srcset' in obj.attrib:
13541367
urls = [u.strip() for u in obj.attrib['srcset'].split(',')]
@@ -1479,9 +1492,16 @@ def _make_renderfunc(self, t_data, fname=None):
14791492
keyword argument dict and then the latter provides the template
14801493
context.
14811494
1495+
Global context keys are made available as part of the context,
1496+
respecting locale.
1497+
1498+
As a special quirk, the "data" key from global_context is made
1499+
available as "global_data" because of name clobbering.
1500+
14821501
"""
14831502
def render_shortcode(*args, **kw):
14841503
context = self.GLOBAL_CONTEXT.copy()
1504+
context['global_data'] = context['data']
14851505
context.update(kw)
14861506
context['_args'] = args
14871507
context['lang'] = utils.LocaleBorg().current_lang
@@ -1999,7 +2019,7 @@ def scan_posts(self, really=False, ignore_quit=False, quiet=False):
19992019
sys.exit(1)
20002020
signal('scanned').send(self)
20012021

2002-
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):
2022+
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):
20032023
"""Helper function for rendering pages and post lists and other related pages.
20042024
20052025
lang is the current language.
@@ -2011,7 +2031,8 @@ def generic_renderer(self, lang, output_name, template_name, filters, file_deps=
20112031
context (optional) a dict used as a basis for the template context. The lang parameter will always be added.
20122032
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.
20132033
post_deps_dict (optional) is a dict merged into the copy of context which is used as an uptodate dependency.
2014-
url_type (optional) allows to override the ``URL_TYPE`` configuration
2034+
url_type (optional) allows to override the ``URL_TYPE`` configuration.
2035+
is_fragment (optional) allows to write a HTML fragment instead of a HTML document.
20152036
"""
20162037
utils.LocaleBorg().set_locale(lang)
20172038

@@ -2045,7 +2066,7 @@ def generic_renderer(self, lang, output_name, template_name, filters, file_deps=
20452066
'targets': [output_name],
20462067
'file_dep': file_deps,
20472068
'actions': [(self.render_template, [template_name, output_name,
2048-
context, url_type])],
2069+
context, url_type, is_fragment])],
20492070
'clean': True,
20502071
'uptodate': [config_changed(deps_dict, 'nikola.nikola.Nikola.generic_renderer')] + ([] if uptodate_deps is None else uptodate_deps)
20512072
}
@@ -2088,7 +2109,8 @@ def generic_page_renderer(self, lang, post, filters, context=None):
20882109
uptodate_deps=uptodate_deps,
20892110
context=context,
20902111
context_deps_remove=['post'],
2091-
post_deps_dict=deps_dict)
2112+
post_deps_dict=deps_dict,
2113+
url_type=post.url_type)
20922114

20932115
def generic_post_list_renderer(self, lang, posts, output_name, template_name, filters, extra_context):
20942116
"""Render pages with lists of posts."""

‎nikola/plugins/command/plugin.py

+19
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ def do_install(self, url, name, show_install_notes=True):
265265
'package manager.')
266266
else:
267267
LOGGER.info('Dependency installation succeeded.')
268+
268269
reqnpypath = os.path.join(dest_path, 'requirements-nonpy.txt')
269270
if os.path.exists(reqnpypath):
270271
LOGGER.notice('This plugin has third-party '
@@ -280,6 +281,24 @@ def do_install(self, url, name, show_install_notes=True):
280281

281282
print('You have to install those yourself or through a package '
282283
'manager.')
284+
285+
req_plug_path = os.path.join(dest_path, 'requirements-plugins.txt')
286+
if os.path.exists(req_plug_path):
287+
LOGGER.notice('This plugin requires other Nikola plugins.')
288+
LOGGER.info('Installing plugins...')
289+
try:
290+
with io.open(req_plug_path, 'r', encoding='utf-8') as inf:
291+
for plugname in inf.readlines():
292+
self.do_install(url, plugname, show_install_notes)
293+
except subprocess.CalledProcessError:
294+
LOGGER.error('Could not install a plugin.')
295+
print('Contents of the requirements-plugins.txt file:\n')
296+
with io.open(req_plug_path, 'r', encoding='utf-8') as fh:
297+
print(utils.indent(fh.read(), 4 * ' '))
298+
print('You have to install those yourself manually.')
299+
else:
300+
LOGGER.info('Dependency installation succeeded.')
301+
283302
confpypath = os.path.join(dest_path, 'conf.py.sample')
284303
if os.path.exists(confpypath) and show_install_notes:
285304
LOGGER.notice('This plugin has a sample config file. Integrate it with yours in order to make this plugin work!')

‎nikola/post.py

+4
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ def __init__(
249249
self.use_in_feeds = use_in_feeds and not is_draft and not is_private \
250250
and not self.publish_later
251251

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

File renamed without changes.

‎snapcraft/edge/snapcraft.yaml

+3-4
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ apps:
1111

1212
parts:
1313
nikola:
14-
plugin: copy
15-
files:
16-
nikola.sh: nikola.sh
14+
plugin: dump
15+
source: script/
1716
nikola-source:
18-
plugin: python3
17+
plugin: python
1918
source: git://github.com/getnikola/nikola.git
2019
requirements: requirements.txt
2120
stage-packages:

‎snapcraft/requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ natsort>=3.5.2
2525
requests>=2.2.0
2626
husl>=4.0.2
2727
piexif>=1.0.3
28+
phpserialize==1.3
29+
webassets==0.12.0
30+

‎snapcraft/stable/build.sh

-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
#!/bin/sh
22
snapcraft
3-
cp ../nikola.py prime/usr/bin/nikola
4-
find prime/ -name '*.a' -exec rm {} \;
5-
snapcraft
63

‎snapcraft/stable/nikola.sh ‎snapcraft/stable/script/nikola.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ export LC_ALL=$APPLOC
1818
export LANG=$APPLOC
1919
export LANGUAGE=${APPLANG%_*}
2020

21-
$SNAP/usr/bin/nikola "$@"
21+
$SNAP/bin/nikola "$@"

‎snapcraft/stable/snapcraft.yaml

+36-15
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,52 @@ version: 7.8.1
33
summary: A static website generator
44
description: A static website generator
55
confinement: strict
6+
grade: stable
67

78
apps:
89
nikola:
910
command: nikola.sh
1011
plugs: [network, network-bind, home]
1112

1213
parts:
14+
nikola-script:
15+
plugin: dump
16+
source: script
1317
nikola:
14-
plugin: copy
15-
files:
16-
nikola.sh: nikola.sh
17-
nikola-source:
18-
plugin: python3
1918
source: git://github.com/getnikola/nikola.git
2019
source-tag: v7.8.1
21-
requirements: requirements.txt
2220
stage-packages:
2321
- locales
2422
- libc-bin
25-
- python3-lxml
26-
- python3-pil
27-
build-packages:
28-
- zlib1g-dev
29-
- libjpeg-turbo8-dev
30-
- libpng12-dev
31-
- libxslt1-dev
32-
- libxml2-dev
33-
- gcc
23+
plugin: python
24+
python-packages:
25+
- Markdown>=2.4.0
26+
- Jinja2>=2.7.2
27+
- pyphen>=0.9.1
28+
- micawber>=0.3.0
29+
- pygal>=2.0.0
30+
- typogrify>=2.0.4
31+
- phpserialize>=1.3
32+
- webassets>=0.10.1
33+
- ghp-import2>=1.0.0
34+
- ws4py==0.3.5
35+
- watchdog==0.8.3
36+
- doit>=0.28.0,<=0.29.0
37+
- Pygments>=1.6
38+
- python-dateutil>=2.4.0
39+
- docutils>=0.12
40+
- mako>=1.0.0
41+
- unidecode>=0.04.16
42+
- lxml>=3.3.5
43+
- Yapsy>=1.11.223
44+
- PyRSS2Gen>=1.1
45+
- logbook>=0.7.0
46+
- blinker>=1.3
47+
- setuptools>=5.4.1
48+
- natsort>=3.5.2
49+
- requests>=2.2.0
50+
- husl>=4.0.2
51+
- piexif>=1.0.3
52+
- notebook>=4.0.0
53+
- ipykernel>=4.0.0
54+
build-packages: [libjpeg-dev]

‎tests/test_rss_feeds.py

+9-12
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@
33
from __future__ import unicode_literals, absolute_import
44

55
import os
6-
import sys
7-
86

97
from collections import defaultdict
108
from io import StringIO
11-
import os
129
import re
1310
import unittest
1411

@@ -50,15 +47,15 @@ def setUp(self):
5047

5148
with mock.patch('nikola.post.get_meta',
5249
mock.Mock(return_value=(
53-
({'title': 'post title',
54-
'slug': 'awesome_article',
55-
'date': '2012-10-01 22:41',
56-
'author': None,
57-
'tags': 'tags',
58-
'link': 'link',
59-
'description': 'description',
60-
'enclosure': 'http://www.example.org/foo.mp3',
61-
'enclosure_length': '5'},
50+
(defaultdict(str, {'title': 'post title',
51+
'slug': 'awesome_article',
52+
'date': '2012-10-01 22:41',
53+
'author': None,
54+
'tags': 'tags',
55+
'link': 'link',
56+
'description': 'description',
57+
'enclosure': 'http://www.example.org/foo.mp3',
58+
'enclosure_length': '5'}),
6259
True)
6360
))):
6461
with mock.patch('nikola.nikola.utils.os.path.isdir',

0 commit comments

Comments
 (0)
Please sign in to comment.