Compare commits

...

17 Commits
v0.4 ... master

Author SHA1 Message Date
Gregory Soutade
27bd360abe Add output language support 2022-10-08 16:08:02 +02:00
Gregory Soutade
c5744585c7 Fix an error in markdown2.py transformation, : character doesn't need to be encoded when converting an URL 2022-10-08 16:07:41 +02:00
Gregory Soutade
2bd6f8ae21 Update site for w3c standards 2022-09-18 17:22:57 +02:00
Gregory Soutade
0d5d5ce535 Fix errors with Python3 2022-09-18 17:22:17 +02:00
Gregory Soutade
4ece9e9079 Force iframe end tag </iframe> to be wrote (optimized by xml parser) 2022-09-18 17:04:15 +02:00
Gregory Soutade
1b0c92e45a Many fixes (encoding/Python3) 2022-06-19 10:12:51 +02:00
Gregory Soutade
fa98b0b0e0 Update blog templates 2022-06-19 10:12:14 +02:00
Gregory Soutade
96e12bce83 Update blog templates 2022-06-19 10:11:35 +02:00
Gregory Soutade
b7fb9738a1 Update blog.soutade.fr 2020-04-06 10:32:09 +02:00
Gregory Soutade
1805967833 Update post/_createPost to not simply append post content, but replace dyn:post node 2020-04-06 10:31:28 +02:00
Gregory Soutade
10347ec588 Don't remove Drafts in blog view
Add a check for robots directly using search form
2020-03-20 16:56:02 +01:00
Gregory Soutade
7cb4f1d3d7 Update Markdown parser from 2.1.1 to 2.3.2 2020-03-20 16:55:45 +01:00
Gregory Soutade
4b642fa48a Fix some bugs:
* Don't use cached objects if file has been removed
	* Replace subblock node with all of this nodes instead of appending them at the end of parent (respect order)
	* Disable Post cache cause it generates some random errors
	* Don't forget to update cur page number, even if Post creation has failed
2020-03-20 16:55:23 +01:00
Gregory Soutade
f99bea97ef Update blog theme 2020-03-20 16:48:52 +01:00
Gregory Soutade
e029a1fc35 Also checks for 'email' field to be present 2016-06-13 07:37:53 +02:00
Gregory Soutade
05c92a1738 Update version and ChangeLog 2016-06-05 09:19:02 +02:00
Gregory Soutade
7b93925d00 Some robots doesn't set referer when they try to add comment 2016-06-05 09:13:46 +02:00
32 changed files with 1519 additions and 1208 deletions

View File

@ -1,3 +1,12 @@
v0.5 (05/06/2016)
** User **
** Dev **
** Bugs **
Some robots doesn't set referer when they try to add comment
v0.4 (22/05/2016)
** User **

2
README
View File

@ -1,6 +1,6 @@
Dynastie is static blog generator delivered under GPL v3 licence terms.
Current version is 0.4
Current version is 0.5
Requirements :
Django >= 1.8, libapache2-mod-wsgi if you want to use Dynastie with Apache. PyGments (Optional).

View File

@ -19,6 +19,7 @@
"""
from django.contrib.auth.models import User
from dynastie.models import Language
class UserProfile(models.Model):
# This field is required.
@ -26,3 +27,6 @@ class UserProfile(models.Model):
# Other fields here
editor = models.CharField(max_length=20, default="TinyMCE")
default_language = models.ForeignKey(Language, on_delete=models.CASCADE)

View File

@ -33,16 +33,17 @@ class PostForm(ModelForm):
class Meta:
model = Post
exclude = ('title_slug', 'creation_date', 'modification_date', 'author', 'blog', 'tags', 'content_format')
exclude = ('title_slug', 'creation_date', 'modification_date', 'author', 'blog', 'tags', 'content_format', 'post_type')
def __init__(self, *args, **kwargs):
super(PostForm, self).__init__(*args, **kwargs)
self.fields['category'].choices = [(cat.id, cat.name) for cat in Category.objects.all()]
self.fields['language'].choices = [(lang.id, lang.name) for lang in Language.objects.all()]
class DraftForm(PostForm):
class Meta:
model = Draft
exclude = ('title_slug', 'creation_date', 'modification_date', 'author', 'blog', 'tags', 'content_format', 'published')
exclude = ('title_slug', 'creation_date', 'modification_date', 'author', 'blog', 'tags', 'content_format', 'published', 'post_type')
class CategoryForm(ModelForm):
class Meta:

View File

@ -77,7 +77,7 @@ class Atom(RSS):
return
f = open(filename, 'rb')
post_content = '<![CDATA[' + f.read() + ']]>'
post_content = '<![CDATA[' + f.read().decode('utf-8') + ']]>'
f.close()
if post.content_format == Post.CONTENT_TEXT:

View File

@ -22,6 +22,7 @@ import hashlib
import gzip
import math
import codecs
import re
from xml.dom import *
from xml.dom.minidom import parse
from xml.parsers.expat import *
@ -32,7 +33,7 @@ class StrictUTF8Writer(codecs.StreamWriter):
value = ''
def __init__(self):
self.value = u''
self.value = ''
def write(self, object):
object = object.replace('&lt;', '<')
@ -41,17 +42,17 @@ class StrictUTF8Writer(codecs.StreamWriter):
object = object.replace('&apos;', "'")
object = object.replace('&amp;', '&')
if not type(object) == unicode:
self.value = self.value + unicode(object, 'utf-8')
if type(object) == bytes:
self.value = self.value + object.decode('utf-8')
else:
self.value = self.value + object
return self.value
def reset(self):
self.value = u''
self.value = ''
def getvalue(self):
return self.value
return self.value.encode('utf-8')
class DynastieGenerator:
@ -64,7 +65,8 @@ class DynastieGenerator:
self.hash_posts = hash_posts
self.hash_posts_content = hash_posts_content
self.user = request and request.user or None
self.iframe_re = re.compile(r'(<iframe.*)/>')
def addReport(self, string, color=''):
if string in self.report: return
if color:
@ -94,8 +96,10 @@ class DynastieGenerator:
writer = StrictUTF8Writer()
node.writexml(writer)
content = writer.getvalue().encode('utf-8')
content = writer.getvalue().decode('utf-8')
# iframe tag must be like <iframe ... ></iframe>, not optimized <iframe ... />
content = self.iframe_re.sub('\\1></iframe>', content).encode('utf-8')
dst_md5 = hashlib.md5()
dst_md5.update(content)
@ -105,6 +109,10 @@ class DynastieGenerator:
else:
cache_obj = cache_objs[0]
if cache_obj and not os.path.exists(filename):
cache_obj.delete()
cache_obj = None
if cache_obj or os.path.exists(filename):
if not cache_obj:
src_md5 = hashlib.md5()
@ -160,8 +168,8 @@ class DynastieGenerator:
for node in subtree.childNodes:
div.appendChild(node.cloneNode(True))
def createElement(self, dom, name='', content='', subtree=None):
div = dom.createElement('div')
def createElement(self, dom, name='', content='', subtree=None, _type='div'):
div = dom.createElement(_type)
if name:
div.setAttribute('class', name)
if content:
@ -186,7 +194,7 @@ class DynastieGenerator:
if node.prefix == 'dyn':
if node.localName in values:
content = values[node.localName]
if isinstance(content, basestring):
if isinstance(content, str):
new_elem = self.createElement(dom, node.localName, content)
else:
new_elem = self.createElement(dom, node.localName)
@ -245,7 +253,7 @@ class DynastieGenerator:
dom2 = root
try:
dom2 = parse(src + '/' + filename)
except ExpatError, e:
except ExpatError as e:
self.addError('Error parsing ' + src + '/' + filename)
return root
@ -257,12 +265,11 @@ class DynastieGenerator:
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.parentNode.insertBefore(child.cloneNode(True), block)
block_found = True
break
block.parentNode.removeChild(block)

View File

@ -157,10 +157,10 @@ class Index(DynastieGenerator):
create_link = (title_elem.getAttribute('link') == '1')
post = self.cur_post_obj
if create_link == True:
node = self.createElement(dom, 'title')
node = self.createElement(dom, 'title', _type='h2')
node.appendChild(self.createLinkElem(dom, post.getPath(), post.title))
else:
node = self.createElement(dom, 'title', post.title)
node = self.createElement(dom, 'title', post.title, _type='h2')
root.replaceChild(node, title_elem)
@ -240,10 +240,11 @@ class Index(DynastieGenerator):
if not user: user = post.author
# Markdown replace
if not post or (post and post.content_format == Post.CONTENT_TEXT):
internal_posts = re.search('\[\[([0-9]+)\]\]', text)
internal_posts = re.finditer('\[\[([0-9]+)\]\]', text)
if internal_posts:
for post_id in internal_posts.groups():
post_id = int(post_id)
for post in internal_posts:
post_id = post.groups()
post_id = int(post_id[0])
if post_id in self.parent_posts: continue
_,post = self._have_I_right(user, post_id)
if not post: continue
@ -291,7 +292,7 @@ class Index(DynastieGenerator):
filename = filename2
if not filename in self.hash_posts_content:
f = codecs.open(filename, 'rb', 'utf-8')
f = codecs.open(filename, 'r', 'utf-8')
post_content = f.read()
f.close()
self.parent_posts.append(post.id)
@ -308,7 +309,7 @@ class Index(DynastieGenerator):
from dynastie.models import Post
post = self.cur_post_obj
if post.id in self.hash_posts and not self.first_try:
if post.id > 0 and post.id in self.hash_posts.keys() and not self.first_try:
node,_ = self.hash_posts[post.id]
return node.cloneNode(0)
@ -318,6 +319,9 @@ class Index(DynastieGenerator):
except:
pass
if post.language != None:
post_elem.setAttribute('lang', post.language.abbrev)
self.parent_posts = []
post_content = self._loadPostContent(post)
if not post_content: return None
@ -334,27 +338,28 @@ class Index(DynastieGenerator):
continue
new_node = dom.createTextNode(post_content)
content_node.appendChild(new_node)
writer = StrictUTF8Writer()
post_elem.writexml(writer)
content = writer.getvalue().encode('utf-8')
md5 = hashlib.md5()
md5.update(content)
if post.id in self.hash_posts:
# Here, we are in first_try, check that computed
# post has the same result than the one in cache
self.first_try = False
_,md5_2 = self.hash_posts[post.id]
# Disable this cache
# writer = StrictUTF8Writer()
# post_elem.writexml(writer)
# content = writer.getvalue().encode('utf-8')
# If not, clear cache
if md5.digest() != md5_2:
self.hash_posts = {}
self.hash_posts[post.id] = (post_elem.cloneNode(0), md5.digest())
else:
self.hash_posts[post.id] = (post_elem.cloneNode(0), md5.digest())
# md5 = hashlib.md5()
# md5.update(content)
# if post.id in self.hash_posts:
# # Here, we are in first_try, check that computed
# # post has the same result than the one in cache
# self.first_try = False
# _,md5_2 = self.hash_posts[post.id]
# # If not, clear cache
# if md5.digest() != md5_2:
# self.hash_posts = {}
# self.hash_posts[post.id] = (post_elem.cloneNode(0), md5.digest())
# else:
# self.hash_posts[post.id] = (post_elem.cloneNode(0), md5.digest())
return post_elem
@ -363,10 +368,9 @@ class Index(DynastieGenerator):
create_link = (node.getAttribute('link') == '1')
for i in range(0, self.posts_per_page):
post_elem = self.createElement(dom, 'post')
if len(posts) > self.cur_post:
if self.cur_post < len(posts):
self.cur_post_obj = posts[self.cur_post]
post_elem = self.createPost(posts, dom, post_elem, node)
if post_elem is None: continue
else:
post_elem = self.createElement(dom, '', '<b>No posts yet</b>')
self.cur_post_obj = None
@ -490,19 +494,20 @@ class Index(DynastieGenerator):
writer = StrictUTF8Writer()
node.firstChild.writexml(writer)
code = writer.getvalue().encode('utf-8')
code = writer.getvalue().decode('utf-8')
r,w = os.pipe()
r,w=os.fdopen(r,'r',0), os.fdopen(w,'w',0)
r,w=os.fdopen(r,'rb'), os.fdopen(w,'wb')
highlight(code, lexer, formatter, w)
w.close()
code = r.read()
code = code.decode('utf-8')
r.close()
# Remove <pre> after <div class="highlight">
code = code[28:-13]
code = u'<div class="highlight">' + unicode(code, 'utf-8') + u'</div>'
code = '<div class="highlight">' + code + u'</div>'
return code
@ -554,8 +559,6 @@ class Index(DynastieGenerator):
self.cur_page = self.cur_page + 1
filename = self.dirname + '/' + self.filename + str(self.cur_page) + '.html'
filename = output + filename
while os.path.exists(filename):
self.addReport('Removing unused ' + filename)
os.unlink(filename)
@ -565,7 +568,6 @@ class Index(DynastieGenerator):
os.unlink(filename)
self.cur_page = self.cur_page + 1
filename = self.dirname + '/' + self.filename + str(self.cur_page) + '.html'
filename = output + filename
def generate(self, blog, src, output):
from dynastie.models import Post, Blog

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@
import datetime
import os
import xml
import re
from xml.dom.minidom import parse, parseString
from dynastie.generators.generator import DynastieGenerator, StrictUTF8Writer
from dynastie.generators.index import Index
@ -133,7 +134,13 @@ class Post(Index):
new_elem = self.createMeta(dom, name, post.author.first_name + ' ' + post.author.last_name)
except:
return None
elif name == 'lang':
meta_elem.removeChild(root)
# Add attribute lang in <html> tag
if post.language != None:
meta_elem.parentNode.setAttribute('lang', post.language.abbrev)
return None
if not new_elem is None:
root.parentNode.replaceChild(new_elem, root)
return new_elem
@ -146,15 +153,14 @@ class Post(Index):
self.replaceByText(dom, root, node, value)
return None
def _createPost(self, post, dom, post_elem, root):
def _createPost(self, post, dom, root, node):
self.cur_post_obj = post
posts = [post]
self.createPost(posts, dom, post_elem, root)
post_elem = self.createElement(dom, 'post')
post_elem = self.createPost(posts, dom, post_elem, node)
# Post are appended by index. Remove template
post_nodes = dom.getElementsByTagNameNS(self.URI, 'post')
post_elem = post_nodes[0]
post_elem.parentNode.removeChild(post_elem)
root.replaceChild(post_elem, node)
title_nodes = dom.getElementsByTagName('title')
@ -164,7 +170,7 @@ class Post(Index):
node.removeChild(node.childNodes[0])
node.appendChild(dom.createTextNode(post.title))
return node
return post_elem
def _generate(self, blog, src, output, posts):
from dynastie.search import Search
@ -244,8 +250,8 @@ class Post(Index):
if not the_class in post_transform:
continue
if the_class == 'post_content':
s = u'<div>' + post_content + u'</div>'
new_node = parseString(s.encode('utf-8'))
s = '<div>' + post_content + u'</div>'
new_node = parseString(s)
for n in new_node.childNodes[0].childNodes:
content_node.appendChild(n)
break
@ -258,6 +264,7 @@ class Post(Index):
def preview(self, request, src, values):
from dynastie.models import Blog
iframe_re = re.compile(r'(<iframe.*)/>')
self.user = request.user
@ -287,4 +294,8 @@ class Post(Index):
writer = StrictUTF8Writer()
nodes[0].writexml(writer)
return writer.getvalue().encode('utf-8')
content = writer.getvalue().decode('utf-8')
# iframe tag must be like <iframe ... ></iframe>, not optimized <iframe ... />
content = self.iframe_re.sub('\\1></iframe>', content)
return content

View File

@ -31,7 +31,7 @@ class RSS(DynastieGenerator):
def appendElement(self, dom, root, name='', content='', attributes=None):
elem = dom.createElement(name)
if attributes:
for k, v in attributes.iteritems():
for k, v in attributes.items():
elem.setAttribute(k, v)
if content != '':
elem.appendChild(dom.createTextNode(content))
@ -77,7 +77,7 @@ class RSS(DynastieGenerator):
return
f = open(filename, 'rb')
post_content = f.read()
post_content = f.read().decode('utf-8')
f.close()
if post.content_format == Post.CONTENT_TEXT:

View File

@ -71,5 +71,5 @@ class Search(Index):
writer = StrictUTF8Writer()
nodes[0].writexml(writer)
return writer.getvalue().encode('utf-8')
return writer.getvalue().decode('utf-8')

View File

@ -34,13 +34,17 @@ from dynastie.generators import *
def slugify(name):
name = name.strip()
name = normalize('NFKD', name).encode('ascii', 'ignore').replace(' ', '-').lower()
name = normalize('NFKD', name).replace(' ', '-').lower()
#remove `other` characters
name = sub('[^a-zA-Z0-9_-]', '', name)
#nomalize dashes
name = sub('-+', '-', name)
return name
class Language(models.Model):
name = models.CharField(max_length=255, unique=True)
abbrev = models.CharField(max_length=4, unique=True)
class Blog(models.Model):
name = models.CharField(max_length=255, unique=True)
title = models.CharField(max_length=255)
@ -83,7 +87,7 @@ class Blog(models.Model):
continue
engine = line.strip()
if not engine in globals():
print 'Engine ' + engine + ' doesn\'t exists'
print('Engine ' + engine + ' doesn\'t exists')
else:
self.engines.append(globals()[engine])
f.close()
@ -116,7 +120,7 @@ class Blog(models.Model):
if os.path.isdir(srcname):
if not os.path.exists(dstname):
os.makedirs(dstname)
self.copytree(srcname, dstname, ignore=True)
shutil.copytree(srcname, dstname, ignore=True)
else:
return self.copytree(srcname, dstname)
else:
@ -162,11 +166,11 @@ class Blog(models.Model):
# XXX What about devices, sockets etc.?
except (IOError, os.error), why:
except (IOError, os.error) as why:
errors.append((srcname, dstname, str(why)))
# catch the Error from the recursive copytree so that we can
# continue with other files
except Exception, err:
except Exception as err:
errors.extend(err.args[0])
if errors:
raise Exception(errors)
@ -186,7 +190,12 @@ class Blog(models.Model):
if inspect.isclass(obj) and obj.__module__.startswith("dynastie.generators"):
if obj.__module__ in generated: continue
e = obj(hash_posts, hash_posts_content)
r = e.generate(self, self.src_path, self.output_path)
print('Go for {}'.format(e))
try:
r = e.generate(self, self.src_path, self.output_path)
except Exception as err:
self.report += err
continue
generated.append(obj.__module__)
if not r is None:
self.report = self.report + '<br/>\n' + r
@ -225,9 +234,9 @@ class Editor(models.Model):
class Category(models.Model):
name = models.CharField(max_length=255, unique=True)
name_slug = models.CharField(max_length=255)
parent = models.ForeignKey('self', blank=True, null=True)
parent = models.ForeignKey('self', blank=True, null=True, on_delete=models.CASCADE)
description = models.TextField(max_length=255, blank=True)
blog = models.ForeignKey(Blog)
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
def save(self):
self.name_slug = slugify(self.name)
@ -243,7 +252,7 @@ class Category(models.Model):
class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
name_slug = models.CharField(max_length=255)
blog = models.ForeignKey(Blog)
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
def save(self):
self.name_slug = slugify(self.name)
@ -255,7 +264,15 @@ class Tag(models.Model):
if os.path.exists(output):
shutil.rmtree(output)
class PostOnlyManager(models.Manager):
def get_queryset(self):
return super(PostOnlyManager, self).get_queryset().filter(post_type='P')
class Post(models.Model):
objects = PostOnlyManager()
title = models.CharField(max_length=255)
title_slug = models.CharField(max_length=255)
category = models.ForeignKey(Category, blank=True, null=True, on_delete=models.SET_NULL)
@ -267,13 +284,15 @@ class Post(models.Model):
description = models.TextField(max_length=255, blank=True)
keywords = models.TextField(blank=True)
tags = models.ManyToManyField(Tag, blank=True, null=True)
blog = models.ForeignKey(Blog)
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
language = models.ForeignKey(Language, null=True, on_delete=models.CASCADE)
CONTENT_HTML = 0
CONTENT_TEXT = 1
CONTENT_FORMAT = (
(CONTENT_HTML, 'HTML'),
(CONTENT_TEXT, 'Text'))
content_format = models.IntegerField(choices=CONTENT_FORMAT, default=CONTENT_HTML, blank=False, null=False)
content_format = models.IntegerField(choices=CONTENT_FORMAT, default=CONTENT_TEXT, blank=False, null=False)
post_type = models.CharField(max_length=1, default='P')
def getPath(self):
filename = '/post/'
@ -344,7 +363,6 @@ class Post(models.Model):
os.mkdir(output + '/_post')
filename = output + '/_post/' + str(self.pk)
content = unicode(content)
content = content.encode('utf-8')
f = open(filename, 'wb')
@ -386,6 +404,9 @@ class Post(models.Model):
return 'text'
class Draft(Post):
objects = models.Manager()
def createDraft(self, content, tags):
b = self.blog
output = b.src_path
@ -393,7 +414,6 @@ class Draft(Post):
os.mkdir(output + '/_draft')
filename = output + '/_draft/' + str(self.pk)
content = unicode(content)
content = content.encode('utf-8')
modif = True
@ -432,8 +452,8 @@ class Draft(Post):
super(Draft, self).save()
class Comment(models.Model):
post = models.ForeignKey(Post)
parent = models.ForeignKey('self', null=True, blank=True)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE)
date = models.DateTimeField()
author = models.CharField(max_length=255)
email = models.EmailField(max_length=255, blank=True)
@ -445,7 +465,7 @@ class Comment(models.Model):
def _remove_br(self):
self.the_comment = self.the_comment.replace('<br />', '\n')
class FileOutputCache(models.Model):
name = models.CharField(max_length=512)
hash = models.CharField(max_length=512)
@ -483,3 +503,7 @@ def pre_delete_post_signal(sender, **kwargs):
@receiver(pre_save, sender=Comment)
def pre_save_comment_signal(sender, **kwargs):
kwargs['instance']._update_line_returns()
@receiver(pre_save, sender=Draft)
def pre_save_draft_signal(sender, **kwargs):
kwargs['instance'].post_type = 'D'

View File

@ -61,7 +61,7 @@ class Search:
def _saveDatabase(self, blog, hashtable):
d = pickle.dumps(hashtable)
f = open(blog.src_path + '/_search.db', 'w')
f = open(blog.src_path + '/_search.db', 'wb')
f.write(d)
f.close()
@ -69,7 +69,7 @@ class Search:
filename = blog.src_path + '/_search.db'
if not os.path.exists(filename):
print 'No search index !'
print('No search index !')
return None
f = open(filename, 'rb')
@ -100,7 +100,7 @@ class Search:
def _prepare_string(self, content):
content = self._remove_tag(content)
content = self._strip_accents(unicode(content, 'utf8'))
content = self._strip_accents(content)
return content
@ -128,12 +128,12 @@ class Search:
except:
return
f = open(filename, 'r')
content = f.read()
f = open(filename, 'rb')
content = f.read().decode('utf-8')
f.close()
self._indexContent(hashtable, index, content, 1)
self._indexContent(hashtable, index, post.title.encode('utf-8'), 5)
self._indexContent(hashtable, index, post.title, 5)
def create_index(self, blog):
hashtable = {}
@ -192,7 +192,7 @@ class Search:
def search(self, blog, string):
hashtable = self._loadDatabase(blog)
string = self._prepare_string(string.encode('utf-8'))
string = self._prepare_string(string)
wordlist = string.split(' ')
@ -207,8 +207,7 @@ class Search:
for post in hashtable[key]:
res[post[0]] = res.get(post[0],0) + post[1]
sorted_res = sorted(res.iteritems(), key=operator.itemgetter(1))
sorted_res.reverse()
sorted_res = sorted(res.items(), key=operator.itemgetter(1), reverse=True)
res = [sorted_res[i][0] for i in range(len(sorted_res))]

View File

@ -1,104 +1,115 @@
<html xmlns:dyn="http://indefero.soutade.fr/p/dynastie" xmlns="http://www.w3.org/1999/xhtml">
<html xmlns:dyn="http://indefero.soutade.fr/p/dynastie" xmlns="http://www.w3.org/1999/xhtml" lang="fr">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type"/>
<meta content="index, follow" name="robots"/>
<meta content="blog, gregory, soutade, grégory, soutadé, Grégory, Soutadé" name="keywords"/>
<meta content="Blog de Grégory Soutadé" name="description"/>
<meta content="Dynastie" name="generator"/>
<meta name="viewport" content="width=width, initial-scale=1"/>
<title>Blog de Grégory Soutadé</title>
<link rel="icon" type="image/png" href="/images/favicon.png" />
<link href="/rss.xml" rel="alternate" type="application/rss+xml" title="RSS 2.0" />
<link href="/atom.xml" rel="alternate" type="application/atom+xml" title="Atom 1.0" />
<link href="/css/blog.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="/js/blog.js"> </script>
<script src="/js/blog.js"> </script>
<dyn:block name="head"/>
</head>
<body>
<a href="/"><img id="logo" src="/images/tux_final.png"/></a>
<div class="body">
<div class="table-row">
<header>
<div class="table-cell" id="header">
<h2 id="title"><a href="/">Blog de Grégory Soutadé</a></h2>
</div>
</header>
</div>
<div class="table-row">
<div class="content">
<dyn:block name="content"/>
<div class="content" id="content">
<header>
<a href="/"><img id="logo" alt="Logo Tux" src="/images/tux_final2.png"/></a>
<div id="header">
<span id="title"><a href="/">Blog de Grégory Soutadé</a></span>
</div>
<div class="menu">
<div class="menu_content">
<div class="menu_content_header">Recherche</div>
<div id="menu_main">
<dyn:replace div_name="form" id="search_form" method="POST" action="/search/dyn:blog_id">
<input type="text" name="text" id="search_text" onkeypress="handleKeyPress(event,this.form)"/>
</dyn:replace>
</div>
</div>
<nav>
<div class="menu_content">
<div class="menu_content_header">Menu principal</div>
<div id="menu_main">
<div class="menu_content_content"><a href="/">Première page</a></div>
<div class="menu_content_content"><a href="/about.html">À propos</a></div>
<div class="menu_content_content"><a href="/all_posts.html">Tous les articles</a></div>
<div class="menu_content_content"><a href="http://indefero.soutade.fr">Projets personnels</a></div>
<div class="menu_content_content"><a href="/ljdc">Les joies du code</a></div>
</div>
</div>
<div class="menu_content">
<div class="menu_content_header">Catégories</div>
<div class="menu_content_content">
<ul>
<li><a href="/category/articles">Articles</a></li>
<li><a href="/category/musique">Musique</a></li>
<li><a href="/category/informatique">Informatique</a></li>
<li><a href="/category/cinema">Cinéma</a></li>
<li><a href="/category/sport">Sport</a></li>
<li><a href="/category/configurations-pc">Configurations PC</a></li>
</ul>
</div>
</div>
<div class="menu_content">
<div class="menu_content_header">Tags</div>
<div class="menu_content_content">
<ul>
<li><a href="/tag/inenglish">In English</a></li>
<li><a href="/tag/python">Python</a></li>
<li><a href="/tag/sheevaplug">SheevaPlug</a></li>
<li><a href="/tag/course-a-pied">Course à pied</a></li>
<li><a href="/tag/tag-rugby">Tag rugby</a></li>
<li><a href="/tag/politique">Politique</a></li>
</ul>
</div>
</div>
<div class="menu_content">
<div class="menu_content_header">Archives</div>
<div class="menu_content_content">
<ul>
<li><a href="/archive/2015">2015</a></li>
<li><a href="/archive/2014">2014</a></li>
<li><a href="/archive/2013">2013</a></li>
<li><a href="/archive/2012">2012</a></li>
<li><a href="/archive/2011">2011</a></li>
<li><a href="/archive/2010">2010</a></li>
</ul>
</div>
</div>
<div class="feed">
<a href="/rss.xml"><img src="/images/rss.png" alt="RSS feeds"></img> RSS</a><a href="/atom.xml"><img src="/images/atom.png" alt="Atom feeds"></img> Atom</a>
</div>
</nav>
<p style="text-align:center">Généré avec <a href="http://indefero.soutade.fr/p/dynastie">Dynastie</a></p>
</div>
</div>
</header>
<dyn:block name="content"/>
<footer>
<div class="footer">
Copyright © 2010-2016 Grégory Soutadé.<br/>
Copyright © 2010-2022 Grégory Soutadé.<br/>
Tous droits réservés.
</div>
</footer>
</div>
<div class="hamburger" id="hamburger">
<a href="#" onclick="switchMenu();return false;">
<img id="hamburger_logo" alt="Menu" src="/images/hamburger.png"/>
</a>
</div>
<div class="menu" id="menu" lang="fr">
<div class="menu_content">
<div class="menu_content_header">Recherche</div>
<div class="menu_main">
<dyn:replace div_name="form" id="search_form" method="POST" action="/search/dyn:blog_id">
<input type="text" name="text" id="search_text" onkeypress="handleKeyPress(event,this.form)"/>
</dyn:replace>
</div>
</div>
<nav>
<div class="menu_content">
<div class="menu_content_header">Menu principal</div>
<div class="menu_main">
<div class="menu_content_content"><a href="/">Première page</a></div>
<div class="menu_content_content"><a href="/about.html">À propos</a></div>
<div class="menu_content_content"><a href="/all_posts.html">Tous les articles</a></div>
<div class="menu_content_content"><a href="http://indefero.soutade.fr">Projets personnels</a></div>
<div class="menu_content_content"><a href="/ljdc">Les joies du code</a></div>
</div>
</div>
<div class="menu_content">
<div class="menu_content_header">Catégories</div>
<div class="menu_content_content">
<ul>
<li><a href="/category/articles">Articles</a></li>
<li><a href="/category/musique">Musique</a></li>
<li><a href="/category/informatique">Informatique</a></li>
<li><a href="/category/sport">Sport</a></li>
<li><a href="/category/configurations-pc">Configurations PC</a></li>
</ul>
</div>
</div>
<div class="menu_content">
<div class="menu_content_header">Tags</div>
<div class="menu_content_content">
<ul>
<li><a href="/tag/inenglish">In English</a></li>
<li><a href="/tag/python">Python</a></li>
<li><a href="/tag/sheevaplug">SheevaPlug</a></li>
<li><a href="/tag/course-a-pied">Course à pied</a></li>
<li><a href="/tag/tag-rugby">Tag rugby</a></li>
<li><a href="/tag/politique">Politique</a></li>
</ul>
</div>
</div>
<div class="menu_content">
<div class="menu_content_header">Archives</div>
<div class="menu_content_content">
<ul>
<li><a href="/archive/2021">2021</a></li>
<li><a href="/archive/2020">2020</a></li>
<li><a href="/archive/2019">2019</a></li>
<li><a href="/archive/2018">2018</a></li>
<li><a href="/archive/2017">2017</a></li>
<li><a href="/archive/2016">2016</a></li>
<li><a href="/archive/2015">2015</a></li>
<li><a href="/archive/2014">2014</a></li>
<li><a href="/archive/2013">2013</a></li>
<li><a href="/archive/2012">2012</a></li>
<li><a href="/archive/2011">2011</a></li>
<li><a href="/archive/2010">2010</a></li>
</ul>
</div>
</div>
<div class="feed">
<a href="/rss.xml"><img src="/images/rss.png" alt="RSS feeds"/> RSS</a><a href="/atom.xml"><img src="/images/atom.png" alt="Atom feeds"/> Atom</a>
</div>
<div class="newsletter">
<p>Newsletter
<input id="pannous_email" type="email" onclick="clear_your_email();" value="Your email"/>
<input type="button" value="Subscribe" onclick="pannous_list_subscribe();"/>
</p>
</div>
</nav>
<p style="text-align:center">Généré avec <a href="http://indefero.soutade.fr/p/dynastie">Dynastie</a></p>
</div>
</body>
</html>

View File

@ -6,100 +6,112 @@
<dyn:meta name="keywords"/>
<dyn:meta name="title"/>
<dyn:meta name="author"/>
<dyn:meta name="lang"/>
<meta content="Dynastie" name="generator"/>
<meta name="viewport" content="width=width, initial-scale=1"/>
<title>Blog de Grégory Soutadé</title>
<link rel="icon" type="image/png" href="/images/favicon.png" />
<link href="/rss.xml" rel="alternate" type="application/rss+xml" title="RSS 2.0" />
<link href="/atom.xml" rel="alternate" type="application/atom+xml" title="Atom 1.0" />
<link href="/css/blog.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="/js/blog.js"> </script>
<script src="/js/blog.js"> </script>
</head>
<body onLoad="javascript:init();">
<a href="/"><img id="logo" src="/images/tux_final.png"/></a>
<div class="body">
<div class="table-row">
<header>
<div class="table-cell" id="header">
<h2 id="title"><a href="/">Blog de Grégory Soutadé</a></h2>
</div>
</header>
</div>
<div class="table-row">
<div class="content">
<dyn:block name="content"/>
<div class="content" id="content">
<header>
<a href="/"><img id="logo" alt="Logo Tux" src="/images/tux_final2.png"/></a>
<div id="header">
<span id="title"><a href="/">Blog de Grégory Soutadé</a></span>
</div>
<div class="menu">
<div class="menu_content">
<div class="menu_content_header">Recherche</div>
<div id="menu_main">
<dyn:replace div_name="form" id="search_form" method="POST" action="/search/dyn:blog_id">
<input type="text" name="text" id="search_text" onkeypress="handleKeyPress(event,this.form)"/>
</dyn:replace>
</div>
</div>
<nav>
<div class="menu_content">
<div class="menu_content_header">Menu principal</div>
<div id="menu_main">
<div class="menu_content_content"><a href="/">Première page</a></div>
<div class="menu_content_content"><a href="/about.html">À propos</a></div>
<div class="menu_content_content"><a href="/all_posts.html">Tous les articles</a></div>
<div class="menu_content_content"><a href="http://indefero.soutade.fr">Projets personnels</a></div>
<div class="menu_content_content"><a href="/ljdc">Les joies du code</a></div>
</div>
</div>
<div class="menu_content">
<div class="menu_content_header">Catégories</div>
<div class="menu_content_content">
<ul>
<li><a href="/category/articles">Articles</a></li>
<li><a href="/category/musique">Musique</a></li>
<li><a href="/category/informatique">Informatique</a></li>
<li><a href="/category/cinema">Cinéma</a></li>
<li><a href="/category/sport">Sport</a></li>
<li><a href="/category/configurations-pc">Configurations PC</a></li>
</ul>
</div>
</div>
<div class="menu_content">
<div class="menu_content_header">Tags</div>
<div class="menu_content_content">
<ul>
<li><a href="/tag/inenglish">In English</a></li>
<li><a href="/tag/python">Python</a></li>
<li><a href="/tag/sheevaplug">SheevaPlug</a></li>
<li><a href="/tag/course-a-pied">Course à pied</a></li>
<li><a href="/tag/tag-rugby">Tag rugby</a></li>
<li><a href="/tag/politique">Politique</a></li>
</ul>
</div>
</div>
<div class="menu_content">
<div class="menu_content_header">Archives</div>
<div class="menu_content_content">
<ul>
<li><a href="/archive/2015">2015</a></li>
<li><a href="/archive/2014">2014</a></li>
<li><a href="/archive/2013">2013</a></li>
<li><a href="/archive/2012">2012</a></li>
<li><a href="/archive/2011">2011</a></li>
<li><a href="/archive/2010">2010</a></li>
</ul>
</div>
</div>
<div class="feed">
<a href="/rss.xml"><img src="/images/rss.png" alt="RSS feeds"></img> RSS</a><a href="/atom.xml"><img src="/images/atom.png" alt="Atom feeds"></img> Atom</a>
</div>
</nav>
<p style="text-align:center">Généré avec <a href="http://indefero.soutade.fr/p/dynastie">Dynastie</a></p>
</div>
</div>
</header>
<dyn:block name="content"/>
<footer>
<div class="footer">
Copyright © 2010-2016 Grégory Soutadé.<br/>
Copyright © 2010-2022 Grégory Soutadé.<br/>
Tous droits réservés.
</div>
</footer>
</div>
<div class="hamburger" id="hamburger">
<a href="#" onclick="switchMenu();return false;">
<img id="hamburger_logo" alt="Menu" src="/images/hamburger.png"/>
</a>
</div>
<div class="menu" id="menu" lang="fr">
<div class="menu_content">
<div class="menu_content_header">Recherche</div>
<div class="menu_main">
<dyn:replace div_name="form" id="search_form" method="POST" action="/search/dyn:blog_id">
<input type="text" name="text" id="search_text" onkeypress="handleKeyPress(event,this.form)"/>
</dyn:replace>
</div>
</div>
<nav>
<div class="menu_content">
<div class="menu_content_header">Menu principal</div>
<div class="menu_main">
<div class="menu_content_content"><a href="/">Première page</a></div>
<div class="menu_content_content"><a href="/about.html">À propos</a></div>
<div class="menu_content_content"><a href="/all_posts.html">Tous les articles</a></div>
<div class="menu_content_content"><a href="http://indefero.soutade.fr">Projets personnels</a></div>
<div class="menu_content_content"><a href="/ljdc">Les joies du code</a></div>
</div>
</div>
<div class="menu_content">
<div class="menu_content_header">Catégories</div>
<div class="menu_content_content">
<ul>
<li><a href="/category/articles">Articles</a></li>
<li><a href="/category/musique">Musique</a></li>
<li><a href="/category/informatique">Informatique</a></li>
<li><a href="/category/sport">Sport</a></li>
<li><a href="/category/configurations-pc">Configurations PC</a></li>
</ul>
</div>
</div>
<div class="menu_content">
<div class="menu_content_header">Tags</div>
<div class="menu_content_content">
<ul>
<li><a href="/tag/inenglish">In English</a></li>
<li><a href="/tag/python">Python</a></li>
<li><a href="/tag/sheevaplug">SheevaPlug</a></li>
<li><a href="/tag/course-a-pied">Course à pied</a></li>
<li><a href="/tag/tag-rugby">Tag rugby</a></li>
<li><a href="/tag/politique">Politique</a></li>
</ul>
</div>
</div>
<div class="menu_content">
<div class="menu_content_header">Archives</div>
<div class="menu_content_content">
<ul>
<li><a href="/archive/2021">2021</a></li>
<li><a href="/archive/2020">2020</a></li>
<li><a href="/archive/2019">2019</a></li>
<li><a href="/archive/2018">2018</a></li>
<li><a href="/archive/2017">2017</a></li>
<li><a href="/archive/2016">2016</a></li>
<li><a href="/archive/2015">2015</a></li>
<li><a href="/archive/2014">2014</a></li>
<li><a href="/archive/2013">2013</a></li>
<li><a href="/archive/2012">2012</a></li>
<li><a href="/archive/2011">2011</a></li>
<li><a href="/archive/2010">2010</a></li>
</ul>
</div>
</div>
<div class="feed">
<a href="/rss.xml"><img src="/images/rss.png" alt="RSS feeds"/> RSS</a><a href="/atom.xml"><img src="/images/atom.png" alt="Atom feeds"/> Atom</a>
</div>
<div class="newsletter">
<p>Newsletter
<input id="pannous_email" type="email" onclick="clear_your_email();" value="Your email"/>
<input type="button" value="Subscribe" onclick="pannous_list_subscribe();"/>
</p>
</div>
</nav>
<p style="text-align:center">Généré avec <a href="http://indefero.soutade.fr/p/dynastie">Dynastie</a></p>
</div>
</body>
</html>