From 14cf5f014ca3576052524e43f0a3fdeff8283741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Sun, 10 Mar 2013 20:11:36 -0700 Subject: [PATCH] Some doc + various enhancements --- pelican/contents.py | 144 +++++++++++----------------------- pelican/settings.py | 18 ++--- pelican/tests/test_pelican.py | 1 + pelican/urlwrappers.py | 79 +++++++++++++++++++ pelican/utils.py | 22 +++--- 5 files changed, 146 insertions(+), 118 deletions(-) create mode 100644 pelican/urlwrappers.py diff --git a/pelican/contents.py b/pelican/contents.py index bcff3cd5..b242b5d9 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -13,34 +13,36 @@ import sys 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 -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__) -class Page(object): - """Represents a page - Given a content, and metadata, create an adequate object. +class Content(object): + """Represents a content. :param content: the string to parse, containing the original content. - """ - mandatory_properties = ('title',) - default_template = 'page' + :param metadata: the metadata associated to this page (optional). + :param settings: the settings dictionary (optional). + :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)) def filename(): return None def __init__(self, content, metadata=None, settings=None, source_path=None, context=None): - # init parameters - if not metadata: + if metadata is None: metadata = {} - if not settings: + if settings is None: settings = copy.deepcopy(_DEFAULT_CONFIG) self.settings = settings @@ -68,6 +70,8 @@ class Page(object): if 'AUTHOR' in settings: self.author = Author(settings['AUTHOR'], settings) + # XXX Split all the following code into pieces, there is too much here. + # manage languages self.in_default_lang = True if 'DEFAULT_LANG' in settings: @@ -100,8 +104,7 @@ class Page(object): self.date_format = self.date_format[1] if hasattr(self, 'date'): - self.locale_date = pelican.utils.strftime(self.date, - self.date_format) + self.locale_date = strftime(self.date, self.date_format) # manage status if not hasattr(self, 'status'): @@ -117,13 +120,14 @@ class Page(object): signals.content_object_init.send(self) def check_properties(self): - """test that each mandatory property is set.""" + """Test mandatory properties are set.""" for prop in self.mandatory_properties: if not hasattr(self, prop): raise NameError(prop) @property def url_format(self): + """Returns the URL, formatted with the proper values""" metadata = copy.copy(self.metadata) metadata.update({ 'slug': getattr(self, 'slug', ''), @@ -146,12 +150,14 @@ class Page(object): return self._expand_settings(key) 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. :param content: content resource that will be passed to the templates. :param siteurl: siteurl which is locally generated by the writer in - case of RELATIVE_URLS. + case of RELATIVE_URLS. """ hrefs = re.compile(r""" (?P<\s*[^\>]* # match tag with src and href attr @@ -165,9 +171,12 @@ class Page(object): what = m.group('what') value = m.group('value') origin = m.group('path') + # we support only filename for now. the plan is to support # categories, tags, etc. in the future, but let's keep things # simple for now. + + # XXX Put this in a different location. if what == 'filename': if value.startswith('/'): value = value[1:] @@ -191,18 +200,23 @@ class Page(object): @memoized def get_content(self, siteurl): - return self._update_content( - self._get_content() if hasattr(self, "_get_content") - else self._content, - siteurl) + + if hasattr(self, '_get_content'): + content = self._get_content() + else: + content = self._content + return self._update_content(content, siteurl) @property def content(self): return self.get_content(self._context['localsiteurl']) def _get_summary(self): - """Returns the summary of an article, based on the summary metadata - if it is set, else truncate the content.""" + """Returns the summary of an article. + + This is based on the summary metadata if set, otherwise truncate the + content. + """ if hasattr(self, '_summary'): return self._summary @@ -217,7 +231,6 @@ class Page(object): summary = property(_get_summary, _set_summary, "Summary of the article." "Based on the content. Can't be set") - url = property(functools.partial(get_url_setting, key='url')) 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): mandatory_properties = ('title', 'date', 'category') default_template = 'article' @@ -259,78 +277,9 @@ class Quote(Page): 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 class StaticContent(object): + @deprecated_attribute(old='filepath', new='source_path', since=(3, 2, 0)) def filepath(): return None @@ -340,6 +289,7 @@ class StaticContent(object): settings = copy.deepcopy(_DEFAULT_CONFIG) self.src = src self.url = dst or src + # On Windows, make sure we end up with Unix-like paths. if os.name == 'nt': self.url = self.url.replace('\\', '/') @@ -355,6 +305,6 @@ def is_valid_content(content, f): content.check_properties() return True except NameError as e: - logger.warning("Skipping %s: impossible to find informations about " - "'%s'" % (f, e)) + logger.error("Skipping %s: impossible to find informations about " + "'%s'" % (f, e)) return False diff --git a/pelican/settings.py b/pelican/settings.py index 1180d0b2..914f963b 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -70,7 +70,7 @@ _DEFAULT_CONFIG = {'PATH': '.', 'DEFAULT_DATE_FORMAT': '%a %d %B %Y', 'DATE_FORMATS': {}, 'JINJA_EXTENSIONS': [], - 'LOCALE': '', # default to user locale + 'LOCALE': '', # defaults to user locale 'DEFAULT_PAGINATION': False, 'DEFAULT_ORPHANS': 0, 'DEFAULT_METADATA': (), @@ -107,9 +107,7 @@ def read_settings(path=None, override=None): def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG): - """ - Load settings from a module, returning a dict. - """ + """Loads settings from a module, returns a dictionary.""" context = copy.deepcopy(default_settings) 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): - """ - Load settings from a file path, returning a dict. - - """ + """Loads settings from a file path, returning a dict.""" name = os.path.basename(path).rpartition('.')[0] module = imp.load_source(name, path) @@ -130,14 +125,15 @@ def get_settings_from_file(path, default_settings=_DEFAULT_CONFIG): def configure_settings(settings): - """ - Provide optimizations, error checking, and warnings for loaded settings + """Provide optimizations, error checking and warnings for the given + settings. + """ if not 'PATH' in settings or not os.path.isdir(settings['PATH']): raise Exception('You need to specify a path containing the content' ' (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']): theme_path = os.sep.join([os.path.dirname( os.path.abspath(__file__)), "themes/%s" % settings['THEME']]) diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 97bab11f..04fd7d2b 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -44,6 +44,7 @@ class TestPelican(LoggedTestCase): super(TestPelican, self).setUp() self.temp_path = mkdtemp() self.old_locale = locale.setlocale(locale.LC_ALL) + self.maxDiff = None locale.setlocale(locale.LC_ALL, str('C')) def tearDown(self): diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py new file mode 100644 index 00000000..943077b0 --- /dev/null +++ b/pelican/urlwrappers.py @@ -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 diff --git a/pelican/utils.py b/pelican/utils.py index 8f9afcc0..2278984d 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -312,13 +312,13 @@ def get_relative_path(path): def truncate_html_words(s, num, end_text='...'): - """Truncates HTML to a certain number of words (not counting tags and - 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 (...). + """Truncates HTML to a certain number of words. - Newlines in the HTML are preserved. - From the django framework. + (not counting tags and 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. (From the django framework). """ length = int(num) if length <= 0: @@ -382,11 +382,13 @@ def truncate_html_words(s, num, end_text='...'): 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. + """ Finds translation and returns them. - 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')) grouped_by_slugs = groupby(content_list, attrgetter('slug'))