From a322d6c4e8281b699292c4421c2143c59d091cb1 Mon Sep 17 00:00:00 2001 From: Alexander Artemenko Date: Fri, 17 Dec 2010 00:04:45 +0300 Subject: [PATCH 1/8] Lowercase meta field's name before looking the processor. --- pelican/readers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pelican/readers.py b/pelican/readers.py index e9520fe6..0616eecd 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -50,11 +50,12 @@ class MarkdownReader(object): metadatas = {} for name, value in md.Meta.items(): + name = name.lower() if name in _METADATAS_FIELDS: meta = _METADATAS_FIELDS[name](value[0]) else: meta = value[0] - metadatas[name.lower()] = meta + metadatas[name] = meta return content, metadatas _EXTENSIONS = {'rst': RstReader, 'md': MarkdownReader} # supported formats From 3decf7f5195dcab046b7c303ca60bbfac463a381 Mon Sep 17 00:00:00 2001 From: Alexander Artemenko Date: Fri, 17 Dec 2010 00:05:39 +0300 Subject: [PATCH 2/8] Added .gitignore file. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c53cdba0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.egg-info +.*.swp +.*.swo From 658e1203b21c87086210b370aecd49c3268ef612 Mon Sep 17 00:00:00 2001 From: Alexander Artemenko Date: Fri, 17 Dec 2010 00:07:55 +0300 Subject: [PATCH 3/8] Simplier metadata processing, using dict's 'get' method with default value. --- pelican/readers.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pelican/readers.py b/pelican/readers.py index 0616eecd..7ff393c5 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -51,11 +51,9 @@ class MarkdownReader(object): metadatas = {} for name, value in md.Meta.items(): name = name.lower() - if name in _METADATAS_FIELDS: - meta = _METADATAS_FIELDS[name](value[0]) - else: - meta = value[0] - metadatas[name] = meta + metadatas[name] = _METADATAS_FIELDS.get( + name, lambda x:x + )(value[0]) return content, metadatas _EXTENSIONS = {'rst': RstReader, 'md': MarkdownReader} # supported formats From bc5a19a37baccddd5719a3b6586ce001b1e3187c Mon Sep 17 00:00:00 2001 From: Alexander Artemenko Date: Fri, 17 Dec 2010 00:32:12 +0300 Subject: [PATCH 4/8] Slugify is broken for non-ascii titles. Now slug could be redefined, using the metadata. --- pelican/contents.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 0caceed0..cfc5e442 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -20,6 +20,12 @@ class Page(object): if 'AUTHOR' in settings: self.author = settings['AUTHOR'] + if not hasattr(self, 'slug'): + self.slug = slugify(self.title) + + if not hasattr(self, 'url'): + self.url = '%s.html' % self.slug + if filename: self.filename = filename @@ -29,14 +35,6 @@ class Page(object): if not hasattr(self, prop): raise NameError(prop) - @property - def url(self): - return '%s.html' % self.slug - - @property - def slug(self): - return slugify(self.title) - @property def summary(self): return truncate_html_words(self.content, 50) From 1a31c74d5c7b34c4cdb483e527d116f63c25b5f2 Mon Sep 17 00:00:00 2001 From: Alexander Artemenko Date: Fri, 17 Dec 2010 00:32:27 +0300 Subject: [PATCH 5/8] .gitignore updated. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c53cdba0..29ac491f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.egg-info .*.swp .*.swo +*.pyc From 3afdb8fcffec3061011ccf33d4b47ba9e4afe1b8 Mon Sep 17 00:00:00 2001 From: Alexander Artemenko Date: Sun, 19 Dec 2010 00:32:43 +0300 Subject: [PATCH 6/8] Articles translations are enabled. To use this feature, you need to add a Lang meta into the each article and set DEFAULT_LANG setting which is 'en' by default. Than, only articles in the default language (or without variant in default language) will be shown in the index of the blog. And each Article will have translations list. The same is applicable to the static Pages. Also, separate feeds are generated for each language except default. --- pelican/contents.py | 13 ++++++++++++- pelican/generators.py | 36 +++++++++++++++++++++++++++++------- pelican/settings.py | 1 + pelican/utils.py | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 8 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index cfc5e442..cc869625 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -12,6 +12,8 @@ class Page(object): def __init__(self, content, metadatas={}, settings={}, filename=None): self.content = content + self.translations = [] + self.status = "published" # default value for key, value in metadatas.items(): setattr(self, key, value) @@ -20,11 +22,20 @@ class Page(object): if 'AUTHOR' in settings: self.author = settings['AUTHOR'] + default_lang = settings.get('DEFAULT_LANG', 'en').lower() + if not hasattr(self, 'lang'): + self.lang = default_lang + + self.in_default_lang = (self.lang == default_lang) + if not hasattr(self, 'slug'): self.slug = slugify(self.title) if not hasattr(self, 'url'): - self.url = '%s.html' % self.slug + if self.in_default_lang: + self.url = '%s.html' % self.slug + else: + self.url = '%s-%s.html' % (self.slug, self.lang) if filename: self.filename = filename diff --git a/pelican/generators.py b/pelican/generators.py index d6077408..59b3866e 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -1,11 +1,13 @@ from operator import attrgetter +from itertools import chain from datetime import datetime +from collections import defaultdict import os from jinja2 import Environment, FileSystemLoader from jinja2.exceptions import TemplateNotFound -from pelican.utils import update_dict, copytree +from pelican.utils import update_dict, copytree, process_translations from pelican.contents import Article, Page, is_valid_content from pelican.readers import read_file @@ -75,7 +77,8 @@ class ArticlesGenerator(Generator): def __init__(self, *args, **kwargs): """initialize properties""" - self.articles = [] + self.articles = [] # only articles in default language + self.translations = [] self.dates = {} self.tags = {} self.categories = {} @@ -100,6 +103,15 @@ class ArticlesGenerator(Generator): self.settings['CATEGORY_FEED_RSS'] % cat, feed_type='rss') + translations_feeds = defaultdict(list) + for article in self.translations: + translations_feeds[article.lang].append(article) + + for lang, items in translations_feeds.items(): + items.sort(key=attrgetter('date'), reverse=True) + writer.write_feed(items, self.context, + self.settings['TRANSLATION_FEED'] % lang) + def generate_pages(self, writer): """Generate the pages on the disk @@ -116,7 +128,7 @@ class ArticlesGenerator(Generator): for cat in self.categories: write('category/%s.html' % cat, templates['category'], self.context, category=cat, articles=self.categories[cat]) - for article in self.articles: + for article in chain(self.translations, self.articles): write('%s' % article.url, templates['article'], self.context, article=article, category=article.category) @@ -126,6 +138,7 @@ class ArticlesGenerator(Generator): # return the list of files to use files = self.get_files(self.path, exclude=['pages',]) + all_articles = [] for f in files: content, metadatas = read_file(f) @@ -149,11 +162,17 @@ class ArticlesGenerator(Generator): if not is_valid_content(article, f): continue - update_dict(self.categories, article.category, article) if hasattr(article, 'tags'): for tag in article.tags: update_dict(self.tags, tag, article) - self.articles.append(article) + all_articles.append(article) + + self.articles, self.translations = process_translations(all_articles) + + for article in self.articles: + # only main articles are listed in categories, not translations + update_dict(self.categories, article.category, article) + # sort the articles by date self.articles.sort(key=attrgetter('date'), reverse=True) @@ -175,20 +194,23 @@ class PagesGenerator(Generator): super(PagesGenerator, self).__init__(*args, **kwargs) def generate_context(self): + all_pages = [] for f in self.get_files(os.sep.join((self.path, 'pages'))): content, metadatas = read_file(f) page = Page(content, metadatas, settings=self.settings, filename=f) if not is_valid_content(page, f): continue - self.pages.append(page) + all_pages.append(page) + + self.pages, self.translations = process_translations(all_pages) self._update_context(('pages', )) self.context['PAGES'] = self.pages def generate_output(self, writer): templates = self.get_templates() - for page in self.pages: + for page in chain(self.translations, self.pages): writer.write_file('pages/%s' % page.url, templates['page'], self.context, page=page) diff --git a/pelican/settings.py b/pelican/settings.py index e3c511d3..9e294b05 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -10,6 +10,7 @@ _DEFAULT_CONFIG = {'PATH': None, 'THEME_STATIC_PATHS': ['static',], 'FEED': 'feeds/all.atom.xml', 'CATEGORY_FEED': 'feeds/%s.atom.xml', + 'TRANSLATION_FEED': 'feeds/all-%s.atom.xml', 'SITENAME': 'A Pelican Blog', 'DISPLAY_PAGES_ON_MENU': True, 'PDF_GENERATOR': False, diff --git a/pelican/utils.py b/pelican/utils.py index c1410039..97a6c64c 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -4,6 +4,8 @@ import os import shutil from datetime import datetime from codecs import open as _open +from itertools import groupby +from operator import attrgetter def update_dict(mapping, key, value): """Update a dict intenal list @@ -147,3 +149,38 @@ def truncate_html_words(s, num, end_text='...'): # Return string return out + +def process_translations(content_list): + """ Finds all translation and returns + tuple with two lists (index, translations). + Index list includes items in default language + or items which have no variant in default language. + + Also, for each content_list item, it + sets attribute 'translations' + """ + grouped_by_slugs = groupby(content_list, attrgetter('slug')) + index = [] + translations = [] + + for slug, items in grouped_by_slugs: + items = list(items) + # find items with default language + default_lang_items = filter( + attrgetter('in_default_lang'), + items + ) + len_ = len(default_lang_items) + if len_ > 1: + print u' [warning] there are %s varianits of "%s"' % (len_, slug) + elif len_ == 0: + default_lang_items = items[:1] + + index.extend(default_lang_items) + translations.extend(filter( + lambda x: x not in default_lang_items, + items + )) + for a in items: + a.translations = filter(lambda x: x != a, items) + return index, translations From 1d74de2559a96b48b05c4ebeab7fc13acc9f5302 Mon Sep 17 00:00:00 2001 From: Alexander Artemenko Date: Tue, 21 Dec 2010 00:21:29 +0300 Subject: [PATCH 7/8] Added another option CLEAN_URLS, to use generated files with mod_rewrite and to show nice urls to the end user. --- pelican/contents.py | 13 ++++++++++--- pelican/generators.py | 4 ++-- pelican/settings.py | 1 + 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index cc869625..1ecced30 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -31,11 +31,18 @@ class Page(object): if not hasattr(self, 'slug'): self.slug = slugify(self.title) - if not hasattr(self, 'url'): + if not hasattr(self, 'save_as'): if self.in_default_lang: - self.url = '%s.html' % self.slug + self.save_as = '%s.html' % self.slug + clean_url = '%s/' % self.slug else: - self.url = '%s-%s.html' % (self.slug, self.lang) + self.save_as = '%s-%s.html' % (self.slug, self.lang) + clean_url = '%s-%s/' % (self.slug, self.lang) + + if settings.get('CLEAN_URLS', False): + self.url = clean_url + else: + self.url = self.save_as if filename: self.filename = filename diff --git a/pelican/generators.py b/pelican/generators.py index 59b3866e..88579c26 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -129,7 +129,7 @@ class ArticlesGenerator(Generator): write('category/%s.html' % cat, templates['category'], self.context, category=cat, articles=self.categories[cat]) for article in chain(self.translations, self.articles): - write('%s' % article.url, + write(article.save_as, templates['article'], self.context, article=article, category=article.category) @@ -211,7 +211,7 @@ class PagesGenerator(Generator): def generate_output(self, writer): templates = self.get_templates() for page in chain(self.translations, self.pages): - writer.write_file('pages/%s' % page.url, templates['page'], + writer.write_file('pages/%s' % page.save_as, templates['page'], self.context, page=page) diff --git a/pelican/settings.py b/pelican/settings.py index 9e294b05..dba11906 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -19,6 +19,7 @@ _DEFAULT_CONFIG = {'PATH': None, 'CSS_FILE': 'main.css', 'REVERSE_ARCHIVE_ORDER': False, 'KEEP_OUTPUT_DIRECTORY': False, + 'CLEAN_URLS': False, # use /blah/ instead /blah.html in urls } def read_settings(filename): From a45f705bda9b503c27e80d0a2b6503b9ab868ec2 Mon Sep 17 00:00:00 2001 From: Alexander Artemenko Date: Tue, 21 Dec 2010 00:22:15 +0300 Subject: [PATCH 8/8] Typo was fixed. --- pelican/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/utils.py b/pelican/utils.py index 97a6c64c..fe7ab009 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -172,7 +172,7 @@ def process_translations(content_list): ) len_ = len(default_lang_items) if len_ > 1: - print u' [warning] there are %s varianits of "%s"' % (len_, slug) + print u' [warning] there are %s variants of "%s"' % (len_, slug) elif len_ == 0: default_lang_items = items[:1]