mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
Some doc + various enhancements
This commit is contained in:
parent
5aeca4826c
commit
14cf5f014c
5 changed files with 146 additions and 118 deletions
|
|
@ -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<markup><\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
|
||||
|
|
|
|||
|
|
@ -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']])
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
79
pelican/urlwrappers.py
Normal file
79
pelican/urlwrappers.py
Normal 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
|
||||
|
|
@ -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'))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue