Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: getnikola/plugins
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: c7ab2c7c976a
Choose a base ref
...
head repository: getnikola/plugins
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: ae07ad888e74
Choose a head ref
  • 2 commits
  • 5 files changed
  • 1 contributor

Commits on Jul 13, 2015

  1. Added support for shortcode plugin data storage.

    Adding additional data support to compile_to_string and compile_html.
    Added basic gallery shortcode plugin.
    felixfontein committed Jul 13, 2015
    Copy the full SHA
    e65a725 View commit details
  2. Copy the full SHA
    ae07ad8 View commit details
4 changes: 2 additions & 2 deletions v7/wordpress_compiler/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
This plugin will allow you to compile unmodified WordPress posts using essentially the same code WordPress is using to convert posts to HTML.

Has support for shortcodes provided by plugins. Comes with [code] shortcode plugin.
Has support for shortcodes provided by plugins. Comes with a basic [code] shortcode plugin, and a basic [gallery] shortcode plugin.

To use it:

@@ -27,4 +27,4 @@ COMPILERS = {
"html": ('.html', '.htm')
}
```
Then all posts whose content is in files ending with `.wp` will be processed by the WordPress compiler plugin.
Then all posts whose content is in files ending with `.wp` or `.wordpress` will be processed by the WordPress compiler plugin.
Original file line number Diff line number Diff line change
@@ -23,7 +23,6 @@

import re
import regex
import threading

import pygments
import pygments.lexers
@@ -37,29 +36,22 @@ class Code(nikola.plugin_categories.CompilerExtension):

def __init__(self):
super(Code, self).__init__()
self.__internal_counter = 0
self.__internal_store = dict()
self.__internal_lock = threading.Lock()

def _filter_code_tags(self, text, context):
result = ''
for piece in regex.split('(\[code(?:|\s+language="[^"]*?")\].*?\[/code\])', text, flags=regex.DOTALL | regex.IGNORECASE):
match = regex.match('\[code(?:|\s+language="([^"]*?)")\](.*?)\[/code\]', piece, flags=regex.DOTALL | regex.IGNORECASE)
if match is not None:
with self.__internal_lock:
counter = self.__internal_counter = self.__internal_counter + 1
the_id = "{0}:{1}".format(context.id, counter)
self.__internal_store[the_id] = match.group(2), match.group(1)
result += '[code id="{0}"]'.format(counter)
the_id = str(context.inc_plugin_counter('wordpress_shortcode_code', 'counter'))
context.store_plugin_data('wordpress_shortcode_code', the_id, (match.group(2), match.group(1)))
result += '[code id="{0}"]'.format(the_id)
else:
result += piece
return result

def _replace_code_tags(self, args, content, tag, context):
the_id = "{0}:{1}".format(context.id, args['id'])
with self.__internal_lock:
codeContent = self.__internal_store[the_id][0]
codeType = self.__internal_store[the_id][1]
the_id = args['id']
codeContent, codeType = context.get_plugin_data('wordpress_shortcode_code', the_id)
if codeType is None:
lexer = pygments.lexers.special.TextLexer()
codeType = 'unformatted'
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Core]
Name = wordpress_shortcode_gallery
Module = wordpress_shortcode_gallery

[Nikola]
Compiler = wordpress

[Documentation]
Author = Felix Fontein
Version = 0.1
Description = Provides minimal [gallery] shortcode.
193 changes: 193 additions & 0 deletions v7/wordpress_compiler/wordpress/plugins/wordpress_shortcode_gallery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# -*- coding: utf-8 -*-

# A WordPress compiler plugin for Nikola
#
# Copyright (C) 2014-2015 by Felix Fontein
# Copyright (C) by the WordPress contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import nikola.plugin_categories

from nikola.utils import get_logger, STDERR_HANDLER

_LOGGER = get_logger('wordpress_shortcode_gallery', STDERR_HANDLER)

import re


def sanitize_html_class(clazz):
# Strip out percent encoded octets
clazz = re.sub('%[a-fA-F0-9][a-fA-F0-9]', '', clazz)
# Limit to A-Z,a-z,0-9,_,-
clazz = re.sub('[^A-Za-z0-9_-]', '', clazz)
return clazz


def sanitize_html_text(text, sanitize_quotes=False):
text = text.replace('&', '&')
text = text.replace('<', '&lt;')
text = text.replace('>', '&gt;')
if sanitize_quotes:
text = text.replace('"', '&quot;')
return text


class Gallery(nikola.plugin_categories.CompilerExtension):
name = 'wordpress_shortcode_gallery'
compiler_name = 'wordpress'

def __init__(self):
super(Gallery, self).__init__()

def _choose_size(self, w, h, size, meta=None):
# Determine maximal size
if meta and 'width' in meta and 'height' in meta:
max_w = meta['width']
max_h = meta['height']
elif size == 'thumb' or size == 'thumbnail':
max_w = 128
max_h = 96
elif size == 'medium':
# Depends on theme. We pick some 'random' values here
max_w = 150
max_h = 150
elif size == 'large':
# Depends on theme. We pick some 'random' values here
max_w = 500
max_h = 500
else:
max_w = w
max_h = h
# Make sure that w x h is at most as large as max_w x max_h
w = float(w)
h = float(h)
if w > max_w:
h = h / w * max_w
w = float(max_w)
if h > max_h:
w = w / h * max_h
h = float(max_h)
return int(w), int(h)

def _process_gallery_tags(self, args, content, tag, context):
# Get gallery counter per post
gallery_counter = context.inc_plugin_counter('wordpress_shortcode_gallery', 'counter')

# Load attachments
attachments = context.get_additional_data('attachments')
if attachments is None:
_LOGGER.error("Cannot find attachments for post {0}!".format(context.get_name()))
return "(Error loading gallery)"

# Load images
if 'ids' not in args:
_LOGGER.error("We only support galleries where all images are explicitly listed! (Post {0})".format(context.get_name()))
return "(Error loading gallery)"
ids = args.pop('ids')
ids = [id for id in ids.split(',')]
images = []
for id in ids:
attachment = attachments.get(id)
if attachment is None:
_LOGGER.error("Cannot find attachment {1} for post {0}!".format(context.get_name(), id))
return "(Error loading gallery)"
images.append(attachment)

# Empty gallery
if len(images) == 0:
return ''

# Load arguments
columns = int(args.pop('columns', '3')) # 0 = no breaks
link = args.pop('link', 'file') # 'file' or 'none'
itemtag = args.pop('itemtag', 'dl')
icontag = args.pop('icontag', 'dt')
captiontag = args.pop('captiontag', 'dd')
size = args.pop('size', 'thumbnail')
rtl = False

# Render gallery
gallery_id = '{0}-{1}'.format(hex(context.id), gallery_counter)
gallery_name = 'wordpress-gallery-{0}'.format(gallery_id)
result = ''
result += '''<style type="text/css">
#{0} {{
margin: auto;
}}
#{0} .wordpress-gallery-item {{
float: {1};
margin-top: 10px;
text-align: center;
width: {2}%;
}}
#{0} img {{
border: 2px solid #cfcfcf;
}}
#{0} .wordpress-gallery-caption {{
margin-left: 0;
}}
</style>'''.format(gallery_name, 'right' if rtl else 'left', 100.0 / columns if columns > 0 else 100)

size_class = sanitize_html_class(size)
result += '<div id="{0}" class="wordpress-gallery wordpress-gallery-id-{1} gallery-columns-{2} gallery-size-{3}">'.format(gallery_name, gallery_id, columns, size_class)
for i, image in enumerate(images):
if columns > 0 and i > 0 and i % columns == 0:
result += '<br style="clear: both;"/>'
url = image['files'][0]
alt_text = image.get('excerpt', image.get('title', ''))
caption = image.get('excerpt', None)
if 'files_meta' in image and 'width' in image['files_meta'][0] and 'height' in image['files_meta'][0]:
w = image['files_meta'][0]['width']
h = image['files_meta'][0]['height']
orientation = 'portrait' if h > w else 'landscape'
else:
orientation = None
# Determine file
file_index = 0
if 'files_meta' in image:
for index in range(1, len(image['files'])):
if size == image['files_meta'][index].get('size'):
file_index = index
# Render entry
result += '<{0} class="wordpress-gallery-item">'.format(itemtag)
result += '<{0} class="wordpress-gallery-icon{1}">'.format(icontag, (' ' + orientation) if orientation else '')
if link == 'file':
result += '<a href="{0}">'.format(url)
file = image['files'][file_index]
if 'files_meta' in image and 'width' in image['files_meta'][file_index] and 'height' in image['files_meta'][file_index]:
w = image['files_meta'][file_index]['width']
h = image['files_meta'][file_index]['height']
w, h = self._choose_size(w, h, size, image['files_meta'][file_index] if file_index > 0 else None)
result += '<img src="{0}" class="wordpress-gallery-thumb" class="{1}" width="{2}" height="{3}" alt="{4}"/>'.format(file, size_class, w, h, sanitize_html_text(caption, True))
else:
result += '<img src="{0}" class="wordpress-gallery-thumb" class="{1}" alt="{2}"/>'.format(file, size_class, sanitize_html_text(alt_text, True))
if link == 'file':
result += '</a>'
result += '</{0}>'.format(icontag)
if caption and captiontag:
result += '<{0} class="wordpress-gallery-caption">'.format(captiontag)
result += sanitize_html_text(caption)
result += '</{0}>'.format(captiontag)
result += '</{0}>'.format(itemtag)
if len(images) > 0:
result += '<br style="clear: both;"/>'
result += '</div>'
return result

def register(self, compile_wordpress, wordpress_modules):
self._user_logged_in = False
self._compile_wordpress = compile_wordpress
compile_wordpress.register_shortcode('gallery', self._process_gallery_tags)
63 changes: 59 additions & 4 deletions v7/wordpress_compiler/wordpress/wordpress.py
Original file line number Diff line number Diff line change
@@ -39,12 +39,18 @@
class Context(object):
id = None

def __init__(self, id):
def __init__(self, id, name=None, additional_data=None):
self.id = id
self.name = name
self.__file_deps_fragment = set()
self.__file_deps_page = set()
self.__uptodate_deps_fragment = list()
self.__uptodate_deps_page = list()
self.__additional_data = additional_data or {}
self.__plugin_data = {}

def get_name(self):
return "(unknown:{0})".format(self.id) if self.name is None else self.name

def add_file_dependency(self, filename, add='both'):
if add not in {'fragment', 'page', 'both'}:
@@ -78,6 +84,24 @@ def get_uptodate_dependencies_fragment(self):
def get_uptodate_dependencies_page(self):
return self.__uptodate_deps_page

def get_additional_data(self, name):
return self.__additional_data.get(name)

def store_plugin_data(self, plugin_name, key, data):
if plugin_name not in self.__plugin_data:
self.__plugin_data[plugin_name] = {}
self.__plugin_data[plugin_name][key] = data

def get_plugin_data(self, plugin_name, key, default_value=None):
plugin_data = self.__plugin_data.get(plugin_name)
return default_value if plugin_data is None else plugin_data.get(key, default_value)

def inc_plugin_counter(self, plugin_name, key):
counter = self.get_plugin_data(plugin_name, key, 0)
counter += 1
self.store_plugin_data(plugin_name, key, counter)
return counter

def __str__(self):
return "Context<" + str(self.id) + ">(" + str(self.__file_deps_fragment) + ", " + str(self.__file_deps_page) + ", " + str(self.__uptodate_deps_fragment) + ", " + str(self.__uptodate_deps_page) + ")"

@@ -165,8 +189,8 @@ def __formatData(self, data, context, source=None):
_LOGGER.warning("The post '" + source + "' still contains shortcodes: " + str(left_shortcodes))
return output

def compile_to_string(self, source_data):
context = Context(hash(source_data))
def compile_to_string(self, source_data, name=None, additional_data=None):
context = Context(hash(source_data), name=name, additional_data=additional_data)
return self.__formatData(source_data, context)

def _read_extra_deps(self, post):
@@ -195,15 +219,46 @@ def _write_deps(self, context, dest):
if os.path.isfile(deps_path):
os.unlink(deps_path)

def _read_similar_file(self, source, suffix):
path, filename = os.path.split(source)
filename_parts = filename.split('.')
for i in range(len(filename_parts), 0, -1):
candidate = os.path.join(path, '.'.join(filename_parts[:i]) + suffix)
try:
with open(candidate, "rb") as in_file:
# _LOGGER.info("Found file {0} for {1}.".format(candidate, source))
return in_file.read()
except:
pass
return None

def load_additional_data(self, source):
result = {}

attachments = self._read_similar_file(source, ".attachments.json")
if attachments is not None:
try:
attachments = json.loads(attachments.decode('utf-8'))
result['attachments'] = attachments
except Exception as e:
_LOGGER.error("Could not load attachments for {0}! (Exception: {1})".format(source, e))

return result

def compile_html(self, source, dest, is_two_file=False):
makedirs(os.path.dirname(dest))
with io.open(dest, "w+", encoding="utf8") as out_file:
# Read post
with io.open(source, "r", encoding="utf8") as in_file:
data = in_file.read()
if not is_two_file:
data = re.split('(\n\n|\r\n\r\n)', data, maxsplit=1)[-1]
context = Context(hash(data))
# Read additional data
additional_data = self.load_additional_data(source)
# Process post
context = Context(hash(data), name=source, additional_data=additional_data)
output = self.__formatData(data, context)
# Write result
out_file.write(output)
self._write_deps(context, dest)