1
0
Fork 0
forked from github/pelican
pelican-theme/pelican/generator.py

269 lines
9 KiB
Python
Raw Normal View History

2010-08-17 15:04:12 +02:00
# -*- coding: utf-8 -*-
2010-08-14 05:45:16 +02:00
import os
2010-08-17 15:04:12 +02:00
import re
import shutil
2010-08-17 15:04:12 +02:00
from codecs import open
from datetime import datetime
from docutils import core
2010-08-14 05:45:16 +02:00
from functools import partial
from operator import attrgetter
2010-08-14 05:45:16 +02:00
2010-08-17 15:04:12 +02:00
from jinja2 import Environment, FileSystemLoader
from feedgenerator import Atom1Feed
2010-08-17 15:04:12 +02:00
import rstdirectives # import the directives to have pygments support
2010-08-14 05:45:16 +02:00
_TEMPLATES = ('index', 'tag', 'tags', 'article', 'category', 'categories',
'archives')
_DIRECT_TEMPLATES = ('index', 'tags', 'categories', 'archives')
_DEFAULT_THEME =\
os.sep.join([os.path.dirname(os.path.abspath(__file__)), "themes"])
_DEFAULT_CONFIG = {'PATH': None,
'THEME': _DEFAULT_THEME,
'OUTPUT_PATH': 'output/',
'MARKUP': 'rst',
'STATIC_PATHS': ['css', 'images'],
'FEED': 'atom.xml',
'CATEGORY_FEED': '%s.xml',
'BLOGNAME': 'A Pelican Blog',
'BLOGURL': ''}
2010-08-14 05:45:16 +02:00
def generate_output(path=None, theme=None, output_path=None, markup=None,
settings=None):
2010-08-14 05:45:16 +02:00
"""Given a list of files, a template and a destination,
output the static files.
That's the main logic of pelican.
:param path: the path where to find the files to parse
:param theme: where to search for templates
2010-08-14 05:45:16 +02:00
:param output_path: where to output the generated files
:param markup: the markup language to use while parsing
:param settings: the settings file to use
2010-08-14 05:45:16 +02:00
"""
# get the settings
context = read_settings(settings)
path = path or context['PATH']
theme = theme or context['THEME']
output_path = output_path or context['OUTPUT_PATH']
2010-08-14 05:45:16 +02:00
output_path = os.path.realpath(output_path)
markup = markup or context['MARKUP']
# get the list of files to parse
if not path:
raise Exception('you need to speciffy a path to search the docs on !')
files = []
for root, dirs, temp_files in os.walk(path, followlinks=True):
files.extend([os.sep.join((root, f)) for f in temp_files
if f.endswith('.%s' % markup)])
2010-08-14 05:45:16 +02:00
articles, dates, years, tags, categories = [], {}, {}, {}, {}
2010-08-14 05:45:16 +02:00
# for each file, get the informations.
for f in files:
f = os.path.abspath(f)
article = Article(open(f, encoding='utf-8').read(), markup, context)
2010-08-14 05:45:16 +02:00
articles.append(article)
if hasattr(article, 'date'):
update_dict(dates, article.date.strftime('%Y-%m-%d'), article)
2010-08-14 05:45:16 +02:00
update_dict(years, article.date.year, article)
if hasattr(article, 'tags'):
for tag in article.tags:
update_dict(tags, tag, article)
if hasattr(article, 'category'):
update_dict(categories, article.category, article)
2010-08-18 15:09:04 +02:00
# order the articles by date
articles.sort(key=attrgetter('date'), reverse=True)
templates = get_templates(theme)
for item in ('articles', 'dates', 'years', 'tags', 'categories'):
value = locals()[item]
if hasattr(value, 'items'):
value = value.items()
context[item] = value
# generate the output
2010-08-14 05:45:16 +02:00
generate = partial(generate_file, output_path)
for template in _DIRECT_TEMPLATES:
generate('%s.html' % template, templates[template], context)
2010-08-14 05:45:16 +02:00
for tag in tags:
generate('tag/%s.html' % tag, templates['tag'], context, tag=tag)
2010-08-14 05:45:16 +02:00
for cat in categories:
generate('category/%s.html' % cat, templates['category'], context,
2010-08-14 05:45:16 +02:00
category=cat)
for article in articles:
generate('%s' % article.url,
templates['article'], context, article=article)
generate_feed(articles, context, output_path, context['FEED'])
for category, articles in categories.values():
articles.sort(key=attrgetter('date'), reverse=True)
generate_feed(articles, context, output_path,
context['CATEGORY_FEED'] % category)
# copy static paths to output
for path in context['STATIC_PATHS']:
try:
shutil.copytree(os.path.join(theme, path),
os.path.join(output_path, path))
except OSError:
pass
def generate_feed(articles, context, output_path=None, filename=None):
"""Generate a feed with the list of articles provided
Return the feed. If no output_path or filename is specified, just return
the feed object.
:param articles: the articles to put on the feed.
:param context: the context to get the feed metadatas.
:param output_path: where to output the file.
:param filename: the filename to output.
"""
feed = Atom1Feed(
title=context['BLOGNAME'],
link=context['BLOGURL'],
feed_url='%s/%s' % (context['BLOGURL'], filename),
description=context.get('BLOGSUBTITLE', ''))
for article in articles:
feed.add_item(
title=article.title,
link='%s/%s' % (context['BLOGURL'], article.url),
description=article.content,
author_name=getattr(article, 'author', 'John Doe'),
pubdate=article.date)
if output_path and filename:
fp = open(os.path.join(output_path, context['FEED']), 'w')
feed.write(fp, 'utf-8')
fp.close()
return feed
2010-08-14 05:45:16 +02:00
def generate_file(path, name, template, context, **kwargs):
"""Write the file with the given informations
:param path: where to generate the file.
:param name: name of the file to output
:param template: template to use to generate the content
:param context: dict to pass to the templates.
:param **kwargs: additional variables to pass to the templates
"""
2010-08-14 05:45:16 +02:00
context.update(kwargs)
output = template.render(context)
filename = os.sep.join((path, name))
2010-08-14 05:45:16 +02:00
try:
os.makedirs(os.path.dirname(filename))
except Exception:
pass
2010-08-17 15:04:12 +02:00
with open(filename, 'w', encoding='utf-8') as f:
2010-08-14 05:45:16 +02:00
f.write(output)
print filename
def get_templates(path=None):
"""Return the templates to use"""
path = os.path.join(path, 'templates')
2010-08-14 05:45:16 +02:00
env = Environment(loader=FileSystemLoader(path))
templates = {}
for template in _TEMPLATES:
templates[template] = env.get_template('%s.html' % template)
return templates
def update_dict(mapping, key, value):
"""Update a dict intenal list
:param mapping: the mapping to update
:param key: the key of the mapping to update.
:param value: the value to append to the list.
"""
2010-08-14 05:45:16 +02:00
if key not in mapping:
mapping[key] = []
mapping[key].append(value)
def read_settings(filename):
2010-08-18 15:08:35 +02:00
"""Load a Python file into a dictionary.
"""
context = _DEFAULT_CONFIG.copy()
if filename:
2010-08-18 15:08:35 +02:00
tempdict = {}
execfile(filename, tempdict)
for key in tempdict:
if key.isupper():
2010-08-18 15:08:35 +02:00
context[key] = tempdict[key]
return context
_METADATA = re.compile('.. ([a-z]+): (.*)', re.M)
_METADATAS_FIELDS = {'tags': lambda x: x.split(', '),
'date': lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M'),
'category': lambda x: x,
'author': lambda x: x}
def parse_metadata(string):
"""Return a dict, containing a list of metadata informations, found
2010-08-14 05:45:16 +02:00
whithin the given string.
:param string: the string to search the metadata in
"""
output = {}
for m in _METADATA.finditer(string):
name = m.group(1)
value = m.group(2)
if name in _METADATAS_FIELDS:
output[name] = _METADATAS_FIELDS[name](value)
return output
def slugify(value):
"""
Normalizes string, converts to lowercase, removes non-alpha characters,
and converts spaces to hyphens.
Took from django sources.
"""
import unicodedata
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
return re.sub('[-\s]+', '-', value)
class Article(object):
"""Represents an article.
Given a string, complete it's properties from here.
:param string: the string to parse, containing the original content.
:param markup: the markup language to use while parsing.
"""
def __init__(self, string, markup=None, config={}):
2010-08-14 05:45:16 +02:00
if markup == None:
markup = 'rst'
2010-08-14 05:45:16 +02:00
for key, value in parse_metadata(string).items():
2010-08-14 05:45:16 +02:00
setattr(self, key, value)
if markup == 'rst':
2010-08-17 15:04:12 +02:00
extra_params = {'input_encoding': 'unicode',
'initial_header_level': '2'}
rendered_content = core.publish_parts(string, writer_name='html',
settings_overrides=extra_params)
2010-08-14 05:45:16 +02:00
self.title = rendered_content.get('title')
self.content = rendered_content.get('body')
2010-08-18 15:09:04 +02:00
if not hasattr(self, 'author'):
if 'AUTHOR' in config:
self.author = config['AUTHOR']
2010-08-14 05:45:16 +02:00
@property
def url(self):
return '%s.html' % slugify(self.title)
2010-08-14 05:45:16 +02:00
@property
def summary(self):
return self.content
2010-08-18 15:09:04 +02:00
2010-08-14 05:45:16 +02:00
def __repr__(self):
return '<%s "%s">' % (self.__class__.__name__, self.title)