Merge branch 'master' of github.com:ametaireau/pelican

This commit is contained in:
Skami18 2011-05-08 13:33:52 +02:00
commit bf38517a91
14 changed files with 146 additions and 86 deletions

View file

@ -49,14 +49,14 @@ Take a look to the Markdown reader::
md = Markdown(extensions = ['meta', 'codehilite'])
content = md.convert(text)
metadatas = {}
metadata = {}
for name, value in md.Meta.items():
if name in _METADATAS_FIELDS:
meta = _METADATAS_FIELDS[name](value[0])
if name in _METADATA_FIELDS:
meta = _METADATA_FIELDS[name](value[0])
else:
meta = value[0]
metadatas[name.lower()] = meta
return content, metadatas
metadata[name.lower()] = meta
return content, metadata
Simple isn't it ?

View file

@ -32,6 +32,8 @@ Setting name (default value) what does it do?
`DEFAULT_CATEGORY` (``'misc'``) The default category to fallback on.
`DEFAULT_DATE_FORMAT` (``'%a %d %B %Y'``) The default date format you want to use.
`DEFAULT_LANG` (``'en'``) The default language to use.
`DEFAULT_METADATA` (``()``) A list containing the default metadata for
each content (articles, pages, etc.)
`DEFAULT_ORPHANS` (0) The minimum number of articles allowed on the
last page. Use this when you don't want to
have a last page with very few articles.
@ -45,8 +47,11 @@ Setting name (default value) what does it do?
informations from the metadata
`FEED` (``'feeds/all.atom.xml'``) relative url to output the atom feed.
`FEED_RSS` (``None``, i.e. no RSS) relative url to output the rss feed.
`FILES_TO_COPY` (``()``, no files) A list of tuples (source, destination) of files
to copy from the source directory to the
output path
`JINJA_EXTENSIONS` (``[]``) A list of any Jinja2 extensions you want to use.
`KEEP_OUTPUT_DIRECTORY` (``False``) Keep the output directory and just update all
`DELETE_OUTPUT_DIRECTORY` (``False``) Delete the output directory instead of just updating all
the generated files.
`LOCALE` (''[2]_) Change the locale.
`MARKUP` (``('rst', 'md')``) A list of available markup languages you want

View file

@ -1,5 +1,6 @@
import argparse
import os
import time
from pelican.generators import (ArticlesGenerator, PagesGenerator,
StaticGenerator, PdfGenerator)
@ -13,7 +14,7 @@ VERSION = "2.6.0"
class Pelican(object):
def __init__(self, settings=None, path=None, theme=None, output_path=None,
markup=None, keep=False):
markup=None, delete_outputdir=False):
"""Read the settings, and performs some checks on the environment
before doing anything else.
"""
@ -31,7 +32,7 @@ class Pelican(object):
output_path = output_path or settings['OUTPUT_PATH']
self.output_path = os.path.realpath(output_path)
self.markup = markup or settings['MARKUP']
self.keep = keep or settings['KEEP_OUTPUT_DIRECTORY']
self.delete_outputdir = delete_outputdir or settings['DELETE_OUTPUT_DIRECTORY']
# find the theme in pelican.theme if the given one does not exists
if not os.path.exists(self.theme):
@ -54,7 +55,7 @@ class Pelican(object):
self.theme,
self.output_path,
self.markup,
self.keep
self.delete_outputdir
) for cls in self.get_generator_classes()
]
@ -62,8 +63,10 @@ class Pelican(object):
if hasattr(p, 'generate_context'):
p.generate_context()
# erase the directory if it is not the source
if os.path.realpath(self.path).startswith(self.output_path) and not self.keep:
# erase the directory if it is not the source and if that's
# explicitely asked
if (self.delete_outputdir and
os.path.realpath(self.path).startswith(self.output_path)):
clean_output_dir(self.output_path)
writer = self.get_writer()
@ -100,11 +103,9 @@ def main():
help='the list of markup language to use (rst or md). Please indicate '
'them separated by commas')
parser.add_argument('-s', '--settings', dest='settings',
help='the settings of the application. Default to None.')
parser.add_argument('-k', '--keep-output-directory', dest='keep',
action='store_true',
help='Keep the output directory and just update all the generated files.'
'Default is to delete the output directory.')
help='the settings of the application. Default to False.')
parser.add_argument('-d', '--delete-output-directory', dest='delete_outputdir',
action='store_true', help='Delete the output directory.')
parser.add_argument('-v', '--verbose', action='store_const', const=log.INFO, dest='verbosity',
help='Show all messages')
parser.add_argument('-q', '--quiet', action='store_const', const=log.CRITICAL, dest='verbosity',
@ -134,12 +135,14 @@ def main():
cls = getattr(module, cls_name)
try:
pelican = cls(settings, args.path, args.theme, args.output, markup, args.keep)
pelican = cls(settings, args.path, args.theme, args.output, markup,
args.delete_outputdir)
if args.autoreload:
while True:
try:
if files_changed(pelican.path, pelican.markup):
pelican.run()
time.sleep(.5) # sleep to avoid cpu load
except KeyboardInterrupt:
break
else:

View file

@ -4,19 +4,21 @@ from pelican.log import *
class Page(object):
"""Represents a page
Given a content, and metadatas, create an adequate object.
Given a content, and metadata, create an adequate object.
:param string: the string to parse, containing the original content.
:param markup: the markup language to use while parsing.
:param content: the string to parse, containing the original content.
"""
mandatory_properties = ('title',)
def __init__(self, content, metadatas={}, settings={}, filename=None):
def __init__(self, content, metadata={}, settings={}, filename=None):
self._content = content
self.translations = []
self.status = "published" # default value
for key, value in metadatas.items():
local_metadata = dict(settings['DEFAULT_METADATA'])
local_metadata.update(metadata)
for key, value in local_metadata.items():
setattr(self, key.lower(), value)
if not hasattr(self, 'author'):
@ -90,6 +92,6 @@ def is_valid_content(content, f):
try:
content.check_properties()
return True
except NameError as e:
except NameError, e:
error(u"Skipping %s: impossible to find informations about '%s'" % (f, e))
return False

View file

@ -11,7 +11,7 @@ import random
from jinja2 import Environment, FileSystemLoader
from jinja2.exceptions import TemplateNotFound
from pelican.utils import copytree, get_relative_path, process_translations, open
from pelican.utils import copy, get_relative_path, process_translations, open
from pelican.contents import Article, Page, is_valid_content
from pelican.readers import read_file
from pelican.log import *
@ -63,7 +63,13 @@ class Generator(object):
extensions = self.markup
files = []
for root, dirs, temp_files in os.walk(path, followlinks=True):
try:
iter = os.walk(path, followlinks=True)
except TypeError: # python 2.5 does not support followlinks
iter = os.walk(path)
for root, dirs, temp_files in iter:
for e in exclude:
if e in dirs:
dirs.remove(e)
@ -183,10 +189,10 @@ class ArticlesGenerator(Generator):
files = self.get_files(self.path, exclude=['pages',])
all_articles = []
for f in files:
content, metadatas = read_file(f)
content, metadata = read_file(f)
# if no category is set, use the name of the path as a category
if 'category' not in metadatas.keys():
if 'category' not in metadata.keys():
if os.path.dirname(f) == self.path:
category = self.settings['DEFAULT_CATEGORY']
@ -194,13 +200,13 @@ class ArticlesGenerator(Generator):
category = os.path.basename(os.path.dirname(f))
if category != '':
metadatas['category'] = unicode(category)
metadata['category'] = unicode(category)
if 'date' not in metadatas.keys()\
if 'date' not in metadata.keys()\
and self.settings['FALLBACK_ON_FS_DATE']:
metadatas['date'] = datetime.fromtimestamp(os.stat(f).st_ctime)
metadata['date'] = datetime.fromtimestamp(os.stat(f).st_ctime)
article = Article(content, metadatas, settings=self.settings,
article = Article(content, metadata, settings=self.settings,
filename=f)
if not is_valid_content(article, f):
continue
@ -273,8 +279,8 @@ class PagesGenerator(Generator):
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,
content, metadata = read_file(f)
page = Page(content, metadata, settings=self.settings,
filename=f)
if not is_valid_content(page, f):
continue
@ -298,9 +304,10 @@ class StaticGenerator(Generator):
def _copy_paths(self, paths, source, destination, output_path,
final_path=None):
"""Copy all the paths from source to destination"""
for path in paths:
copytree(path, source, os.path.join(output_path, destination),
final_path)
copy(path, source, os.path.join(output_path, destination), final_path,
overwrite=True)
def generate_output(self, writer):
self._copy_paths(self.settings['STATIC_PATHS'], self.path,
@ -308,6 +315,10 @@ class StaticGenerator(Generator):
self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme,
'theme', self.output_path, '.')
# copy all the files needed
for source, destination in self.settings['FILES_TO_COPY']:
copy(source, self.path, self.output_path, destination, overwrite=True)
class PdfGenerator(Generator):
"""Generate PDFs on the output dir, for all articles and pages coming from

View file

@ -1,4 +1,6 @@
from logging import *
from logging import CRITICAL, ERROR, WARN, INFO, DEBUG
from logging import critical, error, info, warning, warn, debug
from logging import Formatter, getLogger, StreamHandler
import sys
import os

View file

@ -3,7 +3,7 @@ try:
from docutils import core
# import the directives to have pygments support
import rstdirectives
from pelican import rstdirectives
except ImportError:
core = False
try:
@ -11,15 +11,14 @@ try:
except ImportError:
Markdown = False
import re
import string
from pelican.utils import get_date, open
_METADATAS_PROCESSORS = {
'tags': lambda x: map(string.strip, x.split(',')),
_METADATA_PROCESSORS = {
'tags': lambda x: map(unicode.strip, x.split(',')),
'date': lambda x: get_date(x),
'status': string.strip,
'status': unicode.strip,
}
@ -31,11 +30,11 @@ class RstReader(Reader):
extension = "rst"
def _parse_metadata(self, content):
"""Return the dict containing metadatas"""
"""Return the dict containing metadata"""
output = {}
for m in re.compile('^:([a-z]+): (.*)\s', re.M).finditer(content):
name, value = m.group(1).lower(), m.group(2)
output[name] = _METADATAS_PROCESSORS.get(
output[name] = _METADATA_PROCESSORS.get(
name, lambda x:x
)(value)
return output
@ -43,16 +42,18 @@ class RstReader(Reader):
def read(self, filename):
"""Parse restructured text"""
text = open(filename)
metadatas = self._parse_metadata(text)
metadata = self._parse_metadata(text)
extra_params = {'input_encoding': 'unicode',
'initial_header_level': '2'}
rendered_content = core.publish_parts(text, writer_name='html',
rendered_content = core.publish_parts(text,
source_path=filename,
writer_name='html',
settings_overrides=extra_params)
title = rendered_content.get('title')
content = rendered_content.get('body')
if not metadatas.has_key('title'):
metadatas['title'] = title
return content, metadatas
if not metadata.has_key('title'):
metadata['title'] = title
return content, metadata
class MarkdownReader(Reader):
enabled = bool(Markdown)
@ -64,13 +65,13 @@ class MarkdownReader(Reader):
md = Markdown(extensions = ['meta', 'codehilite'])
content = md.convert(text)
metadatas = {}
metadata = {}
for name, value in md.Meta.items():
name = name.lower()
metadatas[name] = _METADATAS_PROCESSORS.get(
metadata[name] = _METADATA_PROCESSORS.get(
name, lambda x:x
)(value[0])
return content, metadatas
return content, metadata
class HtmlReader(Reader):
@ -80,13 +81,13 @@ class HtmlReader(Reader):
def read(self, filename):
"""Parse content and metadata of (x)HTML files"""
content = open(filename)
metadatas = {'title':'unnamed'}
metadata = {'title':'unnamed'}
for i in self._re.findall(content):
key = i.split(':')[0][5:].strip()
value = i.split(':')[-1][:-3].strip()
metadatas[key.lower()] = value
metadata[key.lower()] = value
return content, metadatas
return content, metadata

View file

@ -21,7 +21,7 @@ _DEFAULT_CONFIG = {'PATH': None,
'CSS_FILE': 'main.css',
'REVERSE_ARCHIVE_ORDER': False,
'REVERSE_CATEGORY_ORDER': False,
'KEEP_OUTPUT_DIRECTORY': False,
'DELETE_OUTPUT_DIRECTORY': False,
'CLEAN_URLS': False, # use /blah/ instead /blah.html in urls
'RELATIVE_URLS': True,
'DEFAULT_LANG': 'en',
@ -37,6 +37,8 @@ _DEFAULT_CONFIG = {'PATH': None,
'WITH_PAGINATION': False,
'DEFAULT_PAGINATION': 5,
'DEFAULT_ORPHANS': 0,
'DEFAULT_METADATA': (),
'FILES_TO_COPY': (),
}
def read_settings(filename):

View file

@ -6,7 +6,7 @@ from datetime import datetime
from codecs import open as _open
from itertools import groupby
from operator import attrgetter
from pelican.log import *
from pelican.log import warning, info
def get_date(string):
@ -42,20 +42,38 @@ def slugify(value):
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
return re.sub('[-\s]+', '-', value)
def copytree(path, origin, destination, topath=None):
"""Copy path from origin to destination, silent any errors"""
def copy(path, source, destination, destination_path=None, overwrite=False):
"""Copy path from origin to destination.
if not topath:
topath = path
try:
fromp = os.path.expanduser(os.path.join(origin, path))
to = os.path.expanduser(os.path.join(destination, topath))
shutil.copytree(fromp, to)
info('copying %s to %s' % (fromp, to))
The function is able to copy either files or directories.
except OSError:
pass
:param path: the path to be copied from the source to the destination
:param source: the source dir
:param destination: the destination dir
:param destination_path: the destination path (optional)
:param overwrite: wether to overwrite the destination if already exists or not
"""
if not destination_path:
destination_path = path
source_ = os.path.abspath(os.path.expanduser(os.path.join(source, path)))
destination_ = os.path.abspath(
os.path.expanduser(os.path.join(destination, destination_path)))
if os.path.isdir(source_):
try:
shutil.copytree(source_, destination_)
info('copying %s to %s' % (source_, destination_))
except OSError:
if overwrite:
shutil.rmtree(destination_)
shutil.copytree(source_, destination_)
info('replacement of %s with %s' % (source_, destination_))
elif os.path.isfile(source_):
shutil.copy(source_, destination_)
info('copying %s to %s' % (source_, destination_))
def clean_output_dir(path):
"""Remove all the files from the output directory"""
@ -164,9 +182,13 @@ def process_translations(content_list):
len_ = len(default_lang_items)
if len_ > 1:
warning(u'there are %s variants of "%s"' % (len_, slug))
for x in default_lang_items:
warning(' %s' % x.filename)
elif len_ == 0:
default_lang_items = items[:1]
if not slug:
warning('empty slug for %r' %( default_lang_items[0].filename,))
index.extend(default_lang_items)
translations.extend(filter(
lambda x: x not in default_lang_items,
@ -188,9 +210,10 @@ def files_changed(path, extensions):
def file_times(path):
"""Return the last time files have been modified"""
for top_level in os.listdir(path):
for root, dirs, files in os.walk(top_level):
for file in filter(with_extension, files):
for root, dirs, files in os.walk(path):
dirs[:] = [x for x in dirs if x[0] != '.']
for file in files:
if any(file.endswith(ext) for ext in extensions):
yield os.stat(os.path.join(root, file)).st_mtime
global LAST_MTIME

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement
import os
import re
from codecs import open
@ -44,9 +45,8 @@ class Writer(object):
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 elements: the articles to put on the feed.
:param context: the context to get the feed metadata.
:param output_path: where to output the file.
:param filename: the filename to output.
:param feed_type: the feed type to use (atom or rss)
"""

View file

@ -2,5 +2,6 @@ Article 1
#########
:date: 2011-02-17
:yeah: oh yeah !
Article 1

View file

@ -0,0 +1,2 @@
User-agent: *
Disallow: /static/pictures

View file

@ -24,4 +24,11 @@ SOCIAL = (('twitter', 'http://twitter.com/ametaireau'),
('lastfm', 'http://lastfm.com/user/akounet'),
('github', 'http://github.com/ametaireau'),)
# global metadata to all the contents
DEFAULT_METADATA = (('yeah', 'it is'),)
# static paths will be copied under the same name
STATIC_PATHS = ["pictures",]
# A list of files to copy from the source to the destination
FILES_TO_COPY = (('extra/robots.txt', 'robots.txt'),)

1
setup.py Normal file → Executable file
View file

@ -1,3 +1,4 @@
#!/usr/bin/env python
from setuptools import setup
import sys