Some doc + various enhancements

This commit is contained in:
Alexis Métaireau 2013-03-10 20:11:36 -07:00
commit 14cf5f014c
5 changed files with 146 additions and 118 deletions

View file

@ -13,34 +13,36 @@ import sys
from datetime import datetime from datetime import datetime
from pelican.settings import _DEFAULT_CONFIG
from pelican.utils import (slugify, truncate_html_words, memoized,
python_2_unicode_compatible, deprecated_attribute)
from pelican import signals from pelican import signals
import pelican.utils from pelican.settings import _DEFAULT_CONFIG
from pelican.utils import (slugify, truncate_html_words, memoized, strftime,
python_2_unicode_compatible, deprecated_attribute)
# Import these so that they're avalaible when you import from pelican.contents.
from pelican.urlwrappers import (URLWrapper, Author, Category, Tag) # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Page(object): class Content(object):
"""Represents a page """Represents a content.
Given a content, and metadata, create an adequate object.
:param content: the string to parse, containing the original content. :param content: the string to parse, containing the original content.
""" :param metadata: the metadata associated to this page (optional).
mandatory_properties = ('title',) :param settings: the settings dictionary (optional).
default_template = 'page' :param source_path: The location of the source of this content (if any).
:param context: The shared context between generators.
"""
@deprecated_attribute(old='filename', new='source_path', since=(3, 2, 0)) @deprecated_attribute(old='filename', new='source_path', since=(3, 2, 0))
def filename(): def filename():
return None return None
def __init__(self, content, metadata=None, settings=None, def __init__(self, content, metadata=None, settings=None,
source_path=None, context=None): source_path=None, context=None):
# init parameters if metadata is None:
if not metadata:
metadata = {} metadata = {}
if not settings: if settings is None:
settings = copy.deepcopy(_DEFAULT_CONFIG) settings = copy.deepcopy(_DEFAULT_CONFIG)
self.settings = settings self.settings = settings
@ -68,6 +70,8 @@ class Page(object):
if 'AUTHOR' in settings: if 'AUTHOR' in settings:
self.author = Author(settings['AUTHOR'], settings) self.author = Author(settings['AUTHOR'], settings)
# XXX Split all the following code into pieces, there is too much here.
# manage languages # manage languages
self.in_default_lang = True self.in_default_lang = True
if 'DEFAULT_LANG' in settings: if 'DEFAULT_LANG' in settings:
@ -100,8 +104,7 @@ class Page(object):
self.date_format = self.date_format[1] self.date_format = self.date_format[1]
if hasattr(self, 'date'): if hasattr(self, 'date'):
self.locale_date = pelican.utils.strftime(self.date, self.locale_date = strftime(self.date, self.date_format)
self.date_format)
# manage status # manage status
if not hasattr(self, 'status'): if not hasattr(self, 'status'):
@ -117,13 +120,14 @@ class Page(object):
signals.content_object_init.send(self) signals.content_object_init.send(self)
def check_properties(self): def check_properties(self):
"""test that each mandatory property is set.""" """Test mandatory properties are set."""
for prop in self.mandatory_properties: for prop in self.mandatory_properties:
if not hasattr(self, prop): if not hasattr(self, prop):
raise NameError(prop) raise NameError(prop)
@property @property
def url_format(self): def url_format(self):
"""Returns the URL, formatted with the proper values"""
metadata = copy.copy(self.metadata) metadata = copy.copy(self.metadata)
metadata.update({ metadata.update({
'slug': getattr(self, 'slug', ''), 'slug': getattr(self, 'slug', ''),
@ -146,12 +150,14 @@ class Page(object):
return self._expand_settings(key) return self._expand_settings(key)
def _update_content(self, content, siteurl): def _update_content(self, content, siteurl):
"""Change all the relative paths of the content to relative paths """Update the content attribute.
Change all the relative paths of the content to relative paths
suitable for the ouput content. suitable for the ouput content.
:param content: content resource that will be passed to the templates. :param content: content resource that will be passed to the templates.
:param siteurl: siteurl which is locally generated by the writer in :param siteurl: siteurl which is locally generated by the writer in
case of RELATIVE_URLS. case of RELATIVE_URLS.
""" """
hrefs = re.compile(r""" hrefs = re.compile(r"""
(?P<markup><\s*[^\>]* # match tag with src and href attr (?P<markup><\s*[^\>]* # match tag with src and href attr
@ -165,9 +171,12 @@ class Page(object):
what = m.group('what') what = m.group('what')
value = m.group('value') value = m.group('value')
origin = m.group('path') origin = m.group('path')
# we support only filename for now. the plan is to support # we support only filename for now. the plan is to support
# categories, tags, etc. in the future, but let's keep things # categories, tags, etc. in the future, but let's keep things
# simple for now. # simple for now.
# XXX Put this in a different location.
if what == 'filename': if what == 'filename':
if value.startswith('/'): if value.startswith('/'):
value = value[1:] value = value[1:]
@ -191,18 +200,23 @@ class Page(object):
@memoized @memoized
def get_content(self, siteurl): def get_content(self, siteurl):
return self._update_content(
self._get_content() if hasattr(self, "_get_content") if hasattr(self, '_get_content'):
else self._content, content = self._get_content()
siteurl) else:
content = self._content
return self._update_content(content, siteurl)
@property @property
def content(self): def content(self):
return self.get_content(self._context['localsiteurl']) return self.get_content(self._context['localsiteurl'])
def _get_summary(self): def _get_summary(self):
"""Returns the summary of an article, based on the summary metadata """Returns the summary of an article.
if it is set, else truncate the content."""
This is based on the summary metadata if set, otherwise truncate the
content.
"""
if hasattr(self, '_summary'): if hasattr(self, '_summary'):
return self._summary return self._summary
@ -217,7 +231,6 @@ class Page(object):
summary = property(_get_summary, _set_summary, "Summary of the article." summary = property(_get_summary, _set_summary, "Summary of the article."
"Based on the content. Can't be set") "Based on the content. Can't be set")
url = property(functools.partial(get_url_setting, key='url')) url = property(functools.partial(get_url_setting, key='url'))
save_as = property(functools.partial(get_url_setting, key='save_as')) save_as = property(functools.partial(get_url_setting, key='save_as'))
@ -250,6 +263,11 @@ class Page(object):
) )
class Page(Content):
mandatory_properties = ('title',)
default_template = 'page'
class Article(Page): class Article(Page):
mandatory_properties = ('title', 'date', 'category') mandatory_properties = ('title', 'date', 'category')
default_template = 'article' default_template = 'article'
@ -259,78 +277,9 @@ class Quote(Page):
base_properties = ('author', 'date') base_properties = ('author', 'date')
@python_2_unicode_compatible
@functools.total_ordering
class URLWrapper(object):
def __init__(self, name, settings):
self.name = name
self.slug = slugify(self.name)
self.settings = settings
def as_dict(self):
return self.__dict__
def __hash__(self):
return hash(self.name)
def _key(self):
return self.name
def _normalize_key(self, key):
return six.text_type(key)
def __eq__(self, other):
return self._key() == self._normalize_key(other)
def __ne__(self, other):
return self._key() != self._normalize_key(other)
def __lt__(self, other):
return self._key() < self._normalize_key(other)
def __str__(self):
return self.name
def _from_settings(self, key, get_page_name=False):
"""Returns URL information as defined in settings.
When get_page_name=True returns URL without anything after {slug} e.g.
if in settings: CATEGORY_URL="cat/{slug}.html" this returns
"cat/{slug}" Useful for pagination.
"""
setting = "%s_%s" % (self.__class__.__name__.upper(), key)
value = self.settings[setting]
if not isinstance(value, six.string_types):
logger.warning('%s is set to %s' % (setting, value))
return value
else:
if get_page_name:
return os.path.splitext(value)[0].format(**self.as_dict())
else:
return value.format(**self.as_dict())
page_name = property(functools.partial(_from_settings, key='URL',
get_page_name=True))
url = property(functools.partial(_from_settings, key='URL'))
save_as = property(functools.partial(_from_settings, key='SAVE_AS'))
class Category(URLWrapper):
pass
class Tag(URLWrapper):
def __init__(self, name, *args, **kwargs):
super(Tag, self).__init__(name.strip(), *args, **kwargs)
class Author(URLWrapper):
pass
@python_2_unicode_compatible @python_2_unicode_compatible
class StaticContent(object): class StaticContent(object):
@deprecated_attribute(old='filepath', new='source_path', since=(3, 2, 0)) @deprecated_attribute(old='filepath', new='source_path', since=(3, 2, 0))
def filepath(): def filepath():
return None return None
@ -340,6 +289,7 @@ class StaticContent(object):
settings = copy.deepcopy(_DEFAULT_CONFIG) settings = copy.deepcopy(_DEFAULT_CONFIG)
self.src = src self.src = src
self.url = dst or src self.url = dst or src
# On Windows, make sure we end up with Unix-like paths. # On Windows, make sure we end up with Unix-like paths.
if os.name == 'nt': if os.name == 'nt':
self.url = self.url.replace('\\', '/') self.url = self.url.replace('\\', '/')
@ -355,6 +305,6 @@ def is_valid_content(content, f):
content.check_properties() content.check_properties()
return True return True
except NameError as e: except NameError as e:
logger.warning("Skipping %s: impossible to find informations about " logger.error("Skipping %s: impossible to find informations about "
"'%s'" % (f, e)) "'%s'" % (f, e))
return False return False

View file

@ -70,7 +70,7 @@ _DEFAULT_CONFIG = {'PATH': '.',
'DEFAULT_DATE_FORMAT': '%a %d %B %Y', 'DEFAULT_DATE_FORMAT': '%a %d %B %Y',
'DATE_FORMATS': {}, 'DATE_FORMATS': {},
'JINJA_EXTENSIONS': [], 'JINJA_EXTENSIONS': [],
'LOCALE': '', # default to user locale 'LOCALE': '', # defaults to user locale
'DEFAULT_PAGINATION': False, 'DEFAULT_PAGINATION': False,
'DEFAULT_ORPHANS': 0, 'DEFAULT_ORPHANS': 0,
'DEFAULT_METADATA': (), 'DEFAULT_METADATA': (),
@ -107,9 +107,7 @@ def read_settings(path=None, override=None):
def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG): def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG):
""" """Loads settings from a module, returns a dictionary."""
Load settings from a module, returning a dict.
"""
context = copy.deepcopy(default_settings) context = copy.deepcopy(default_settings)
if module is not None: if module is not None:
@ -119,10 +117,7 @@ def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG):
def get_settings_from_file(path, default_settings=_DEFAULT_CONFIG): def get_settings_from_file(path, default_settings=_DEFAULT_CONFIG):
""" """Loads settings from a file path, returning a dict."""
Load settings from a file path, returning a dict.
"""
name = os.path.basename(path).rpartition('.')[0] name = os.path.basename(path).rpartition('.')[0]
module = imp.load_source(name, path) module = imp.load_source(name, path)
@ -130,14 +125,15 @@ def get_settings_from_file(path, default_settings=_DEFAULT_CONFIG):
def configure_settings(settings): def configure_settings(settings):
""" """Provide optimizations, error checking and warnings for the given
Provide optimizations, error checking, and warnings for loaded settings settings.
""" """
if not 'PATH' in settings or not os.path.isdir(settings['PATH']): if not 'PATH' in settings or not os.path.isdir(settings['PATH']):
raise Exception('You need to specify a path containing the content' raise Exception('You need to specify a path containing the content'
' (see pelican --help for more information)') ' (see pelican --help for more information)')
# find the theme in pelican.theme if the given one does not exists # lookup the theme in "pelican/themes" if the given one doesn't exist
if not os.path.isdir(settings['THEME']): if not os.path.isdir(settings['THEME']):
theme_path = os.sep.join([os.path.dirname( theme_path = os.sep.join([os.path.dirname(
os.path.abspath(__file__)), "themes/%s" % settings['THEME']]) os.path.abspath(__file__)), "themes/%s" % settings['THEME']])

View file

@ -44,6 +44,7 @@ class TestPelican(LoggedTestCase):
super(TestPelican, self).setUp() super(TestPelican, self).setUp()
self.temp_path = mkdtemp() self.temp_path = mkdtemp()
self.old_locale = locale.setlocale(locale.LC_ALL) self.old_locale = locale.setlocale(locale.LC_ALL)
self.maxDiff = None
locale.setlocale(locale.LC_ALL, str('C')) locale.setlocale(locale.LC_ALL, str('C'))
def tearDown(self): def tearDown(self):

79
pelican/urlwrappers.py Normal file
View file

@ -0,0 +1,79 @@
import os
import functools
import logging
import six
from pelican.utils import (slugify, python_2_unicode_compatible)
logger = logging.getLogger(__name__)
@python_2_unicode_compatible
@functools.total_ordering
class URLWrapper(object):
def __init__(self, name, settings):
self.name = name
self.slug = slugify(self.name)
self.settings = settings
def as_dict(self):
return self.__dict__
def __hash__(self):
return hash(self.name)
def _key(self):
return self.name
def _normalize_key(self, key):
return six.text_type(key)
def __eq__(self, other):
return self._key() == self._normalize_key(other)
def __ne__(self, other):
return self._key() != self._normalize_key(other)
def __lt__(self, other):
return self._key() < self._normalize_key(other)
def __str__(self):
return self.name
def _from_settings(self, key, get_page_name=False):
"""Returns URL information as defined in settings.
When get_page_name=True returns URL without anything after {slug} e.g.
if in settings: CATEGORY_URL="cat/{slug}.html" this returns
"cat/{slug}" Useful for pagination.
"""
setting = "%s_%s" % (self.__class__.__name__.upper(), key)
value = self.settings[setting]
if not isinstance(value, six.string_types):
logger.warning('%s is set to %s' % (setting, value))
return value
else:
if get_page_name:
return os.path.splitext(value)[0].format(**self.as_dict())
else:
return value.format(**self.as_dict())
page_name = property(functools.partial(_from_settings, key='URL',
get_page_name=True))
url = property(functools.partial(_from_settings, key='URL'))
save_as = property(functools.partial(_from_settings, key='SAVE_AS'))
class Category(URLWrapper):
pass
class Tag(URLWrapper):
def __init__(self, name, *args, **kwargs):
super(Tag, self).__init__(name.strip(), *args, **kwargs)
class Author(URLWrapper):
pass

View file

@ -312,13 +312,13 @@ def get_relative_path(path):
def truncate_html_words(s, num, end_text='...'): def truncate_html_words(s, num, end_text='...'):
"""Truncates HTML to a certain number of words (not counting tags and """Truncates HTML to a certain number of words.
comments). Closes opened tags if they were correctly closed in the given
html. Takes an optional argument of what should be used to notify that the
string has been truncated, defaulting to ellipsis (...).
Newlines in the HTML are preserved. (not counting tags and comments). Closes opened tags if they were correctly
From the django framework. closed in the given html. Takes an optional argument of what should be used
to notify that the string has been truncated, defaulting to ellipsis (...).
Newlines in the HTML are preserved. (From the django framework).
""" """
length = int(num) length = int(num)
if length <= 0: if length <= 0:
@ -382,11 +382,13 @@ def truncate_html_words(s, num, end_text='...'):
def process_translations(content_list): def process_translations(content_list):
""" Finds all translation and returns tuple with two lists (index, """ Finds translation and returns them.
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' Returns a tuple with two lists (index, translations). Index list includes
items in default language or items which have no variant in default
language.
For each content_list item, sets the 'translations' attribute.
""" """
content_list.sort(key=attrgetter('slug')) content_list.sort(key=attrgetter('slug'))
grouped_by_slugs = groupby(content_list, attrgetter('slug')) grouped_by_slugs = groupby(content_list, attrgetter('slug'))