Dynastie/dynastie/generators/generator.py
Gregory Soutade e0b8f544ff Fix HTML article inclusion
Fix draft inclusion in preview
Enhance cache post content (avoid recomputing md5sum if present)
Add generation duration time
Add post only generation (for Dev)
Remove Draft when it becomes Post
Update blog Copyright
Update TinyMCE plugins for inclusion
Sort tags by name
2016-01-09 20:10:27 +01:00

279 lines
9.0 KiB
Python
Executable File

# -*- coding: utf-8 -*-
"""
Copyright 2012-2014 Grégory Soutadé
This file is part of Dynastie.
Dynastie 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 3 of the License, or
(at your option) any later version.
Dynastie 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 Dynastie. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import hashlib
import gzip
import math
import codecs
from xml.dom import *
from xml.dom.minidom import parse
from xml.parsers.expat import *
class StrictUTF8Writer(codecs.StreamWriter):
'''A StreamWriter for utf8'''
encode = codecs.utf_8_encode
value = ''
def __init__(self):
self.value = u''
def write(self, object):
object = object.replace('&lt;', '<')
object = object.replace('&gt;', '>')
object = object.replace('&quot;', '"')
object = object.replace('&apos;', "'")
object = object.replace('&amp;', '&')
if not type(object) == unicode:
self.value = self.value + unicode(object, 'utf-8')
else:
self.value = self.value + object
return self.value
def reset(self):
self.value = u''
def getvalue(self):
return self.value
class DynastieGenerator:
URI = "http://indefero.soutade.fr/p/dynastie"
def __init__(self, request, hash_posts={}, hash_posts_content={}):
self.report = ''
self.somethingWrote = False
self.request = request
self.hash_posts = hash_posts
self.hash_posts_content = hash_posts_content
self.user = request and request.user or None
def addReport(self, string, color=''):
if string in self.report: return
if color:
self.report = self.report + '<span style="color:' + color + '">'
self.report = self.report + '<b>' + self.__class__.__name__ + '</b> : '
self.report = self.report + string
if color:
self.report = self.report + '</span>'
self.report = self.report + '<br/>\n'
def addWarning(self, string):
self.addReport(string, 'yellow')
def addError(self, string):
self.addReport(string, 'red')
# Virtual
def generate(self, blog, src, output):
return
def computeNbPages(self, nb_post, nb_post_per_page):
res = math.ceil((nb_post*1.0)/(nb_post_per_page*1.0))
return int(res)
def writeIfNotTheSame(self, filename, node):
from dynastie.models import FileOutputCache
writer = StrictUTF8Writer()
node.writexml(writer)
content = writer.getvalue().encode('utf-8')
dst_md5 = hashlib.md5()
dst_md5.update(content)
cache_objs = FileOutputCache.objects.filter(name=filename)
if cache_objs.count() == 0:
cache_obj = None
else:
cache_obj = cache_objs[0]
if cache_obj or os.path.exists(filename):
if not cache_obj:
src_md5 = hashlib.md5()
f = open(filename,'rb')
src_md5.update(f.read())
f.close()
src_md5 = src_md5.hexdigest()
else:
src_md5 = cache_obj.hash
if src_md5 == dst_md5.hexdigest():
if cache_obj is None:
cache_obj = FileOutputCache(name=filename, hash=src_md5)
cache_obj.save()
filename = filename + '.gz'
if not os.path.exists(filename):
f = gzip.open(filename, 'wb')
f.write(content)
f.close()
return
if os.path.exists(filename):
os.unlink(filename)
if cache_obj is None:
cache_obj = FileOutputCache(name=filename, hash=dst_md5.hexdigest())
else:
cache_obj.hash = dst_md5.hexdigest()
self.addReport('Write (and compress) ' + filename)
f = open(filename,'wb')
f.write(content)
f.close()
filename = filename + '.gz'
#self.addReport('Compressing it ' + filename)
f = gzip.open(filename, 'wb')
f.write(content)
f.close()
cache_obj.save()
self.somethingWrote = True
def createLinkElem(self, dom, path, title):
link_elem = dom.createElement('a')
link_elem.setAttribute('href', path)
text_elem = dom.createTextNode(title)
link_elem.appendChild(text_elem)
return link_elem
def cloneSubtree(self, div, subtree):
for node in subtree.childNodes:
div.appendChild(node.cloneNode(True))
def createElement(self, dom, name='', content='', subtree=None):
div = dom.createElement('div')
if name:
div.setAttribute('class', name)
if content:
div.appendChild(dom.createTextNode(content))
if subtree:
self.cloneSubtree(div, subtree)
return div
def createMeta(self, dom, name='', content=''):
div = dom.createElement('meta')
if name:
div.setAttribute('name', name)
if content:
div.setAttribute('content', content)
return div
# Recursively transform <dyn:XXX> elements with the ones in values
def simpleTransform(self, values, dom, elem, root):
for node in root.childNodes:
if node.prefix == 'dyn':
if node.localName in values:
content = values[node.localName]
if isinstance(content, basestring):
new_elem = self.createElement(dom, node.localName, content)
else:
new_elem = self.createElement(dom, node.localName)
new_elem.appendChild(content)
else:
new_elem = node.cloneNode(False)
self.simpleTransform(values, dom, new_elem, node)
else:
new_elem = node.cloneNode(False)
self.simpleTransform(values, dom, new_elem, node)
elem.appendChild(new_elem)
def replaceByText(self, dom, root, node, content):
new_node = dom.createTextNode(content)
root.replaceChild(new_node, node)
def _parse(self, hooks, posts, dom, root):
for node in root.childNodes:
if node.prefix == 'dyn' and node.localName in hooks:
node = hooks[node.localName](posts, dom, root, node)
if node and node.hasChildNodes():
self._parse(hooks, posts, dom, node)
def parse(self, src, hooks, posts, dom, root):
bases = dom.getElementsByTagNameNS(self.URI, 'base')
if not bases:
self._parse(hooks, posts, dom, root)
return root
if len(bases) != 1:
self.addError('More than one base defined')
return root
base = bases[0]
if not base.hasAttribute('file'):
self.addError('No \'file\' attribute defined')
return root
filename = base.getAttribute('file')
if not os.path.exists(src + '/' + filename):
self.addError('Base ' + filename + ' doesn\'t exists')
return root
target_blocks = base.getElementsByTagNameNS(self.URI, 'block')
if not target_blocks:
self.addError('No \'block\' defined in ' + src + '/' + filename)
return root
for target_block in target_blocks:
if not target_block.hasAttribute('name'):
self.addError('Every block must have a name in ' + src + '/' + filename)
return root
dom2 = root
try:
dom2 = parse(src + '/' + filename)
except ExpatError, e:
self.addError('Error parsing ' + src + '/' + filename)
return root
blocks = dom2.getElementsByTagNameNS(self.URI, 'block')
block_found = False
for block in blocks:
if not block.hasAttribute('name'):
self.addError('block has no attribute \'name\' in ' + src + '/' + filename)
return root
blockname = block.getAttribute('name')
for target_block in target_blocks:
if blockname != target_block.getAttribute('name'):
continue
for child in target_block.childNodes:
block.parentNode.appendChild(child.cloneNode(True))
block_found = True
break
block.parentNode.removeChild(block)
if not block_found:
self.addError('Any block found in ' + src + '/' + filename)
return root
root = dom2.firstChild
self.parse(src, hooks, posts, dom2, root)
return root