mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
Merge 9fc9c6ea5a into 39f74280b3
This commit is contained in:
commit
bc2d7fb675
26 changed files with 415 additions and 317 deletions
|
|
@ -191,22 +191,22 @@ class Pelican(object):
|
||||||
|
|
||||||
pluralized_articles = maybe_pluralize(
|
pluralized_articles = maybe_pluralize(
|
||||||
len(articles_generator.articles) +
|
len(articles_generator.articles) +
|
||||||
len(articles_generator.translations),
|
len(articles_generator.translations),
|
||||||
'article',
|
'article',
|
||||||
'articles')
|
'articles')
|
||||||
pluralized_drafts = maybe_pluralize(
|
pluralized_drafts = maybe_pluralize(
|
||||||
len(articles_generator.drafts) +
|
len(articles_generator.drafts) +
|
||||||
len(articles_generator.drafts_translations),
|
len(articles_generator.drafts_translations),
|
||||||
'draft',
|
'draft',
|
||||||
'drafts')
|
'drafts')
|
||||||
pluralized_pages = maybe_pluralize(
|
pluralized_pages = maybe_pluralize(
|
||||||
len(pages_generator.pages) +
|
len(pages_generator.pages) +
|
||||||
len(pages_generator.translations),
|
len(pages_generator.translations),
|
||||||
'page',
|
'page',
|
||||||
'pages')
|
'pages')
|
||||||
pluralized_hidden_pages = maybe_pluralize(
|
pluralized_hidden_pages = maybe_pluralize(
|
||||||
len(pages_generator.hidden_pages) +
|
len(pages_generator.hidden_pages) +
|
||||||
len(pages_generator.hidden_translations),
|
len(pages_generator.hidden_translations),
|
||||||
'hidden page',
|
'hidden page',
|
||||||
'hidden pages')
|
'hidden pages')
|
||||||
|
|
||||||
|
|
@ -243,8 +243,8 @@ class Pelican(object):
|
||||||
return generators
|
return generators
|
||||||
|
|
||||||
def get_writer(self):
|
def get_writer(self):
|
||||||
writers = [ w for (_, w) in signals.get_writer.send(self)
|
writers = [w for (_, w) in signals.get_writer.send(self)
|
||||||
if isinstance(w, type) ]
|
if isinstance(w, type)]
|
||||||
writers_found = len(writers)
|
writers_found = len(writers)
|
||||||
if writers_found == 0:
|
if writers_found == 0:
|
||||||
return Writer(self.output_path, settings=self.settings)
|
return Writer(self.output_path, settings=self.settings)
|
||||||
|
|
@ -354,7 +354,8 @@ def get_config(args):
|
||||||
# argparse returns bytes in Py2. There is no definite answer as to which
|
# argparse returns bytes in Py2. There is no definite answer as to which
|
||||||
# encoding argparse (or sys.argv) uses.
|
# encoding argparse (or sys.argv) uses.
|
||||||
# "Best" option seems to be locale.getpreferredencoding()
|
# "Best" option seems to be locale.getpreferredencoding()
|
||||||
# ref: http://mail.python.org/pipermail/python-list/2006-October/405766.html
|
# ref:
|
||||||
|
# http://mail.python.org/pipermail/python-list/2006-October/405766.html
|
||||||
if not six.PY3:
|
if not six.PY3:
|
||||||
enc = locale.getpreferredencoding()
|
enc = locale.getpreferredencoding()
|
||||||
for key in config:
|
for key in config:
|
||||||
|
|
@ -367,7 +368,7 @@ def get_instance(args):
|
||||||
|
|
||||||
config_file = args.settings
|
config_file = args.settings
|
||||||
if config_file is None and os.path.isfile(DEFAULT_CONFIG_NAME):
|
if config_file is None and os.path.isfile(DEFAULT_CONFIG_NAME):
|
||||||
config_file = DEFAULT_CONFIG_NAME
|
config_file = DEFAULT_CONFIG_NAME
|
||||||
|
|
||||||
settings = read_settings(config_file, override=get_config(args))
|
settings = read_settings(config_file, override=get_config(args))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FileDataCacher(object):
|
class FileDataCacher(object):
|
||||||
|
|
||||||
"""Class that can cache data contained in files"""
|
"""Class that can cache data contained in files"""
|
||||||
|
|
||||||
def __init__(self, settings, cache_name, caching_policy, load_policy):
|
def __init__(self, settings, cache_name, caching_policy, load_policy):
|
||||||
|
|
@ -77,6 +78,7 @@ class FileDataCacher(object):
|
||||||
|
|
||||||
|
|
||||||
class FileStampDataCacher(FileDataCacher):
|
class FileStampDataCacher(FileDataCacher):
|
||||||
|
|
||||||
"""Subclass that also caches the stamp of the file"""
|
"""Subclass that also caches the stamp of the file"""
|
||||||
|
|
||||||
def __init__(self, settings, cache_name, caching_policy, load_policy):
|
def __init__(self, settings, cache_name, caching_policy, load_policy):
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Content(object):
|
class Content(object):
|
||||||
|
|
||||||
"""Represents a content.
|
"""Represents a content.
|
||||||
|
|
||||||
:param content: the string to parse, containing the original content.
|
:param content: the string to parse, containing the original content.
|
||||||
|
|
@ -66,7 +67,7 @@ class Content(object):
|
||||||
# also keep track of the metadata attributes available
|
# also keep track of the metadata attributes available
|
||||||
self.metadata = local_metadata
|
self.metadata = local_metadata
|
||||||
|
|
||||||
#default template if it's not defined in page
|
# default template if it's not defined in page
|
||||||
self.template = self._get_template()
|
self.template = self._get_template()
|
||||||
|
|
||||||
# First, read the authors from "authors", if not, fallback to "author"
|
# First, read the authors from "authors", if not, fallback to "author"
|
||||||
|
|
@ -96,11 +97,11 @@ class Content(object):
|
||||||
if not hasattr(self, 'slug'):
|
if not hasattr(self, 'slug'):
|
||||||
if settings['SLUGIFY_SOURCE'] == 'title' and hasattr(self, 'title'):
|
if settings['SLUGIFY_SOURCE'] == 'title' and hasattr(self, 'title'):
|
||||||
self.slug = slugify(self.title,
|
self.slug = slugify(self.title,
|
||||||
settings.get('SLUG_SUBSTITUTIONS', ()))
|
settings.get('SLUG_SUBSTITUTIONS', ()))
|
||||||
elif settings['SLUGIFY_SOURCE'] == 'basename' and source_path != None:
|
elif settings['SLUGIFY_SOURCE'] == 'basename' and source_path != None:
|
||||||
basename = os.path.basename(os.path.splitext(source_path)[0])
|
basename = os.path.basename(os.path.splitext(source_path)[0])
|
||||||
self.slug = slugify(basename,
|
self.slug = slugify(basename,
|
||||||
settings.get('SLUG_SUBSTITUTIONS', ()))
|
settings.get('SLUG_SUBSTITUTIONS', ()))
|
||||||
|
|
||||||
self.source_path = source_path
|
self.source_path = source_path
|
||||||
|
|
||||||
|
|
@ -234,14 +235,14 @@ class Content(object):
|
||||||
linked_content.attach_to(self)
|
linked_content.attach_to(self)
|
||||||
else:
|
else:
|
||||||
logger.warning("%s used {attach} link syntax on a "
|
logger.warning("%s used {attach} link syntax on a "
|
||||||
"non-static file. Use {filename} instead.",
|
"non-static file. Use {filename} instead.",
|
||||||
self.get_relative_source_path())
|
self.get_relative_source_path())
|
||||||
origin = '/'.join((siteurl, linked_content.url))
|
origin = '/'.join((siteurl, linked_content.url))
|
||||||
origin = origin.replace('\\', '/') # for Windows paths.
|
origin = origin.replace('\\', '/') # for Windows paths.
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Unable to find `%s`, skipping url replacement.",
|
"Unable to find `%s`, skipping url replacement.",
|
||||||
value.geturl(), extra = {
|
value.geturl(), extra={
|
||||||
'limit_msg': ("Other resources were not found "
|
'limit_msg': ("Other resources were not found "
|
||||||
"and their urls not replaced")})
|
"and their urls not replaced")})
|
||||||
elif what == 'category':
|
elif what == 'category':
|
||||||
|
|
@ -337,7 +338,8 @@ class Content(object):
|
||||||
|
|
||||||
return posixize_path(
|
return posixize_path(
|
||||||
os.path.relpath(
|
os.path.relpath(
|
||||||
os.path.abspath(os.path.join(self.settings['PATH'], source_path)),
|
os.path.abspath(
|
||||||
|
os.path.join(self.settings['PATH'], source_path)),
|
||||||
os.path.abspath(self.settings['PATH'])
|
os.path.abspath(self.settings['PATH'])
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -371,6 +373,7 @@ class Quote(Page):
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Static(Page):
|
class Static(Page):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(Static, self).__init__(*args, **kwargs)
|
super(Static, self).__init__(*args, **kwargs)
|
||||||
self._output_location_referenced = False
|
self._output_location_referenced = False
|
||||||
|
|
@ -424,10 +427,10 @@ class Static(Page):
|
||||||
|
|
||||||
def _log_reason(reason):
|
def _log_reason(reason):
|
||||||
logger.warning("The {attach} link in %s cannot relocate %s "
|
logger.warning("The {attach} link in %s cannot relocate %s "
|
||||||
"because %s. Falling back to {filename} link behavior instead.",
|
"because %s. Falling back to {filename} link behavior instead.",
|
||||||
content.get_relative_source_path(),
|
content.get_relative_source_path(),
|
||||||
self.get_relative_source_path(), reason,
|
self.get_relative_source_path(), reason,
|
||||||
extra={'limit_msg': "More {attach} warnings silenced."})
|
extra={'limit_msg': "More {attach} warnings silenced."})
|
||||||
|
|
||||||
# We never override an override, because we don't want to interfere
|
# We never override an override, because we don't want to interfere
|
||||||
# with user-defined overrides that might be in EXTRA_PATH_METADATA.
|
# with user-defined overrides that might be in EXTRA_PATH_METADATA.
|
||||||
|
|
@ -452,5 +455,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.error("Skipping %s: could not find information about '%s'", f, e)
|
logger.error(
|
||||||
|
"Skipping %s: could not find information about '%s'", f, e)
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,10 @@ logger = logging.getLogger(__name__)
|
||||||
class PelicanTemplateNotFound(Exception):
|
class PelicanTemplateNotFound(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Generator(object):
|
class Generator(object):
|
||||||
|
|
||||||
"""Baseclass generator"""
|
"""Baseclass generator"""
|
||||||
|
|
||||||
def __init__(self, context, settings, path, theme, output_path,
|
def __init__(self, context, settings, path, theme, output_path,
|
||||||
|
|
@ -58,7 +60,7 @@ class Generator(object):
|
||||||
theme_path = os.path.dirname(os.path.abspath(__file__))
|
theme_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
simple_loader = FileSystemLoader(os.path.join(theme_path,
|
simple_loader = FileSystemLoader(os.path.join(theme_path,
|
||||||
"themes", "simple", "templates"))
|
"themes", "simple", "templates"))
|
||||||
self.env = Environment(
|
self.env = Environment(
|
||||||
trim_blocks=True,
|
trim_blocks=True,
|
||||||
lstrip_blocks=True,
|
lstrip_blocks=True,
|
||||||
|
|
@ -91,7 +93,7 @@ class Generator(object):
|
||||||
self._templates[name] = self.env.get_template(name + '.html')
|
self._templates[name] = self.env.get_template(name + '.html')
|
||||||
except TemplateNotFound:
|
except TemplateNotFound:
|
||||||
raise PelicanTemplateNotFound('[templates] unable to load %s.html from %s'
|
raise PelicanTemplateNotFound('[templates] unable to load %s.html from %s'
|
||||||
% (name, self._templates_path))
|
% (name, self._templates_path))
|
||||||
return self._templates[name]
|
return self._templates[name]
|
||||||
|
|
||||||
def _include_path(self, path, extensions=None):
|
def _include_path(self, path, extensions=None):
|
||||||
|
|
@ -105,7 +107,7 @@ class Generator(object):
|
||||||
extensions = tuple(self.readers.extensions)
|
extensions = tuple(self.readers.extensions)
|
||||||
basename = os.path.basename(path)
|
basename = os.path.basename(path)
|
||||||
|
|
||||||
#check IGNORE_FILES
|
# check IGNORE_FILES
|
||||||
ignores = self.settings['IGNORE_FILES']
|
ignores = self.settings['IGNORE_FILES']
|
||||||
if any(fnmatch.fnmatch(basename, ignore) for ignore in ignores):
|
if any(fnmatch.fnmatch(basename, ignore) for ignore in ignores):
|
||||||
return False
|
return False
|
||||||
|
|
@ -123,7 +125,7 @@ class Generator(object):
|
||||||
extensions are allowed)
|
extensions are allowed)
|
||||||
"""
|
"""
|
||||||
if isinstance(paths, six.string_types):
|
if isinstance(paths, six.string_types):
|
||||||
paths = [paths] # backward compatibility for older generators
|
paths = [paths] # backward compatibility for older generators
|
||||||
|
|
||||||
# group the exclude dir names by parent path, for use with os.walk()
|
# group the exclude dir names by parent path, for use with os.walk()
|
||||||
exclusions_by_dirpath = {}
|
exclusions_by_dirpath = {}
|
||||||
|
|
@ -196,6 +198,7 @@ class Generator(object):
|
||||||
|
|
||||||
|
|
||||||
class CachingGenerator(Generator, FileStampDataCacher):
|
class CachingGenerator(Generator, FileStampDataCacher):
|
||||||
|
|
||||||
'''Subclass of Generator and FileStampDataCacher classes
|
'''Subclass of Generator and FileStampDataCacher classes
|
||||||
|
|
||||||
enables content caching, either at the generator or reader level
|
enables content caching, either at the generator or reader level
|
||||||
|
|
@ -211,7 +214,8 @@ class CachingGenerator(Generator, FileStampDataCacher):
|
||||||
readers_cache_name=(cls_name + '-Readers'),
|
readers_cache_name=(cls_name + '-Readers'),
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
cache_this_level = self.settings['CONTENT_CACHING_LAYER'] == 'generator'
|
cache_this_level = self.settings[
|
||||||
|
'CONTENT_CACHING_LAYER'] == 'generator'
|
||||||
caching_policy = cache_this_level and self.settings['CACHE_CONTENT']
|
caching_policy = cache_this_level and self.settings['CACHE_CONTENT']
|
||||||
load_policy = cache_this_level and self.settings['LOAD_CONTENT_CACHE']
|
load_policy = cache_this_level and self.settings['LOAD_CONTENT_CACHE']
|
||||||
FileStampDataCacher.__init__(self, self.settings, cls_name,
|
FileStampDataCacher.__init__(self, self.settings, cls_name,
|
||||||
|
|
@ -255,6 +259,7 @@ class TemplatePagesGenerator(Generator):
|
||||||
|
|
||||||
|
|
||||||
class ArticlesGenerator(CachingGenerator):
|
class ArticlesGenerator(CachingGenerator):
|
||||||
|
|
||||||
"""Generate blog articles"""
|
"""Generate blog articles"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
@ -266,7 +271,7 @@ class ArticlesGenerator(CachingGenerator):
|
||||||
self.categories = defaultdict(list)
|
self.categories = defaultdict(list)
|
||||||
self.related_posts = []
|
self.related_posts = []
|
||||||
self.authors = defaultdict(list)
|
self.authors = defaultdict(list)
|
||||||
self.drafts = [] # only drafts in default language
|
self.drafts = [] # only drafts in default language
|
||||||
self.drafts_translations = []
|
self.drafts_translations = []
|
||||||
super(ArticlesGenerator, self).__init__(*args, **kwargs)
|
super(ArticlesGenerator, self).__init__(*args, **kwargs)
|
||||||
signals.article_generator_init.send(self)
|
signals.article_generator_init.send(self)
|
||||||
|
|
@ -472,9 +477,9 @@ class ArticlesGenerator(CachingGenerator):
|
||||||
"""Generate drafts pages."""
|
"""Generate drafts pages."""
|
||||||
for draft in chain(self.drafts_translations, self.drafts):
|
for draft in chain(self.drafts_translations, self.drafts):
|
||||||
write(draft.save_as, self.get_template(draft.template),
|
write(draft.save_as, self.get_template(draft.template),
|
||||||
self.context, article=draft, category=draft.category,
|
self.context, article=draft, category=draft.category,
|
||||||
override_output=hasattr(draft, 'override_save_as'),
|
override_output=hasattr(draft, 'override_save_as'),
|
||||||
blog=True, all_articles=self.articles)
|
blog=True, all_articles=self.articles)
|
||||||
|
|
||||||
def generate_pages(self, writer):
|
def generate_pages(self, writer):
|
||||||
"""Generate the pages on the disk"""
|
"""Generate the pages on the disk"""
|
||||||
|
|
@ -503,7 +508,8 @@ class ArticlesGenerator(CachingGenerator):
|
||||||
exclude=self.settings['ARTICLE_EXCLUDES']):
|
exclude=self.settings['ARTICLE_EXCLUDES']):
|
||||||
article_or_draft = self.get_cached_data(f, None)
|
article_or_draft = self.get_cached_data(f, None)
|
||||||
if article_or_draft is None:
|
if article_or_draft is None:
|
||||||
#TODO needs overhaul, maybe nomad for read_file solution, unified behaviour
|
# TODO needs overhaul, maybe nomad for read_file solution,
|
||||||
|
# unified behaviour
|
||||||
try:
|
try:
|
||||||
article_or_draft = self.readers.read_file(
|
article_or_draft = self.readers.read_file(
|
||||||
base_path=self.path, path=f, content_class=Article,
|
base_path=self.path, path=f, content_class=Article,
|
||||||
|
|
@ -514,7 +520,7 @@ class ArticlesGenerator(CachingGenerator):
|
||||||
context_sender=self)
|
context_sender=self)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Could not process %s\n%s', f, e,
|
logger.error('Could not process %s\n%s', f, e,
|
||||||
exc_info=self.settings.get('DEBUG', False))
|
exc_info=self.settings.get('DEBUG', False))
|
||||||
self._add_failed_source_path(f)
|
self._add_failed_source_path(f)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -536,7 +542,7 @@ class ArticlesGenerator(CachingGenerator):
|
||||||
all_drafts.append(article_or_draft)
|
all_drafts.append(article_or_draft)
|
||||||
else:
|
else:
|
||||||
logger.error("Unknown status '%s' for file %s, skipping it.",
|
logger.error("Unknown status '%s' for file %s, skipping it.",
|
||||||
article_or_draft.status, f)
|
article_or_draft.status, f)
|
||||||
self._add_failed_source_path(f)
|
self._add_failed_source_path(f)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -544,9 +550,8 @@ class ArticlesGenerator(CachingGenerator):
|
||||||
|
|
||||||
self.add_source_path(article_or_draft)
|
self.add_source_path(article_or_draft)
|
||||||
|
|
||||||
|
|
||||||
self.articles, self.translations = process_translations(all_articles,
|
self.articles, self.translations = process_translations(all_articles,
|
||||||
order_by=self.settings['ARTICLE_ORDER_BY'])
|
order_by=self.settings['ARTICLE_ORDER_BY'])
|
||||||
self.drafts, self.drafts_translations = \
|
self.drafts, self.drafts_translations = \
|
||||||
process_translations(all_drafts)
|
process_translations(all_drafts)
|
||||||
|
|
||||||
|
|
@ -589,6 +594,7 @@ class ArticlesGenerator(CachingGenerator):
|
||||||
|
|
||||||
|
|
||||||
class PagesGenerator(CachingGenerator):
|
class PagesGenerator(CachingGenerator):
|
||||||
|
|
||||||
"""Generate pages"""
|
"""Generate pages"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
@ -616,7 +622,7 @@ class PagesGenerator(CachingGenerator):
|
||||||
context_sender=self)
|
context_sender=self)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Could not process %s\n%s', f, e,
|
logger.error('Could not process %s\n%s', f, e,
|
||||||
exc_info=self.settings.get('DEBUG', False))
|
exc_info=self.settings.get('DEBUG', False))
|
||||||
self._add_failed_source_path(f)
|
self._add_failed_source_path(f)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -630,7 +636,7 @@ class PagesGenerator(CachingGenerator):
|
||||||
hidden_pages.append(page)
|
hidden_pages.append(page)
|
||||||
else:
|
else:
|
||||||
logger.error("Unknown status '%s' for file %s, skipping it.",
|
logger.error("Unknown status '%s' for file %s, skipping it.",
|
||||||
page.status, f)
|
page.status, f)
|
||||||
self._add_failed_source_path(f)
|
self._add_failed_source_path(f)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -639,7 +645,7 @@ class PagesGenerator(CachingGenerator):
|
||||||
self.add_source_path(page)
|
self.add_source_path(page)
|
||||||
|
|
||||||
self.pages, self.translations = process_translations(all_pages,
|
self.pages, self.translations = process_translations(all_pages,
|
||||||
order_by=self.settings['PAGE_ORDER_BY'])
|
order_by=self.settings['PAGE_ORDER_BY'])
|
||||||
self.hidden_pages, self.hidden_translations = (
|
self.hidden_pages, self.hidden_translations = (
|
||||||
process_translations(hidden_pages))
|
process_translations(hidden_pages))
|
||||||
|
|
||||||
|
|
@ -661,6 +667,7 @@ class PagesGenerator(CachingGenerator):
|
||||||
|
|
||||||
|
|
||||||
class StaticGenerator(Generator):
|
class StaticGenerator(Generator):
|
||||||
|
|
||||||
"""copy static paths (what you want to copy, like images, medias etc.
|
"""copy static paths (what you want to copy, like images, medias etc.
|
||||||
to output"""
|
to output"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,16 @@ from collections import defaultdict, Mapping
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
|
||||||
class BaseFormatter(logging.Formatter):
|
class BaseFormatter(logging.Formatter):
|
||||||
|
|
||||||
def __init__(self, fmt=None, datefmt=None):
|
def __init__(self, fmt=None, datefmt=None):
|
||||||
FORMAT = '%(customlevelname)s %(message)s'
|
FORMAT = '%(customlevelname)s %(message)s'
|
||||||
super(BaseFormatter, self).__init__(fmt=FORMAT, datefmt=datefmt)
|
super(BaseFormatter, self).__init__(fmt=FORMAT, datefmt=datefmt)
|
||||||
|
|
||||||
def format(self, record):
|
def format(self, record):
|
||||||
record.__dict__['customlevelname'] = self._get_levelname(record.levelname)
|
record.__dict__['customlevelname'] = self._get_levelname(
|
||||||
|
record.levelname)
|
||||||
# format multiline messages 'nicely' to make it clear they are together
|
# format multiline messages 'nicely' to make it clear they are together
|
||||||
record.msg = record.msg.replace('\n', '\n | ')
|
record.msg = record.msg.replace('\n', '\n | ')
|
||||||
return super(BaseFormatter, self).format(record)
|
return super(BaseFormatter, self).format(record)
|
||||||
|
|
@ -69,6 +72,7 @@ class ANSIFormatter(BaseFormatter):
|
||||||
|
|
||||||
|
|
||||||
class TextFormatter(BaseFormatter):
|
class TextFormatter(BaseFormatter):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Convert a `logging.LogRecord' object into text.
|
Convert a `logging.LogRecord' object into text.
|
||||||
"""
|
"""
|
||||||
|
|
@ -81,6 +85,7 @@ class TextFormatter(BaseFormatter):
|
||||||
|
|
||||||
|
|
||||||
class LimitFilter(logging.Filter):
|
class LimitFilter(logging.Filter):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Remove duplicates records, and limit the number of records in the same
|
Remove duplicates records, and limit the number of records in the same
|
||||||
group.
|
group.
|
||||||
|
|
@ -124,6 +129,7 @@ class LimitFilter(logging.Filter):
|
||||||
|
|
||||||
|
|
||||||
class SafeLogger(logging.Logger):
|
class SafeLogger(logging.Logger):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Base Logger which properly encodes Exceptions in Py2
|
Base Logger which properly encodes Exceptions in Py2
|
||||||
"""
|
"""
|
||||||
|
|
@ -132,13 +138,13 @@ class SafeLogger(logging.Logger):
|
||||||
def _log(self, level, msg, args, exc_info=None, extra=None):
|
def _log(self, level, msg, args, exc_info=None, extra=None):
|
||||||
# if the only argument is a Mapping, Logger uses that for formatting
|
# if the only argument is a Mapping, Logger uses that for formatting
|
||||||
# format values for that case
|
# format values for that case
|
||||||
if args and len(args)==1 and isinstance(args[0], Mapping):
|
if args and len(args) == 1 and isinstance(args[0], Mapping):
|
||||||
args = ({k: self._decode_arg(v) for k, v in args[0].items()},)
|
args = ({k: self._decode_arg(v) for k, v in args[0].items()},)
|
||||||
# otherwise, format each arg
|
# otherwise, format each arg
|
||||||
else:
|
else:
|
||||||
args = tuple(self._decode_arg(arg) for arg in args)
|
args = tuple(self._decode_arg(arg) for arg in args)
|
||||||
super(SafeLogger, self)._log(level, msg, args,
|
super(SafeLogger, self)._log(level, msg, args,
|
||||||
exc_info=exc_info, extra=extra)
|
exc_info=exc_info, extra=extra)
|
||||||
|
|
||||||
def _decode_arg(self, arg):
|
def _decode_arg(self, arg):
|
||||||
'''
|
'''
|
||||||
|
|
@ -158,6 +164,7 @@ class SafeLogger(logging.Logger):
|
||||||
|
|
||||||
|
|
||||||
class LimitLogger(SafeLogger):
|
class LimitLogger(SafeLogger):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A logger which adds LimitFilter automatically
|
A logger which adds LimitFilter automatically
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ PaginationRule = namedtuple(
|
||||||
|
|
||||||
|
|
||||||
class Paginator(object):
|
class Paginator(object):
|
||||||
|
|
||||||
def __init__(self, name, object_list, settings):
|
def __init__(self, name, object_list, settings):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.object_list = object_list
|
self.object_list = object_list
|
||||||
|
|
@ -68,6 +69,7 @@ class Paginator(object):
|
||||||
|
|
||||||
|
|
||||||
class Page(object):
|
class Page(object):
|
||||||
|
|
||||||
def __init__(self, name, object_list, number, paginator, settings):
|
def __init__(self, name, object_list, number, paginator, settings):
|
||||||
self.name, self.extension = os.path.splitext(name)
|
self.name, self.extension = os.path.splitext(name)
|
||||||
self.object_list = object_list
|
self.object_list = object_list
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ from pelican.cache import FileStampDataCacher
|
||||||
from pelican.contents import Page, Category, Tag, Author
|
from pelican.contents import Page, Category, Tag, Author
|
||||||
from pelican.utils import get_date, pelican_open, SafeDatetime, posixize_path
|
from pelican.utils import get_date, pelican_open, SafeDatetime, posixize_path
|
||||||
|
|
||||||
|
|
||||||
def ensure_metadata_list(text):
|
def ensure_metadata_list(text):
|
||||||
"""Canonicalize the format of a list of authors or tags. This works
|
"""Canonicalize the format of a list of authors or tags. This works
|
||||||
the same way as Docutils' "authors" field: if it's already a list,
|
the same way as Docutils' "authors" field: if it's already a list,
|
||||||
|
|
@ -86,7 +87,9 @@ def _filter_discardable_metadata(metadata):
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BaseReader(object):
|
class BaseReader(object):
|
||||||
|
|
||||||
"""Base class to read files.
|
"""Base class to read files.
|
||||||
|
|
||||||
This class is used to process static files, and it can be inherited for
|
This class is used to process static files, and it can be inherited for
|
||||||
|
|
@ -159,12 +162,14 @@ class PelicanHTMLTranslator(HTMLTranslator):
|
||||||
|
|
||||||
|
|
||||||
class RstReader(BaseReader):
|
class RstReader(BaseReader):
|
||||||
|
|
||||||
"""Reader for reStructuredText files"""
|
"""Reader for reStructuredText files"""
|
||||||
|
|
||||||
enabled = bool(docutils)
|
enabled = bool(docutils)
|
||||||
file_extensions = ['rst']
|
file_extensions = ['rst']
|
||||||
|
|
||||||
class FileInput(docutils.io.FileInput):
|
class FileInput(docutils.io.FileInput):
|
||||||
|
|
||||||
"""Patch docutils.io.FileInput to remove "U" mode in py3.
|
"""Patch docutils.io.FileInput to remove "U" mode in py3.
|
||||||
|
|
||||||
Universal newlines is enabled by default and "U" mode is deprecated
|
Universal newlines is enabled by default and "U" mode is deprecated
|
||||||
|
|
@ -238,6 +243,7 @@ class RstReader(BaseReader):
|
||||||
|
|
||||||
|
|
||||||
class MarkdownReader(BaseReader):
|
class MarkdownReader(BaseReader):
|
||||||
|
|
||||||
"""Reader for Markdown files"""
|
"""Reader for Markdown files"""
|
||||||
|
|
||||||
enabled = bool(Markdown)
|
enabled = bool(Markdown)
|
||||||
|
|
@ -268,7 +274,7 @@ class MarkdownReader(BaseReader):
|
||||||
elif name in METADATA_PROCESSORS:
|
elif name in METADATA_PROCESSORS:
|
||||||
if len(value) > 1:
|
if len(value) > 1:
|
||||||
logger.warning('Duplicate definition of `%s` '
|
logger.warning('Duplicate definition of `%s` '
|
||||||
'for %s. Using first one.', name, self._source_path)
|
'for %s. Using first one.', name, self._source_path)
|
||||||
output[name] = self.process_metadata(name, value[0])
|
output[name] = self.process_metadata(name, value[0])
|
||||||
elif len(value) > 1:
|
elif len(value) > 1:
|
||||||
# handle list metadata as list of string
|
# handle list metadata as list of string
|
||||||
|
|
@ -291,12 +297,14 @@ class MarkdownReader(BaseReader):
|
||||||
|
|
||||||
|
|
||||||
class HTMLReader(BaseReader):
|
class HTMLReader(BaseReader):
|
||||||
|
|
||||||
"""Parses HTML files as input, looking for meta, title, and body tags"""
|
"""Parses HTML files as input, looking for meta, title, and body tags"""
|
||||||
|
|
||||||
file_extensions = ['htm', 'html']
|
file_extensions = ['htm', 'html']
|
||||||
enabled = True
|
enabled = True
|
||||||
|
|
||||||
class _HTMLParser(HTMLParser):
|
class _HTMLParser(HTMLParser):
|
||||||
|
|
||||||
def __init__(self, settings, filename):
|
def __init__(self, settings, filename):
|
||||||
try:
|
try:
|
||||||
# Python 3.4+
|
# Python 3.4+
|
||||||
|
|
@ -380,7 +388,8 @@ class HTMLReader(BaseReader):
|
||||||
def _handle_meta_tag(self, attrs):
|
def _handle_meta_tag(self, attrs):
|
||||||
name = self._attr_value(attrs, 'name')
|
name = self._attr_value(attrs, 'name')
|
||||||
if name is None:
|
if name is None:
|
||||||
attr_serialized = ', '.join(['{}="{}"'.format(k, v) for k, v in attrs])
|
attr_serialized = ', '.join(
|
||||||
|
['{}="{}"'.format(k, v) for k, v in attrs])
|
||||||
logger.warning("Meta tag in file %s does not have a 'name' "
|
logger.warning("Meta tag in file %s does not have a 'name' "
|
||||||
"attribute, skipping. Attributes: %s",
|
"attribute, skipping. Attributes: %s",
|
||||||
self._filename, attr_serialized)
|
self._filename, attr_serialized)
|
||||||
|
|
@ -420,6 +429,7 @@ class HTMLReader(BaseReader):
|
||||||
|
|
||||||
|
|
||||||
class Readers(FileStampDataCacher):
|
class Readers(FileStampDataCacher):
|
||||||
|
|
||||||
"""Interface for all readers.
|
"""Interface for all readers.
|
||||||
|
|
||||||
This class contains a mapping of file extensions / Reader classes, to know
|
This class contains a mapping of file extensions / Reader classes, to know
|
||||||
|
|
@ -475,7 +485,7 @@ class Readers(FileStampDataCacher):
|
||||||
path = os.path.abspath(os.path.join(base_path, path))
|
path = os.path.abspath(os.path.join(base_path, path))
|
||||||
source_path = posixize_path(os.path.relpath(path, base_path))
|
source_path = posixize_path(os.path.relpath(path, base_path))
|
||||||
logger.debug('Read file %s -> %s',
|
logger.debug('Read file %s -> %s',
|
||||||
source_path, content_class.__name__)
|
source_path, content_class.__name__)
|
||||||
|
|
||||||
if not fmt:
|
if not fmt:
|
||||||
_, ext = os.path.splitext(os.path.basename(path))
|
_, ext = os.path.splitext(os.path.basename(path))
|
||||||
|
|
@ -487,7 +497,7 @@ class Readers(FileStampDataCacher):
|
||||||
|
|
||||||
if preread_signal:
|
if preread_signal:
|
||||||
logger.debug('Signal %s.send(%s)',
|
logger.debug('Signal %s.send(%s)',
|
||||||
preread_signal.name, preread_sender)
|
preread_signal.name, preread_sender)
|
||||||
preread_signal.send(preread_sender)
|
preread_signal.send(preread_sender)
|
||||||
|
|
||||||
reader = self.readers[fmt]
|
reader = self.readers[fmt]
|
||||||
|
|
@ -540,7 +550,7 @@ class Readers(FileStampDataCacher):
|
||||||
|
|
||||||
if context_signal:
|
if context_signal:
|
||||||
logger.debug('Signal %s.send(%s, <metadata>)',
|
logger.debug('Signal %s.send(%s, <metadata>)',
|
||||||
context_signal.name, context_sender)
|
context_signal.name, context_sender)
|
||||||
context_signal.send(context_sender, metadata=metadata)
|
context_signal.send(context_sender, metadata=metadata)
|
||||||
|
|
||||||
return content_class(content=content, metadata=metadata,
|
return content_class(content=content, metadata=metadata,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import pelican.settings as pys
|
||||||
|
|
||||||
|
|
||||||
class Pygments(Directive):
|
class Pygments(Directive):
|
||||||
|
|
||||||
""" Source code syntax highlighting.
|
""" Source code syntax highlighting.
|
||||||
"""
|
"""
|
||||||
required_arguments = 1
|
required_arguments = 1
|
||||||
|
|
|
||||||
|
|
@ -54,12 +54,12 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
socketserver.TCPServer.allow_reuse_address = True
|
socketserver.TCPServer.allow_reuse_address = True
|
||||||
try:
|
try:
|
||||||
httpd = socketserver.TCPServer((SERVER, PORT), ComplexHTTPRequestHandler)
|
httpd = socketserver.TCPServer(
|
||||||
|
(SERVER, PORT), ComplexHTTPRequestHandler)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
logging.error("Could not listen on port %s, server %s.", PORT, SERVER)
|
logging.error("Could not listen on port %s, server %s.", PORT, SERVER)
|
||||||
sys.exit(getattr(e, 'exitcode', 1))
|
sys.exit(getattr(e, 'exitcode', 1))
|
||||||
|
|
||||||
|
|
||||||
logging.info("Serving at port %s, server %s.", PORT, SERVER)
|
logging.info("Serving at port %s, server %s.", PORT, SERVER)
|
||||||
try:
|
try:
|
||||||
httpd.serve_forever()
|
httpd.serve_forever()
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ DEFAULT_CONFIG = {
|
||||||
'LOAD_CONTENT_CACHE': False,
|
'LOAD_CONTENT_CACHE': False,
|
||||||
'WRITE_SELECTED': [],
|
'WRITE_SELECTED': [],
|
||||||
'FORMATTED_FIELDS': ['summary'],
|
'FORMATTED_FIELDS': ['summary'],
|
||||||
}
|
}
|
||||||
|
|
||||||
PYGMENTS_RST_OPTIONS = None
|
PYGMENTS_RST_OPTIONS = None
|
||||||
|
|
||||||
|
|
@ -158,8 +158,8 @@ def read_settings(path=None, override=None):
|
||||||
"has been deprecated (should be a list)")
|
"has been deprecated (should be a list)")
|
||||||
local_settings['PLUGIN_PATHS'] = [local_settings['PLUGIN_PATHS']]
|
local_settings['PLUGIN_PATHS'] = [local_settings['PLUGIN_PATHS']]
|
||||||
elif local_settings['PLUGIN_PATHS'] is not None:
|
elif local_settings['PLUGIN_PATHS'] is not None:
|
||||||
local_settings['PLUGIN_PATHS'] = [os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(path), pluginpath)))
|
local_settings['PLUGIN_PATHS'] = [os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(path), pluginpath)))
|
||||||
if not isabs(pluginpath) else pluginpath for pluginpath in local_settings['PLUGIN_PATHS']]
|
if not isabs(pluginpath) else pluginpath for pluginpath in local_settings['PLUGIN_PATHS']]
|
||||||
else:
|
else:
|
||||||
local_settings = copy.deepcopy(DEFAULT_CONFIG)
|
local_settings = copy.deepcopy(DEFAULT_CONFIG)
|
||||||
|
|
||||||
|
|
@ -205,7 +205,7 @@ def configure_settings(settings):
|
||||||
|
|
||||||
# specify the log messages to be ignored
|
# specify the log messages to be ignored
|
||||||
LimitFilter._ignore.update(set(settings.get('LOG_FILTER',
|
LimitFilter._ignore.update(set(settings.get('LOG_FILTER',
|
||||||
DEFAULT_CONFIG['LOG_FILTER'])))
|
DEFAULT_CONFIG['LOG_FILTER'])))
|
||||||
|
|
||||||
# lookup the theme in "pelican/themes" if the given one doesn't exist
|
# 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']):
|
||||||
|
|
@ -223,19 +223,19 @@ def configure_settings(settings):
|
||||||
settings['WRITE_SELECTED'] = [
|
settings['WRITE_SELECTED'] = [
|
||||||
os.path.abspath(path) for path in
|
os.path.abspath(path) for path in
|
||||||
settings.get('WRITE_SELECTED', DEFAULT_CONFIG['WRITE_SELECTED'])
|
settings.get('WRITE_SELECTED', DEFAULT_CONFIG['WRITE_SELECTED'])
|
||||||
]
|
]
|
||||||
|
|
||||||
# standardize strings to lowercase strings
|
# standardize strings to lowercase strings
|
||||||
for key in [
|
for key in [
|
||||||
'DEFAULT_LANG',
|
'DEFAULT_LANG',
|
||||||
]:
|
]:
|
||||||
if key in settings:
|
if key in settings:
|
||||||
settings[key] = settings[key].lower()
|
settings[key] = settings[key].lower()
|
||||||
|
|
||||||
# standardize strings to lists
|
# standardize strings to lists
|
||||||
for key in [
|
for key in [
|
||||||
'LOCALE',
|
'LOCALE',
|
||||||
]:
|
]:
|
||||||
if key in settings and isinstance(settings[key], six.string_types):
|
if key in settings and isinstance(settings[key], six.string_types):
|
||||||
settings[key] = [settings[key]]
|
settings[key] = [settings[key]]
|
||||||
|
|
||||||
|
|
@ -243,12 +243,12 @@ def configure_settings(settings):
|
||||||
for key, types in [
|
for key, types in [
|
||||||
('OUTPUT_SOURCES_EXTENSION', six.string_types),
|
('OUTPUT_SOURCES_EXTENSION', six.string_types),
|
||||||
('FILENAME_METADATA', six.string_types),
|
('FILENAME_METADATA', six.string_types),
|
||||||
]:
|
]:
|
||||||
if key in settings and not isinstance(settings[key], types):
|
if key in settings and not isinstance(settings[key], types):
|
||||||
value = settings.pop(key)
|
value = settings.pop(key)
|
||||||
logger.warn('Detected misconfigured %s (%s), '
|
logger.warn('Detected misconfigured %s (%s), '
|
||||||
'falling back to the default (%s)',
|
'falling back to the default (%s)',
|
||||||
key, value, DEFAULT_CONFIG[key])
|
key, value, DEFAULT_CONFIG[key])
|
||||||
|
|
||||||
# try to set the different locales, fallback on the default.
|
# try to set the different locales, fallback on the default.
|
||||||
locales = settings.get('LOCALE', DEFAULT_CONFIG['LOCALE'])
|
locales = settings.get('LOCALE', DEFAULT_CONFIG['LOCALE'])
|
||||||
|
|
@ -275,11 +275,11 @@ def configure_settings(settings):
|
||||||
|
|
||||||
# check content caching layer and warn of incompatibilities
|
# check content caching layer and warn of incompatibilities
|
||||||
if (settings.get('CACHE_CONTENT', False) and
|
if (settings.get('CACHE_CONTENT', False) and
|
||||||
settings.get('CONTENT_CACHING_LAYER', '') == 'generator' and
|
settings.get('CONTENT_CACHING_LAYER', '') == 'generator' and
|
||||||
settings.get('WITH_FUTURE_DATES', DEFAULT_CONFIG['WITH_FUTURE_DATES'])):
|
settings.get('WITH_FUTURE_DATES', DEFAULT_CONFIG['WITH_FUTURE_DATES'])):
|
||||||
logger.warning('WITH_FUTURE_DATES conflicts with '
|
logger.warning('WITH_FUTURE_DATES conflicts with '
|
||||||
"CONTENT_CACHING_LAYER set to 'generator', "
|
"CONTENT_CACHING_LAYER set to 'generator', "
|
||||||
"use 'reader' layer instead")
|
"use 'reader' layer instead")
|
||||||
|
|
||||||
# Warn if feeds are generated with both SITEURL & FEED_DOMAIN undefined
|
# Warn if feeds are generated with both SITEURL & FEED_DOMAIN undefined
|
||||||
feed_keys = [
|
feed_keys = [
|
||||||
|
|
@ -322,7 +322,7 @@ def configure_settings(settings):
|
||||||
new_key = key + '_PATHS'
|
new_key = key + '_PATHS'
|
||||||
if old_key in settings:
|
if old_key in settings:
|
||||||
logger.warning('Deprecated setting %s, moving it to %s list',
|
logger.warning('Deprecated setting %s, moving it to %s list',
|
||||||
old_key, new_key)
|
old_key, new_key)
|
||||||
settings[new_key] = [settings[old_key]] # also make a list
|
settings[new_key] = [settings[old_key]] # also make a list
|
||||||
del settings[old_key]
|
del settings[old_key]
|
||||||
|
|
||||||
|
|
@ -366,7 +366,7 @@ def configure_settings(settings):
|
||||||
('LESS_GENERATOR', 'the Webassets plugin', None),
|
('LESS_GENERATOR', 'the Webassets plugin', None),
|
||||||
('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA',
|
('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA',
|
||||||
'https://github.com/getpelican/pelican/blob/master/docs/settings.rst#path-metadata'),
|
'https://github.com/getpelican/pelican/blob/master/docs/settings.rst#path-metadata'),
|
||||||
]:
|
]:
|
||||||
if old in settings:
|
if old in settings:
|
||||||
message = 'The {} setting has been removed in favor of {}'.format(
|
message = 'The {} setting has been removed in favor of {}'.format(
|
||||||
old, new)
|
old, new)
|
||||||
|
|
|
||||||
|
|
@ -31,17 +31,16 @@ DEFAULT_METADATA = {'yeah': 'it is'}
|
||||||
# path-specific metadata
|
# path-specific metadata
|
||||||
EXTRA_PATH_METADATA = {
|
EXTRA_PATH_METADATA = {
|
||||||
'extra/robots.txt': {'path': 'robots.txt'},
|
'extra/robots.txt': {'path': 'robots.txt'},
|
||||||
}
|
}
|
||||||
|
|
||||||
# static paths will be copied without parsing their contents
|
# static paths will be copied without parsing their contents
|
||||||
STATIC_PATHS = [
|
STATIC_PATHS = [
|
||||||
'pictures',
|
'pictures',
|
||||||
'extra/robots.txt',
|
'extra/robots.txt',
|
||||||
]
|
]
|
||||||
|
|
||||||
FORMATTED_FIELDS = ['summary', 'custom_formatted_field']
|
FORMATTED_FIELDS = ['summary', 'custom_formatted_field']
|
||||||
|
|
||||||
# foobar will not be used, because it's not in caps. All configuration keys
|
# foobar will not be used, because it's not in caps. All configuration keys
|
||||||
# have to be in caps
|
# have to be in caps
|
||||||
foobar = "barbaz"
|
foobar = "barbaz"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,12 +167,13 @@ def get_settings(**kwargs):
|
||||||
Set keyword arguments to override specific settings.
|
Set keyword arguments to override specific settings.
|
||||||
"""
|
"""
|
||||||
settings = DEFAULT_CONFIG.copy()
|
settings = DEFAULT_CONFIG.copy()
|
||||||
for key,value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
settings[key] = value
|
settings[key] = value
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
|
|
||||||
class LogCountHandler(BufferingHandler):
|
class LogCountHandler(BufferingHandler):
|
||||||
|
|
||||||
"""Capturing and counting logged messages."""
|
"""Capturing and counting logged messages."""
|
||||||
|
|
||||||
def __init__(self, capacity=1000):
|
def __init__(self, capacity=1000):
|
||||||
|
|
@ -180,12 +181,13 @@ class LogCountHandler(BufferingHandler):
|
||||||
|
|
||||||
def count_logs(self, msg=None, level=None):
|
def count_logs(self, msg=None, level=None):
|
||||||
return len([l for l in self.buffer
|
return len([l for l in self.buffer
|
||||||
if (msg is None or re.match(msg, l.getMessage()))
|
if (msg is None or re.match(msg, l.getMessage()))
|
||||||
and (level is None or l.levelno == level)
|
and (level is None or l.levelno == level)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
class LoggedTestCase(unittest.TestCase):
|
class LoggedTestCase(unittest.TestCase):
|
||||||
|
|
||||||
"""A test case that captures log messages."""
|
"""A test case that captures log messages."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ class TestCache(unittest.TestCase):
|
||||||
settings['CACHE_PATH'] = self.temp_cache
|
settings['CACHE_PATH'] = self.temp_cache
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
||||||
def test_article_object_caching(self):
|
def test_article_object_caching(self):
|
||||||
"""Test Article objects caching at the generator level"""
|
"""Test Article objects caching at the generator level"""
|
||||||
|
|
@ -44,7 +43,6 @@ class TestCache(unittest.TestCase):
|
||||||
settings['DEFAULT_DATE'] = (1970, 1, 1)
|
settings['DEFAULT_DATE'] = (1970, 1, 1)
|
||||||
settings['READERS'] = {'asc': None}
|
settings['READERS'] = {'asc': None}
|
||||||
|
|
||||||
|
|
||||||
generator = ArticlesGenerator(
|
generator = ArticlesGenerator(
|
||||||
context=settings.copy(), settings=settings,
|
context=settings.copy(), settings=settings,
|
||||||
path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
|
path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
|
||||||
|
|
@ -108,7 +106,8 @@ class TestCache(unittest.TestCase):
|
||||||
path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
|
path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
|
||||||
generator.readers.read_file = MagicMock()
|
generator.readers.read_file = MagicMock()
|
||||||
generator.generate_context()
|
generator.generate_context()
|
||||||
self.assertEqual(generator.readers.read_file.call_count, orig_call_count)
|
self.assertEqual(
|
||||||
|
generator.readers.read_file.call_count, orig_call_count)
|
||||||
|
|
||||||
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
@unittest.skipUnless(MagicMock, 'Needs Mock module')
|
||||||
def test_page_object_caching(self):
|
def test_page_object_caching(self):
|
||||||
|
|
@ -181,5 +180,5 @@ class TestCache(unittest.TestCase):
|
||||||
path=CUR_DIR, theme=settings['THEME'], output_path=None)
|
path=CUR_DIR, theme=settings['THEME'], output_path=None)
|
||||||
generator.readers.read_file = MagicMock()
|
generator.readers.read_file = MagicMock()
|
||||||
generator.generate_context()
|
generator.generate_context()
|
||||||
self.assertEqual(generator.readers.read_file.call_count, orig_call_count)
|
self.assertEqual(
|
||||||
|
generator.readers.read_file.call_count, orig_call_count)
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ class TestPage(unittest.TestCase):
|
||||||
# them to initialise object's attributes.
|
# them to initialise object's attributes.
|
||||||
metadata = {'foo': 'bar', 'foobar': 'baz', 'title': 'foobar', }
|
metadata = {'foo': 'bar', 'foobar': 'baz', 'title': 'foobar', }
|
||||||
page = Page(TEST_CONTENT, metadata=metadata,
|
page = Page(TEST_CONTENT, metadata=metadata,
|
||||||
context={'localsiteurl': ''})
|
context={'localsiteurl': ''})
|
||||||
for key, value in metadata.items():
|
for key, value in metadata.items():
|
||||||
self.assertTrue(hasattr(page, key))
|
self.assertTrue(hasattr(page, key))
|
||||||
self.assertEqual(value, getattr(page, key))
|
self.assertEqual(value, getattr(page, key))
|
||||||
|
|
@ -141,12 +141,13 @@ class TestPage(unittest.TestCase):
|
||||||
# page.locale_date is a unicode string in both python2 and python3
|
# page.locale_date is a unicode string in both python2 and python3
|
||||||
dt_date = dt.strftime(DEFAULT_CONFIG['DEFAULT_DATE_FORMAT'])
|
dt_date = dt.strftime(DEFAULT_CONFIG['DEFAULT_DATE_FORMAT'])
|
||||||
# dt_date is a byte string in python2, and a unicode string in python3
|
# dt_date is a byte string in python2, and a unicode string in python3
|
||||||
# Let's make sure it is a unicode string (relies on python 3.3 supporting the u prefix)
|
# Let's make sure it is a unicode string (relies on python 3.3
|
||||||
|
# supporting the u prefix)
|
||||||
if type(dt_date) != type(u''):
|
if type(dt_date) != type(u''):
|
||||||
# python2:
|
# python2:
|
||||||
dt_date = unicode(dt_date, 'utf8')
|
dt_date = unicode(dt_date, 'utf8')
|
||||||
|
|
||||||
self.assertEqual(page.locale_date, dt_date )
|
self.assertEqual(page.locale_date, dt_date)
|
||||||
page_kwargs['settings'] = get_settings()
|
page_kwargs['settings'] = get_settings()
|
||||||
|
|
||||||
# I doubt this can work on all platforms ...
|
# I doubt this can work on all platforms ...
|
||||||
|
|
@ -390,6 +391,7 @@ class TestPage(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestArticle(TestPage):
|
class TestArticle(TestPage):
|
||||||
|
|
||||||
def test_template(self):
|
def test_template(self):
|
||||||
# Articles default to article, metadata overwrites
|
# Articles default to article, metadata overwrites
|
||||||
default_article = Article(**self.page_kwargs)
|
default_article = Article(**self.page_kwargs)
|
||||||
|
|
@ -401,17 +403,19 @@ class TestArticle(TestPage):
|
||||||
|
|
||||||
def test_slugify_category_author(self):
|
def test_slugify_category_author(self):
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
settings['SLUG_SUBSTITUTIONS'] = [ ('C#', 'csharp') ]
|
settings['SLUG_SUBSTITUTIONS'] = [('C#', 'csharp')]
|
||||||
settings['ARTICLE_URL'] = '{author}/{category}/{slug}/'
|
settings['ARTICLE_URL'] = '{author}/{category}/{slug}/'
|
||||||
settings['ARTICLE_SAVE_AS'] = '{author}/{category}/{slug}/index.html'
|
settings['ARTICLE_SAVE_AS'] = '{author}/{category}/{slug}/index.html'
|
||||||
article_kwargs = self._copy_page_kwargs()
|
article_kwargs = self._copy_page_kwargs()
|
||||||
article_kwargs['metadata']['author'] = Author("O'Brien", settings)
|
article_kwargs['metadata']['author'] = Author("O'Brien", settings)
|
||||||
article_kwargs['metadata']['category'] = Category('C# & stuff', settings)
|
article_kwargs['metadata'][
|
||||||
|
'category'] = Category('C# & stuff', settings)
|
||||||
article_kwargs['metadata']['title'] = 'fnord'
|
article_kwargs['metadata']['title'] = 'fnord'
|
||||||
article_kwargs['settings'] = settings
|
article_kwargs['settings'] = settings
|
||||||
article = Article(**article_kwargs)
|
article = Article(**article_kwargs)
|
||||||
self.assertEqual(article.url, 'obrien/csharp-stuff/fnord/')
|
self.assertEqual(article.url, 'obrien/csharp-stuff/fnord/')
|
||||||
self.assertEqual(article.save_as, 'obrien/csharp-stuff/fnord/index.html')
|
self.assertEqual(
|
||||||
|
article.save_as, 'obrien/csharp-stuff/fnord/index.html')
|
||||||
|
|
||||||
|
|
||||||
class TestStatic(LoggedTestCase):
|
class TestStatic(LoggedTestCase):
|
||||||
|
|
@ -426,7 +430,7 @@ class TestStatic(LoggedTestCase):
|
||||||
self.context = self.settings.copy()
|
self.context = self.settings.copy()
|
||||||
|
|
||||||
self.static = Static(content=None, metadata={}, settings=self.settings,
|
self.static = Static(content=None, metadata={}, settings=self.settings,
|
||||||
source_path=posix_join('dir', 'foo.jpg'), context=self.context)
|
source_path=posix_join('dir', 'foo.jpg'), context=self.context)
|
||||||
|
|
||||||
self.context['filenames'] = {self.static.source_path: self.static}
|
self.context['filenames'] = {self.static.source_path: self.static}
|
||||||
|
|
||||||
|
|
@ -437,8 +441,8 @@ class TestStatic(LoggedTestCase):
|
||||||
"""attach_to() overrides a static file's save_as and url.
|
"""attach_to() overrides a static file's save_as and url.
|
||||||
"""
|
"""
|
||||||
page = Page(content="fake page",
|
page = Page(content="fake page",
|
||||||
metadata={'title': 'fakepage'}, settings=self.settings,
|
metadata={'title': 'fakepage'}, settings=self.settings,
|
||||||
source_path=os.path.join('dir', 'fakepage.md'))
|
source_path=os.path.join('dir', 'fakepage.md'))
|
||||||
self.static.attach_to(page)
|
self.static.attach_to(page)
|
||||||
|
|
||||||
expected_save_as = os.path.join('outpages', 'foo.jpg')
|
expected_save_as = os.path.join('outpages', 'foo.jpg')
|
||||||
|
|
@ -449,7 +453,7 @@ class TestStatic(LoggedTestCase):
|
||||||
"""attach_to() preserves dirs inside the linking document dir.
|
"""attach_to() preserves dirs inside the linking document dir.
|
||||||
"""
|
"""
|
||||||
page = Page(content="fake page", metadata={'title': 'fakepage'},
|
page = Page(content="fake page", metadata={'title': 'fakepage'},
|
||||||
settings=self.settings, source_path='fakepage.md')
|
settings=self.settings, source_path='fakepage.md')
|
||||||
self.static.attach_to(page)
|
self.static.attach_to(page)
|
||||||
|
|
||||||
expected_save_as = os.path.join('outpages', 'dir', 'foo.jpg')
|
expected_save_as = os.path.join('outpages', 'dir', 'foo.jpg')
|
||||||
|
|
@ -460,8 +464,8 @@ class TestStatic(LoggedTestCase):
|
||||||
"""attach_to() ignores dirs outside the linking document dir.
|
"""attach_to() ignores dirs outside the linking document dir.
|
||||||
"""
|
"""
|
||||||
page = Page(content="fake page",
|
page = Page(content="fake page",
|
||||||
metadata={'title': 'fakepage'}, settings=self.settings,
|
metadata={'title': 'fakepage'}, settings=self.settings,
|
||||||
source_path=os.path.join('dir', 'otherdir', 'fakepage.md'))
|
source_path=os.path.join('dir', 'otherdir', 'fakepage.md'))
|
||||||
self.static.attach_to(page)
|
self.static.attach_to(page)
|
||||||
|
|
||||||
expected_save_as = os.path.join('outpages', 'foo.jpg')
|
expected_save_as = os.path.join('outpages', 'foo.jpg')
|
||||||
|
|
@ -472,8 +476,8 @@ class TestStatic(LoggedTestCase):
|
||||||
"""attach_to() does nothing when called a second time.
|
"""attach_to() does nothing when called a second time.
|
||||||
"""
|
"""
|
||||||
page = Page(content="fake page",
|
page = Page(content="fake page",
|
||||||
metadata={'title': 'fakepage'}, settings=self.settings,
|
metadata={'title': 'fakepage'}, settings=self.settings,
|
||||||
source_path=os.path.join('dir', 'fakepage.md'))
|
source_path=os.path.join('dir', 'fakepage.md'))
|
||||||
|
|
||||||
self.static.attach_to(page)
|
self.static.attach_to(page)
|
||||||
|
|
||||||
|
|
@ -482,8 +486,8 @@ class TestStatic(LoggedTestCase):
|
||||||
PAGE_SAVE_AS=os.path.join('otherpages', '{slug}.html'),
|
PAGE_SAVE_AS=os.path.join('otherpages', '{slug}.html'),
|
||||||
PAGE_URL='otherpages/{slug}.html'))
|
PAGE_URL='otherpages/{slug}.html'))
|
||||||
otherdir_page = Page(content="other page",
|
otherdir_page = Page(content="other page",
|
||||||
metadata={'title': 'otherpage'}, settings=otherdir_settings,
|
metadata={'title': 'otherpage'}, settings=otherdir_settings,
|
||||||
source_path=os.path.join('dir', 'otherpage.md'))
|
source_path=os.path.join('dir', 'otherpage.md'))
|
||||||
|
|
||||||
self.static.attach_to(otherdir_page)
|
self.static.attach_to(otherdir_page)
|
||||||
|
|
||||||
|
|
@ -498,8 +502,8 @@ class TestStatic(LoggedTestCase):
|
||||||
original_save_as = self.static.save_as
|
original_save_as = self.static.save_as
|
||||||
|
|
||||||
page = Page(content="fake page",
|
page = Page(content="fake page",
|
||||||
metadata={'title': 'fakepage'}, settings=self.settings,
|
metadata={'title': 'fakepage'}, settings=self.settings,
|
||||||
source_path=os.path.join('dir', 'fakepage.md'))
|
source_path=os.path.join('dir', 'fakepage.md'))
|
||||||
self.static.attach_to(page)
|
self.static.attach_to(page)
|
||||||
|
|
||||||
self.assertEqual(self.static.save_as, original_save_as)
|
self.assertEqual(self.static.save_as, original_save_as)
|
||||||
|
|
@ -512,8 +516,8 @@ class TestStatic(LoggedTestCase):
|
||||||
original_url = self.static.url
|
original_url = self.static.url
|
||||||
|
|
||||||
page = Page(content="fake page",
|
page = Page(content="fake page",
|
||||||
metadata={'title': 'fakepage'}, settings=self.settings,
|
metadata={'title': 'fakepage'}, settings=self.settings,
|
||||||
source_path=os.path.join('dir', 'fakepage.md'))
|
source_path=os.path.join('dir', 'fakepage.md'))
|
||||||
self.static.attach_to(page)
|
self.static.attach_to(page)
|
||||||
|
|
||||||
self.assertEqual(self.static.save_as, self.static.source_path)
|
self.assertEqual(self.static.save_as, self.static.source_path)
|
||||||
|
|
@ -524,14 +528,15 @@ class TestStatic(LoggedTestCase):
|
||||||
(For example, by the user with EXTRA_PATH_METADATA)
|
(For example, by the user with EXTRA_PATH_METADATA)
|
||||||
"""
|
"""
|
||||||
customstatic = Static(content=None,
|
customstatic = Static(content=None,
|
||||||
metadata=dict(save_as='customfoo.jpg', url='customfoo.jpg'),
|
metadata=dict(
|
||||||
settings=self.settings,
|
save_as='customfoo.jpg', url='customfoo.jpg'),
|
||||||
source_path=os.path.join('dir', 'foo.jpg'),
|
settings=self.settings,
|
||||||
context=self.settings.copy())
|
source_path=os.path.join('dir', 'foo.jpg'),
|
||||||
|
context=self.settings.copy())
|
||||||
|
|
||||||
page = Page(content="fake page",
|
page = Page(content="fake page",
|
||||||
metadata={'title': 'fakepage'}, settings=self.settings,
|
metadata={'title': 'fakepage'}, settings=self.settings,
|
||||||
source_path=os.path.join('dir', 'fakepage.md'))
|
source_path=os.path.join('dir', 'fakepage.md'))
|
||||||
|
|
||||||
customstatic.attach_to(page)
|
customstatic.attach_to(page)
|
||||||
|
|
||||||
|
|
@ -543,13 +548,13 @@ class TestStatic(LoggedTestCase):
|
||||||
"""
|
"""
|
||||||
html = '<a href="{attach}../foo.jpg">link</a>'
|
html = '<a href="{attach}../foo.jpg">link</a>'
|
||||||
page = Page(content=html,
|
page = Page(content=html,
|
||||||
metadata={'title': 'fakepage'}, settings=self.settings,
|
metadata={'title': 'fakepage'}, settings=self.settings,
|
||||||
source_path=os.path.join('dir', 'otherdir', 'fakepage.md'),
|
source_path=os.path.join('dir', 'otherdir', 'fakepage.md'),
|
||||||
context=self.context)
|
context=self.context)
|
||||||
content = page.get_content('')
|
content = page.get_content('')
|
||||||
|
|
||||||
self.assertNotEqual(content, html,
|
self.assertNotEqual(content, html,
|
||||||
"{attach} link syntax did not trigger URL replacement.")
|
"{attach} link syntax did not trigger URL replacement.")
|
||||||
|
|
||||||
expected_save_as = os.path.join('outpages', 'foo.jpg')
|
expected_save_as = os.path.join('outpages', 'foo.jpg')
|
||||||
self.assertEqual(self.static.save_as, expected_save_as)
|
self.assertEqual(self.static.save_as, expected_save_as)
|
||||||
|
|
@ -573,9 +578,9 @@ class TestStatic(LoggedTestCase):
|
||||||
|
|
||||||
html = '<a href="{category}foo">link</a>'
|
html = '<a href="{category}foo">link</a>'
|
||||||
page = Page(content=html,
|
page = Page(content=html,
|
||||||
metadata={'title': 'fakepage'}, settings=self.settings,
|
metadata={'title': 'fakepage'}, settings=self.settings,
|
||||||
source_path=os.path.join('dir', 'otherdir', 'fakepage.md'),
|
source_path=os.path.join('dir', 'otherdir', 'fakepage.md'),
|
||||||
context=self.context)
|
context=self.context)
|
||||||
content = page.get_content('')
|
content = page.get_content('')
|
||||||
|
|
||||||
self.assertNotEqual(content, html)
|
self.assertNotEqual(content, html)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ CONTENT_DIR = os.path.join(CUR_DIR, 'content')
|
||||||
|
|
||||||
|
|
||||||
class TestGenerator(unittest.TestCase):
|
class TestGenerator(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.old_locale = locale.setlocale(locale.LC_ALL)
|
self.old_locale = locale.setlocale(locale.LC_ALL)
|
||||||
locale.setlocale(locale.LC_ALL, str('C'))
|
locale.setlocale(locale.LC_ALL, str('C'))
|
||||||
|
|
@ -35,7 +36,6 @@ class TestGenerator(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
locale.setlocale(locale.LC_ALL, self.old_locale)
|
locale.setlocale(locale.LC_ALL, self.old_locale)
|
||||||
|
|
||||||
|
|
||||||
def test_include_path(self):
|
def test_include_path(self):
|
||||||
self.settings['IGNORE_FILES'] = {'ignored1.rst', 'ignored2.rst'}
|
self.settings['IGNORE_FILES'] = {'ignored1.rst', 'ignored2.rst'}
|
||||||
|
|
||||||
|
|
@ -53,40 +53,41 @@ class TestGenerator(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
# We use our own Generator so we can give it our own content path
|
# We use our own Generator so we can give it our own content path
|
||||||
generator = Generator(context=self.settings.copy(),
|
generator = Generator(context=self.settings.copy(),
|
||||||
settings=self.settings,
|
settings=self.settings,
|
||||||
path=os.path.join(CUR_DIR, 'nested_content'),
|
path=os.path.join(CUR_DIR, 'nested_content'),
|
||||||
theme=self.settings['THEME'], output_path=None)
|
theme=self.settings['THEME'], output_path=None)
|
||||||
|
|
||||||
filepaths = generator.get_files(paths=['maindir'])
|
filepaths = generator.get_files(paths=['maindir'])
|
||||||
found_files = {os.path.basename(f) for f in filepaths}
|
found_files = {os.path.basename(f) for f in filepaths}
|
||||||
expected_files = {'maindir.md', 'subdir.md'}
|
expected_files = {'maindir.md', 'subdir.md'}
|
||||||
self.assertFalse(expected_files - found_files,
|
self.assertFalse(expected_files - found_files,
|
||||||
"get_files() failed to find one or more files")
|
"get_files() failed to find one or more files")
|
||||||
|
|
||||||
# Test string as `paths` argument rather than list
|
# Test string as `paths` argument rather than list
|
||||||
filepaths = generator.get_files(paths='maindir')
|
filepaths = generator.get_files(paths='maindir')
|
||||||
found_files = {os.path.basename(f) for f in filepaths}
|
found_files = {os.path.basename(f) for f in filepaths}
|
||||||
expected_files = {'maindir.md', 'subdir.md'}
|
expected_files = {'maindir.md', 'subdir.md'}
|
||||||
self.assertFalse(expected_files - found_files,
|
self.assertFalse(expected_files - found_files,
|
||||||
"get_files() failed to find one or more files")
|
"get_files() failed to find one or more files")
|
||||||
|
|
||||||
filepaths = generator.get_files(paths=[''], exclude=['maindir'])
|
filepaths = generator.get_files(paths=[''], exclude=['maindir'])
|
||||||
found_files = {os.path.basename(f) for f in filepaths}
|
found_files = {os.path.basename(f) for f in filepaths}
|
||||||
self.assertNotIn('maindir.md', found_files,
|
self.assertNotIn('maindir.md', found_files,
|
||||||
"get_files() failed to exclude a top-level directory")
|
"get_files() failed to exclude a top-level directory")
|
||||||
self.assertNotIn('subdir.md', found_files,
|
self.assertNotIn('subdir.md', found_files,
|
||||||
"get_files() failed to exclude a subdir of an excluded directory")
|
"get_files() failed to exclude a subdir of an excluded directory")
|
||||||
|
|
||||||
filepaths = generator.get_files(paths=[''],
|
filepaths = generator.get_files(paths=[''],
|
||||||
exclude=[os.path.join('maindir', 'subdir')])
|
exclude=[os.path.join('maindir', 'subdir')])
|
||||||
found_files = {os.path.basename(f) for f in filepaths}
|
found_files = {os.path.basename(f) for f in filepaths}
|
||||||
self.assertNotIn('subdir.md', found_files,
|
self.assertNotIn('subdir.md', found_files,
|
||||||
"get_files() failed to exclude a subdirectory")
|
"get_files() failed to exclude a subdirectory")
|
||||||
|
|
||||||
filepaths = generator.get_files(paths=[''], exclude=['subdir'])
|
filepaths = generator.get_files(paths=[''], exclude=['subdir'])
|
||||||
found_files = {os.path.basename(f) for f in filepaths}
|
found_files = {os.path.basename(f) for f in filepaths}
|
||||||
self.assertIn('subdir.md', found_files,
|
self.assertIn('subdir.md', found_files,
|
||||||
"get_files() excluded a subdirectory by name, ignoring its path")
|
"get_files() excluded a subdirectory by name, ignoring its path")
|
||||||
|
|
||||||
|
|
||||||
class TestArticlesGenerator(unittest.TestCase):
|
class TestArticlesGenerator(unittest.TestCase):
|
||||||
|
|
||||||
|
|
@ -96,7 +97,8 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
settings['DEFAULT_CATEGORY'] = 'Default'
|
settings['DEFAULT_CATEGORY'] = 'Default'
|
||||||
settings['DEFAULT_DATE'] = (1970, 1, 1)
|
settings['DEFAULT_DATE'] = (1970, 1, 1)
|
||||||
settings['READERS'] = {'asc': None}
|
settings['READERS'] = {'asc': None}
|
||||||
settings['CACHE_CONTENT'] = False # cache not needed for this logic tests
|
# cache not needed for this logic tests
|
||||||
|
settings['CACHE_CONTENT'] = False
|
||||||
|
|
||||||
cls.generator = ArticlesGenerator(
|
cls.generator = ArticlesGenerator(
|
||||||
context=settings.copy(), settings=settings,
|
context=settings.copy(), settings=settings,
|
||||||
|
|
@ -152,23 +154,28 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
['Test mkd File', 'published', 'test', 'article'],
|
['Test mkd File', 'published', 'test', 'article'],
|
||||||
['This is a super article !', 'published', 'Yeah', 'article'],
|
['This is a super article !', 'published', 'Yeah', 'article'],
|
||||||
['This is a super article !', 'published', 'Yeah', 'article'],
|
['This is a super article !', 'published', 'Yeah', 'article'],
|
||||||
['Article with Nonconformant HTML meta tags', 'published', 'Default', 'article'],
|
['Article with Nonconformant HTML meta tags',
|
||||||
|
'published', 'Default', 'article'],
|
||||||
['This is a super article !', 'published', 'yeah', 'article'],
|
['This is a super article !', 'published', 'yeah', 'article'],
|
||||||
['This is a super article !', 'published', 'yeah', 'article'],
|
['This is a super article !', 'published', 'yeah', 'article'],
|
||||||
['This is a super article !', 'published', 'yeah', 'article'],
|
['This is a super article !', 'published', 'yeah', 'article'],
|
||||||
['This is a super article !', 'published', 'Default', 'article'],
|
['This is a super article !', 'published', 'Default', 'article'],
|
||||||
['This is an article with category !', 'published', 'yeah',
|
['This is an article with category !', 'published', 'yeah',
|
||||||
'article'],
|
'article'],
|
||||||
['This is an article with multiple authors!', 'published', 'Default', 'article'],
|
['This is an article with multiple authors!',
|
||||||
['This is an article with multiple authors!', 'published', 'Default', 'article'],
|
'published', 'Default', 'article'],
|
||||||
['This is an article with multiple authors in list format!', 'published', 'Default', 'article'],
|
['This is an article with multiple authors!',
|
||||||
['This is an article with multiple authors in lastname, firstname format!', 'published', 'Default', 'article'],
|
'published', 'Default', 'article'],
|
||||||
|
['This is an article with multiple authors in list format!',
|
||||||
|
'published', 'Default', 'article'],
|
||||||
|
['This is an article with multiple authors in lastname, firstname format!',
|
||||||
|
'published', 'Default', 'article'],
|
||||||
['This is an article without category !', 'published', 'Default',
|
['This is an article without category !', 'published', 'Default',
|
||||||
'article'],
|
'article'],
|
||||||
['This is an article without category !', 'published',
|
['This is an article without category !', 'published',
|
||||||
'TestCategory', 'article'],
|
'TestCategory', 'article'],
|
||||||
['An Article With Code Block To Test Typogrify Ignore',
|
['An Article With Code Block To Test Typogrify Ignore',
|
||||||
'published', 'Default', 'article'],
|
'published', 'Default', 'article'],
|
||||||
['マックOS X 10.8でパイソンとVirtualenvをインストールと設定', 'published',
|
['マックOS X 10.8でパイソンとVirtualenvをインストールと設定', 'published',
|
||||||
'指導書', 'article'],
|
'指導書', 'article'],
|
||||||
]
|
]
|
||||||
|
|
@ -292,7 +299,7 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
generator.generate_period_archives(write)
|
generator.generate_period_archives(write)
|
||||||
dates = [d for d in generator.dates if d.date.year == 1970]
|
dates = [d for d in generator.dates if d.date.year == 1970]
|
||||||
self.assertEqual(len(dates), 1)
|
self.assertEqual(len(dates), 1)
|
||||||
#among other things it must have at least been called with this
|
# among other things it must have at least been called with this
|
||||||
settings["period"] = (1970,)
|
settings["period"] = (1970,)
|
||||||
write.assert_called_with("posts/1970/index.html",
|
write.assert_called_with("posts/1970/index.html",
|
||||||
generator.get_template("period_archives"),
|
generator.get_template("period_archives"),
|
||||||
|
|
@ -300,7 +307,8 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
blog=True, dates=dates)
|
blog=True, dates=dates)
|
||||||
|
|
||||||
del settings["period"]
|
del settings["period"]
|
||||||
settings['MONTH_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/{date:%b}/index.html'
|
settings[
|
||||||
|
'MONTH_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/{date:%b}/index.html'
|
||||||
generator = ArticlesGenerator(
|
generator = ArticlesGenerator(
|
||||||
context=settings, settings=settings,
|
context=settings, settings=settings,
|
||||||
path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
|
path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
|
||||||
|
|
@ -308,17 +316,18 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
write = MagicMock()
|
write = MagicMock()
|
||||||
generator.generate_period_archives(write)
|
generator.generate_period_archives(write)
|
||||||
dates = [d for d in generator.dates if d.date.year == 1970
|
dates = [d for d in generator.dates if d.date.year == 1970
|
||||||
and d.date.month == 1]
|
and d.date.month == 1]
|
||||||
self.assertEqual(len(dates), 1)
|
self.assertEqual(len(dates), 1)
|
||||||
settings["period"] = (1970, "January")
|
settings["period"] = (1970, "January")
|
||||||
#among other things it must have at least been called with this
|
# among other things it must have at least been called with this
|
||||||
write.assert_called_with("posts/1970/Jan/index.html",
|
write.assert_called_with("posts/1970/Jan/index.html",
|
||||||
generator.get_template("period_archives"),
|
generator.get_template("period_archives"),
|
||||||
settings,
|
settings,
|
||||||
blog=True, dates=dates)
|
blog=True, dates=dates)
|
||||||
|
|
||||||
del settings["period"]
|
del settings["period"]
|
||||||
settings['DAY_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/{date:%b}/{date:%d}/index.html'
|
settings[
|
||||||
|
'DAY_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/{date:%b}/{date:%d}/index.html'
|
||||||
generator = ArticlesGenerator(
|
generator = ArticlesGenerator(
|
||||||
context=settings, settings=settings,
|
context=settings, settings=settings,
|
||||||
path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
|
path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
|
||||||
|
|
@ -326,11 +335,11 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
write = MagicMock()
|
write = MagicMock()
|
||||||
generator.generate_period_archives(write)
|
generator.generate_period_archives(write)
|
||||||
dates = [d for d in generator.dates if d.date.year == 1970
|
dates = [d for d in generator.dates if d.date.year == 1970
|
||||||
and d.date.month == 1
|
and d.date.month == 1
|
||||||
and d.date.day == 1]
|
and d.date.day == 1]
|
||||||
self.assertEqual(len(dates), 1)
|
self.assertEqual(len(dates), 1)
|
||||||
settings["period"] = (1970, "January", 1)
|
settings["period"] = (1970, "January", 1)
|
||||||
#among other things it must have at least been called with this
|
# among other things it must have at least been called with this
|
||||||
write.assert_called_with("posts/1970/Jan/01/index.html",
|
write.assert_called_with("posts/1970/Jan/01/index.html",
|
||||||
generator.get_template("period_archives"),
|
generator.get_template("period_archives"),
|
||||||
settings,
|
settings,
|
||||||
|
|
@ -347,11 +356,13 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
def test_generate_authors(self):
|
def test_generate_authors(self):
|
||||||
"""Check authors generation."""
|
"""Check authors generation."""
|
||||||
authors = [author.name for author, _ in self.generator.authors]
|
authors = [author.name for author, _ in self.generator.authors]
|
||||||
authors_expected = sorted(['Alexis Métaireau', 'Author, First', 'Author, Second', 'First Author', 'Second Author'])
|
authors_expected = sorted(
|
||||||
|
['Alexis Métaireau', 'Author, First', 'Author, Second', 'First Author', 'Second Author'])
|
||||||
self.assertEqual(sorted(authors), authors_expected)
|
self.assertEqual(sorted(authors), authors_expected)
|
||||||
# test for slug
|
# test for slug
|
||||||
authors = [author.slug for author, _ in self.generator.authors]
|
authors = [author.slug for author, _ in self.generator.authors]
|
||||||
authors_expected = ['alexis-metaireau', 'author-first', 'author-second', 'first-author', 'second-author']
|
authors_expected = ['alexis-metaireau', 'author-first',
|
||||||
|
'author-second', 'first-author', 'second-author']
|
||||||
self.assertEqual(sorted(authors), sorted(authors_expected))
|
self.assertEqual(sorted(authors), sorted(authors_expected))
|
||||||
|
|
||||||
def test_standard_metadata_in_default_metadata(self):
|
def test_standard_metadata_in_default_metadata(self):
|
||||||
|
|
@ -391,7 +402,8 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
settings = get_settings(filenames={})
|
settings = get_settings(filenames={})
|
||||||
settings['DEFAULT_CATEGORY'] = 'Default'
|
settings['DEFAULT_CATEGORY'] = 'Default'
|
||||||
settings['DEFAULT_DATE'] = (1970, 1, 1)
|
settings['DEFAULT_DATE'] = (1970, 1, 1)
|
||||||
settings['CACHE_CONTENT'] = False # cache not needed for this logic tests
|
# cache not needed for this logic tests
|
||||||
|
settings['CACHE_CONTENT'] = False
|
||||||
settings['ARTICLE_ORDER_BY'] = 'title'
|
settings['ARTICLE_ORDER_BY'] = 'title'
|
||||||
|
|
||||||
generator = ArticlesGenerator(
|
generator = ArticlesGenerator(
|
||||||
|
|
@ -435,7 +447,8 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
settings = get_settings(filenames={})
|
settings = get_settings(filenames={})
|
||||||
settings['DEFAULT_CATEGORY'] = 'Default'
|
settings['DEFAULT_CATEGORY'] = 'Default'
|
||||||
settings['DEFAULT_DATE'] = (1970, 1, 1)
|
settings['DEFAULT_DATE'] = (1970, 1, 1)
|
||||||
settings['CACHE_CONTENT'] = False # cache not needed for this logic tests
|
# cache not needed for this logic tests
|
||||||
|
settings['CACHE_CONTENT'] = False
|
||||||
settings['ARTICLE_ORDER_BY'] = 'reversed-title'
|
settings['ARTICLE_ORDER_BY'] = 'reversed-title'
|
||||||
|
|
||||||
generator = ArticlesGenerator(
|
generator = ArticlesGenerator(
|
||||||
|
|
@ -561,7 +574,7 @@ class TestPageGenerator(unittest.TestCase):
|
||||||
are generated correctly on pages
|
are generated correctly on pages
|
||||||
"""
|
"""
|
||||||
settings = get_settings(filenames={})
|
settings = get_settings(filenames={})
|
||||||
settings['PAGE_PATHS'] = ['TestPages'] # relative to CUR_DIR
|
settings['PAGE_PATHS'] = ['TestPages'] # relative to CUR_DIR
|
||||||
settings['CACHE_PATH'] = self.temp_cache
|
settings['CACHE_PATH'] = self.temp_cache
|
||||||
settings['DEFAULT_DATE'] = (1970, 1, 1)
|
settings['DEFAULT_DATE'] = (1970, 1, 1)
|
||||||
|
|
||||||
|
|
@ -586,7 +599,6 @@ class TestTemplatePagesGenerator(unittest.TestCase):
|
||||||
self.old_locale = locale.setlocale(locale.LC_ALL)
|
self.old_locale = locale.setlocale(locale.LC_ALL)
|
||||||
locale.setlocale(locale.LC_ALL, str('C'))
|
locale.setlocale(locale.LC_ALL, str('C'))
|
||||||
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
rmtree(self.temp_content)
|
rmtree(self.temp_content)
|
||||||
rmtree(self.temp_output)
|
rmtree(self.temp_output)
|
||||||
|
|
@ -633,21 +645,21 @@ class TestStaticGenerator(unittest.TestCase):
|
||||||
"""Test that StaticGenerator respects STATIC_EXCLUDES.
|
"""Test that StaticGenerator respects STATIC_EXCLUDES.
|
||||||
"""
|
"""
|
||||||
settings = get_settings(STATIC_EXCLUDES=['subdir'],
|
settings = get_settings(STATIC_EXCLUDES=['subdir'],
|
||||||
PATH=self.content_path, STATIC_PATHS=[''])
|
PATH=self.content_path, STATIC_PATHS=[''])
|
||||||
context = settings.copy()
|
context = settings.copy()
|
||||||
context['filenames'] = {}
|
context['filenames'] = {}
|
||||||
|
|
||||||
StaticGenerator(context=context, settings=settings,
|
StaticGenerator(context=context, settings=settings,
|
||||||
path=settings['PATH'], output_path=None,
|
path=settings['PATH'], output_path=None,
|
||||||
theme=settings['THEME']).generate_context()
|
theme=settings['THEME']).generate_context()
|
||||||
|
|
||||||
staticnames = [os.path.basename(c.source_path)
|
staticnames = [os.path.basename(c.source_path)
|
||||||
for c in context['staticfiles']]
|
for c in context['staticfiles']]
|
||||||
|
|
||||||
self.assertNotIn('subdir_fake_image.jpg', staticnames,
|
self.assertNotIn('subdir_fake_image.jpg', staticnames,
|
||||||
"StaticGenerator processed a file in a STATIC_EXCLUDES directory")
|
"StaticGenerator processed a file in a STATIC_EXCLUDES directory")
|
||||||
self.assertIn('fake_image.jpg', staticnames,
|
self.assertIn('fake_image.jpg', staticnames,
|
||||||
"StaticGenerator skipped a file that it should have included")
|
"StaticGenerator skipped a file that it should have included")
|
||||||
|
|
||||||
def test_static_exclude_sources(self):
|
def test_static_exclude_sources(self):
|
||||||
"""Test that StaticGenerator respects STATIC_EXCLUDE_SOURCES.
|
"""Test that StaticGenerator respects STATIC_EXCLUDE_SOURCES.
|
||||||
|
|
@ -655,21 +667,21 @@ class TestStaticGenerator(unittest.TestCase):
|
||||||
# Test STATIC_EXCLUDE_SOURCES=True
|
# Test STATIC_EXCLUDE_SOURCES=True
|
||||||
|
|
||||||
settings = get_settings(STATIC_EXCLUDE_SOURCES=True,
|
settings = get_settings(STATIC_EXCLUDE_SOURCES=True,
|
||||||
PATH=self.content_path, PAGE_PATHS=[''], STATIC_PATHS=[''],
|
PATH=self.content_path, PAGE_PATHS=[''], STATIC_PATHS=[''],
|
||||||
CACHE_CONTENT=False)
|
CACHE_CONTENT=False)
|
||||||
context = settings.copy()
|
context = settings.copy()
|
||||||
context['filenames'] = {}
|
context['filenames'] = {}
|
||||||
|
|
||||||
for generator_class in (PagesGenerator, StaticGenerator):
|
for generator_class in (PagesGenerator, StaticGenerator):
|
||||||
generator_class(context=context, settings=settings,
|
generator_class(context=context, settings=settings,
|
||||||
path=settings['PATH'], output_path=None,
|
path=settings['PATH'], output_path=None,
|
||||||
theme=settings['THEME']).generate_context()
|
theme=settings['THEME']).generate_context()
|
||||||
|
|
||||||
staticnames = [os.path.basename(c.source_path)
|
staticnames = [os.path.basename(c.source_path)
|
||||||
for c in context['staticfiles']]
|
for c in context['staticfiles']]
|
||||||
|
|
||||||
self.assertFalse(any(name.endswith(".md") for name in staticnames),
|
self.assertFalse(any(name.endswith(".md") for name in staticnames),
|
||||||
"STATIC_EXCLUDE_SOURCES=True failed to exclude a markdown file")
|
"STATIC_EXCLUDE_SOURCES=True failed to exclude a markdown file")
|
||||||
|
|
||||||
# Test STATIC_EXCLUDE_SOURCES=False
|
# Test STATIC_EXCLUDE_SOURCES=False
|
||||||
|
|
||||||
|
|
@ -679,12 +691,11 @@ class TestStaticGenerator(unittest.TestCase):
|
||||||
|
|
||||||
for generator_class in (PagesGenerator, StaticGenerator):
|
for generator_class in (PagesGenerator, StaticGenerator):
|
||||||
generator_class(context=context, settings=settings,
|
generator_class(context=context, settings=settings,
|
||||||
path=settings['PATH'], output_path=None,
|
path=settings['PATH'], output_path=None,
|
||||||
theme=settings['THEME']).generate_context()
|
theme=settings['THEME']).generate_context()
|
||||||
|
|
||||||
staticnames = [os.path.basename(c.source_path)
|
staticnames = [os.path.basename(c.source_path)
|
||||||
for c in context['staticfiles']]
|
for c in context['staticfiles']]
|
||||||
|
|
||||||
self.assertTrue(any(name.endswith(".md") for name in staticnames),
|
self.assertTrue(any(name.endswith(".md") for name in staticnames),
|
||||||
"STATIC_EXCLUDE_SOURCES=False failed to include a markdown file")
|
"STATIC_EXCLUDE_SOURCES=False failed to include a markdown file")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ except ImportError:
|
||||||
LXML = False
|
LXML = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@skipIfNoExecutable(['pandoc', '--version'])
|
@skipIfNoExecutable(['pandoc', '--version'])
|
||||||
@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module')
|
@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module')
|
||||||
class TestWordpressXmlImporter(unittest.TestCase):
|
class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
|
|
@ -67,7 +66,8 @@ class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
silent_f2p = mute(True)(fields2pelican)
|
silent_f2p = mute(True)(fields2pelican)
|
||||||
test_post = filter(lambda p: p[0].startswith("Empty Page"), self.posts)
|
test_post = filter(lambda p: p[0].startswith("Empty Page"), self.posts)
|
||||||
with temporary_folder() as temp:
|
with temporary_folder() as temp:
|
||||||
fname = list(silent_f2p(test_post, 'markdown', temp, dirpage=True))[0]
|
fname = list(
|
||||||
|
silent_f2p(test_post, 'markdown', temp, dirpage=True))[0]
|
||||||
self.assertTrue(fname.endswith('pages%sempty.md' % os.path.sep))
|
self.assertTrue(fname.endswith('pages%sempty.md' % os.path.sep))
|
||||||
|
|
||||||
def test_dircat(self):
|
def test_dircat(self):
|
||||||
|
|
@ -75,10 +75,11 @@ class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
test_posts = []
|
test_posts = []
|
||||||
for post in self.posts:
|
for post in self.posts:
|
||||||
# check post kind
|
# check post kind
|
||||||
if len(post[5]) > 0: # Has a category
|
if len(post[5]) > 0: # Has a category
|
||||||
test_posts.append(post)
|
test_posts.append(post)
|
||||||
with temporary_folder() as temp:
|
with temporary_folder() as temp:
|
||||||
fnames = list(silent_f2p(test_posts, 'markdown', temp, dircat=True))
|
fnames = list(
|
||||||
|
silent_f2p(test_posts, 'markdown', temp, dircat=True))
|
||||||
index = 0
|
index = 0
|
||||||
for post in test_posts:
|
for post in test_posts:
|
||||||
name = post[2]
|
name = post[2]
|
||||||
|
|
@ -108,9 +109,12 @@ class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
cust_data.append((title, kind))
|
cust_data.append((title, kind))
|
||||||
self.assertEqual(3, len(cust_data))
|
self.assertEqual(3, len(cust_data))
|
||||||
self.assertEqual(('A custom post in category 4', 'custom1'), cust_data[0])
|
self.assertEqual(
|
||||||
self.assertEqual(('A custom post in category 5', 'custom1'), cust_data[1])
|
('A custom post in category 4', 'custom1'), cust_data[0])
|
||||||
self.assertEqual(('A 2nd custom post type also in category 5', 'custom2'), cust_data[2])
|
self.assertEqual(
|
||||||
|
('A custom post in category 5', 'custom1'), cust_data[1])
|
||||||
|
self.assertEqual(
|
||||||
|
('A 2nd custom post type also in category 5', 'custom2'), cust_data[2])
|
||||||
|
|
||||||
def test_custom_posts_put_in_own_dir(self):
|
def test_custom_posts_put_in_own_dir(self):
|
||||||
silent_f2p = mute(True)(fields2pelican)
|
silent_f2p = mute(True)(fields2pelican)
|
||||||
|
|
@ -122,7 +126,8 @@ class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
test_posts.append(post)
|
test_posts.append(post)
|
||||||
with temporary_folder() as temp:
|
with temporary_folder() as temp:
|
||||||
fnames = list(silent_f2p(test_posts, 'markdown', temp, wp_custpost = True))
|
fnames = list(
|
||||||
|
silent_f2p(test_posts, 'markdown', temp, wp_custpost=True))
|
||||||
index = 0
|
index = 0
|
||||||
for post in test_posts:
|
for post in test_posts:
|
||||||
name = post[2]
|
name = post[2]
|
||||||
|
|
@ -144,7 +149,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
test_posts.append(post)
|
test_posts.append(post)
|
||||||
with temporary_folder() as temp:
|
with temporary_folder() as temp:
|
||||||
fnames = list(silent_f2p(test_posts, 'markdown', temp,
|
fnames = list(silent_f2p(test_posts, 'markdown', temp,
|
||||||
wp_custpost=True, dircat=True))
|
wp_custpost=True, dircat=True))
|
||||||
index = 0
|
index = 0
|
||||||
for post in test_posts:
|
for post in test_posts:
|
||||||
name = post[2]
|
name = post[2]
|
||||||
|
|
@ -157,7 +162,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
def test_wp_custpost_true_dirpage_false(self):
|
def test_wp_custpost_true_dirpage_false(self):
|
||||||
#pages should only be put in their own directory when dirpage = True
|
# pages should only be put in their own directory when dirpage = True
|
||||||
silent_f2p = mute(True)(fields2pelican)
|
silent_f2p = mute(True)(fields2pelican)
|
||||||
test_posts = []
|
test_posts = []
|
||||||
for post in self.custposts:
|
for post in self.custposts:
|
||||||
|
|
@ -166,7 +171,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
test_posts.append(post)
|
test_posts.append(post)
|
||||||
with temporary_folder() as temp:
|
with temporary_folder() as temp:
|
||||||
fnames = list(silent_f2p(test_posts, 'markdown', temp,
|
fnames = list(silent_f2p(test_posts, 'markdown', temp,
|
||||||
wp_custpost=True, dirpage=False))
|
wp_custpost=True, dirpage=False))
|
||||||
index = 0
|
index = 0
|
||||||
for post in test_posts:
|
for post in test_posts:
|
||||||
name = post[2]
|
name = post[2]
|
||||||
|
|
@ -175,7 +180,6 @@ class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
out_name = fnames[index]
|
out_name = fnames[index]
|
||||||
self.assertFalse(out_name.endswith(filename))
|
self.assertFalse(out_name.endswith(filename))
|
||||||
|
|
||||||
|
|
||||||
def test_can_toggle_raw_html_code_parsing(self):
|
def test_can_toggle_raw_html_code_parsing(self):
|
||||||
def r(f):
|
def r(f):
|
||||||
with open(f, encoding='utf-8') as infile:
|
with open(f, encoding='utf-8') as infile:
|
||||||
|
|
@ -184,20 +188,22 @@ class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
|
|
||||||
with temporary_folder() as temp:
|
with temporary_folder() as temp:
|
||||||
|
|
||||||
rst_files = (r(f) for f in silent_f2p(self.posts, 'markdown', temp))
|
rst_files = (r(f)
|
||||||
|
for f in silent_f2p(self.posts, 'markdown', temp))
|
||||||
self.assertTrue(any('<iframe' in rst for rst in rst_files))
|
self.assertTrue(any('<iframe' in rst for rst in rst_files))
|
||||||
rst_files = (r(f) for f in silent_f2p(self.posts, 'markdown', temp,
|
rst_files = (r(f) for f in silent_f2p(self.posts, 'markdown', temp,
|
||||||
strip_raw=True))
|
strip_raw=True))
|
||||||
self.assertFalse(any('<iframe' in rst for rst in rst_files))
|
self.assertFalse(any('<iframe' in rst for rst in rst_files))
|
||||||
# no effect in rst
|
# no effect in rst
|
||||||
rst_files = (r(f) for f in silent_f2p(self.posts, 'rst', temp))
|
rst_files = (r(f) for f in silent_f2p(self.posts, 'rst', temp))
|
||||||
self.assertFalse(any('<iframe' in rst for rst in rst_files))
|
self.assertFalse(any('<iframe' in rst for rst in rst_files))
|
||||||
rst_files = (r(f) for f in silent_f2p(self.posts, 'rst', temp,
|
rst_files = (r(f) for f in silent_f2p(self.posts, 'rst', temp,
|
||||||
strip_raw=True))
|
strip_raw=True))
|
||||||
self.assertFalse(any('<iframe' in rst for rst in rst_files))
|
self.assertFalse(any('<iframe' in rst for rst in rst_files))
|
||||||
|
|
||||||
def test_decode_html_entities_in_titles(self):
|
def test_decode_html_entities_in_titles(self):
|
||||||
test_posts = [post for post in self.posts if post[2] == 'html-entity-test']
|
test_posts = [
|
||||||
|
post for post in self.posts if post[2] == 'html-entity-test']
|
||||||
self.assertEqual(len(test_posts), 1)
|
self.assertEqual(len(test_posts), 1)
|
||||||
|
|
||||||
post = test_posts[0]
|
post = test_posts[0]
|
||||||
|
|
@ -216,14 +222,16 @@ class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
encoded_content = encoded_file.read()
|
encoded_content = encoded_file.read()
|
||||||
with open(WORDPRESS_DECODED_CONTENT_SAMPLE, 'r') as decoded_file:
|
with open(WORDPRESS_DECODED_CONTENT_SAMPLE, 'r') as decoded_file:
|
||||||
decoded_content = decoded_file.read()
|
decoded_content = decoded_file.read()
|
||||||
self.assertEqual(decode_wp_content(encoded_content, br=False), decoded_content)
|
self.assertEqual(
|
||||||
|
decode_wp_content(encoded_content, br=False), decoded_content)
|
||||||
|
|
||||||
def test_preserve_verbatim_formatting(self):
|
def test_preserve_verbatim_formatting(self):
|
||||||
def r(f):
|
def r(f):
|
||||||
with open(f, encoding='utf-8') as infile:
|
with open(f, encoding='utf-8') as infile:
|
||||||
return infile.read()
|
return infile.read()
|
||||||
silent_f2p = mute(True)(fields2pelican)
|
silent_f2p = mute(True)(fields2pelican)
|
||||||
test_post = filter(lambda p: p[0].startswith("Code in List"), self.posts)
|
test_post = filter(
|
||||||
|
lambda p: p[0].startswith("Code in List"), self.posts)
|
||||||
with temporary_folder() as temp:
|
with temporary_folder() as temp:
|
||||||
md = [r(f) for f in silent_f2p(test_post, 'markdown', temp)][0]
|
md = [r(f) for f in silent_f2p(test_post, 'markdown', temp)][0]
|
||||||
self.assertTrue(re.search(r'\s+a = \[1, 2, 3\]', md))
|
self.assertTrue(re.search(r'\s+a = \[1, 2, 3\]', md))
|
||||||
|
|
@ -231,14 +239,16 @@ class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
|
|
||||||
for_line = re.search(r'\s+for i in zip\(a, b\):', md).group(0)
|
for_line = re.search(r'\s+for i in zip\(a, b\):', md).group(0)
|
||||||
print_line = re.search(r'\s+print i', md).group(0)
|
print_line = re.search(r'\s+print i', md).group(0)
|
||||||
self.assertTrue(for_line.rindex('for') < print_line.rindex('print'))
|
self.assertTrue(
|
||||||
|
for_line.rindex('for') < print_line.rindex('print'))
|
||||||
|
|
||||||
def test_code_in_list(self):
|
def test_code_in_list(self):
|
||||||
def r(f):
|
def r(f):
|
||||||
with open(f, encoding='utf-8') as infile:
|
with open(f, encoding='utf-8') as infile:
|
||||||
return infile.read()
|
return infile.read()
|
||||||
silent_f2p = mute(True)(fields2pelican)
|
silent_f2p = mute(True)(fields2pelican)
|
||||||
test_post = filter(lambda p: p[0].startswith("Code in List"), self.posts)
|
test_post = filter(
|
||||||
|
lambda p: p[0].startswith("Code in List"), self.posts)
|
||||||
with temporary_folder() as temp:
|
with temporary_folder() as temp:
|
||||||
md = [r(f) for f in silent_f2p(test_post, 'markdown', temp)][0]
|
md = [r(f) for f in silent_f2p(test_post, 'markdown', temp)][0]
|
||||||
sample_line = re.search(r'- This is a code sample', md).group(0)
|
sample_line = re.search(r'- This is a code sample', md).group(0)
|
||||||
|
|
@ -247,6 +257,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestBuildHeader(unittest.TestCase):
|
class TestBuildHeader(unittest.TestCase):
|
||||||
|
|
||||||
def test_build_header(self):
|
def test_build_header(self):
|
||||||
header = build_header('test', None, None, None, None, None)
|
header = build_header('test', None, None, None, None, None)
|
||||||
self.assertEqual(header, 'test\n####\n\n')
|
self.assertEqual(header, 'test\n####\n\n')
|
||||||
|
|
@ -285,31 +296,31 @@ class TestBuildHeader(unittest.TestCase):
|
||||||
self.assertEqual(build_header(*header_data), expected_docutils)
|
self.assertEqual(build_header(*header_data), expected_docutils)
|
||||||
self.assertEqual(build_markdown_header(*header_data), expected_md)
|
self.assertEqual(build_markdown_header(*header_data), expected_md)
|
||||||
|
|
||||||
|
|
||||||
def test_build_header_with_east_asian_characters(self):
|
def test_build_header_with_east_asian_characters(self):
|
||||||
header = build_header('これは広い幅の文字だけで構成されたタイトルです',
|
header = build_header('これは広い幅の文字だけで構成されたタイトルです',
|
||||||
None, None, None, None, None)
|
None, None, None, None, None)
|
||||||
|
|
||||||
self.assertEqual(header,
|
self.assertEqual(header,
|
||||||
'これは広い幅の文字だけで構成されたタイトルです\n' +
|
'これは広い幅の文字だけで構成されたタイトルです\n' +
|
||||||
'##############################################\n\n')
|
'##############################################\n\n')
|
||||||
|
|
||||||
def test_galleries_added_to_header(self):
|
def test_galleries_added_to_header(self):
|
||||||
header = build_header('test', None, None, None, None,
|
header = build_header('test', None, None, None, None,
|
||||||
None, attachments=['output/test1', 'output/test2'])
|
None, attachments=['output/test1', 'output/test2'])
|
||||||
self.assertEqual(header, 'test\n####\n' + ':attachments: output/test1, '
|
self.assertEqual(header, 'test\n####\n' + ':attachments: output/test1, '
|
||||||
+ 'output/test2\n\n')
|
+ 'output/test2\n\n')
|
||||||
|
|
||||||
def test_galleries_added_to_markdown_header(self):
|
def test_galleries_added_to_markdown_header(self):
|
||||||
header = build_markdown_header('test', None, None, None, None, None,
|
header = build_markdown_header('test', None, None, None, None, None,
|
||||||
attachments=['output/test1', 'output/test2'])
|
attachments=['output/test1', 'output/test2'])
|
||||||
self.assertEqual(header, 'Title: test\n' + 'Attachments: output/test1, '
|
self.assertEqual(header, 'Title: test\n' + 'Attachments: output/test1, '
|
||||||
+ 'output/test2\n\n')
|
+ 'output/test2\n\n')
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module')
|
@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module')
|
||||||
@unittest.skipUnless(LXML, 'Needs lxml module')
|
@unittest.skipUnless(LXML, 'Needs lxml module')
|
||||||
class TestWordpressXMLAttachements(unittest.TestCase):
|
class TestWordpressXMLAttachements(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.old_locale = locale.setlocale(locale.LC_ALL)
|
self.old_locale = locale.setlocale(locale.LC_ALL)
|
||||||
locale.setlocale(locale.LC_ALL, str('C'))
|
locale.setlocale(locale.LC_ALL, str('C'))
|
||||||
|
|
@ -326,14 +337,19 @@ class TestWordpressXMLAttachements(unittest.TestCase):
|
||||||
self.assertTrue(self.attachments)
|
self.assertTrue(self.attachments)
|
||||||
for post in self.attachments.keys():
|
for post in self.attachments.keys():
|
||||||
if post is None:
|
if post is None:
|
||||||
self.assertTrue(self.attachments[post][0] == 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Pelican_lakes_entrance02.jpg/240px-Pelican_lakes_entrance02.jpg')
|
self.assertTrue(self.attachments[post][
|
||||||
|
0] == 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Pelican_lakes_entrance02.jpg/240px-Pelican_lakes_entrance02.jpg')
|
||||||
elif post == 'with-excerpt':
|
elif post == 'with-excerpt':
|
||||||
self.assertTrue(self.attachments[post][0] == 'http://thisurlisinvalid.notarealdomain/not_an_image.jpg')
|
self.assertTrue(self.attachments[post][
|
||||||
self.assertTrue(self.attachments[post][1] == 'http://en.wikipedia.org/wiki/File:Pelikan_Walvis_Bay.jpg')
|
0] == 'http://thisurlisinvalid.notarealdomain/not_an_image.jpg')
|
||||||
|
self.assertTrue(self.attachments[post][
|
||||||
|
1] == 'http://en.wikipedia.org/wiki/File:Pelikan_Walvis_Bay.jpg')
|
||||||
elif post == 'with-tags':
|
elif post == 'with-tags':
|
||||||
self.assertTrue(self.attachments[post][0] == 'http://thisurlisinvalid.notarealdomain')
|
self.assertTrue(
|
||||||
|
self.attachments[post][0] == 'http://thisurlisinvalid.notarealdomain')
|
||||||
else:
|
else:
|
||||||
self.fail('all attachments should match to a filename or None, {}'.format(post))
|
self.fail(
|
||||||
|
'all attachments should match to a filename or None, {}'.format(post))
|
||||||
|
|
||||||
def test_download_attachments(self):
|
def test_download_attachments(self):
|
||||||
real_file = os.path.join(CUR_DIR, 'content/article.rst')
|
real_file = os.path.join(CUR_DIR, 'content/article.rst')
|
||||||
|
|
@ -344,4 +360,5 @@ class TestWordpressXMLAttachements(unittest.TestCase):
|
||||||
locations = list(silent_da(temp, [good_url, bad_url]))
|
locations = list(silent_da(temp, [good_url, bad_url]))
|
||||||
self.assertEqual(1, len(locations))
|
self.assertEqual(1, len(locations))
|
||||||
directory = locations[0]
|
directory = locations[0]
|
||||||
self.assertTrue(directory.endswith(os.path.join('content', 'article.rst')), directory)
|
self.assertTrue(
|
||||||
|
directory.endswith(os.path.join('content', 'article.rst')), directory)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,9 @@ from jinja2.utils import generate_lorem_ipsum
|
||||||
TEST_CONTENT = str(generate_lorem_ipsum(n=1))
|
TEST_CONTENT = str(generate_lorem_ipsum(n=1))
|
||||||
TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False)
|
TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False)
|
||||||
|
|
||||||
|
|
||||||
class TestPage(unittest.TestCase):
|
class TestPage(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestPage, self).setUp()
|
super(TestPage, self).setUp()
|
||||||
self.old_locale = locale.setlocale(locale.LC_ALL)
|
self.old_locale = locale.setlocale(locale.LC_ALL)
|
||||||
|
|
@ -49,7 +51,8 @@ class TestPage(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.page_kwargs['metadata']['author'] = Author('Blogger', settings)
|
self.page_kwargs['metadata']['author'] = Author('Blogger', settings)
|
||||||
object_list = [Article(**self.page_kwargs), Article(**self.page_kwargs)]
|
object_list = [
|
||||||
|
Article(**self.page_kwargs), Article(**self.page_kwargs)]
|
||||||
paginator = Paginator('foobar.foo', object_list, settings)
|
paginator = Paginator('foobar.foo', object_list, settings)
|
||||||
page = paginator.page(1)
|
page = paginator.page(1)
|
||||||
self.assertEqual(page.save_as, 'foobar.foo')
|
self.assertEqual(page.save_as, 'foobar.foo')
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from pelican.tests.support import LoggedTestCase, mute, locale_available, unitte
|
||||||
|
|
||||||
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
SAMPLES_PATH = os.path.abspath(os.path.join(
|
SAMPLES_PATH = os.path.abspath(os.path.join(
|
||||||
CURRENT_DIR, os.pardir, os.pardir, 'samples'))
|
CURRENT_DIR, os.pardir, os.pardir, 'samples'))
|
||||||
OUTPUT_PATH = os.path.abspath(os.path.join(CURRENT_DIR, 'output'))
|
OUTPUT_PATH = os.path.abspath(os.path.join(CURRENT_DIR, 'output'))
|
||||||
|
|
||||||
INPUT_PATH = os.path.join(SAMPLES_PATH, "content")
|
INPUT_PATH = os.path.join(SAMPLES_PATH, "content")
|
||||||
|
|
@ -27,13 +27,13 @@ SAMPLE_FR_CONFIG = os.path.join(SAMPLES_PATH, "pelican.conf_FR.py")
|
||||||
|
|
||||||
def recursiveDiff(dcmp):
|
def recursiveDiff(dcmp):
|
||||||
diff = {
|
diff = {
|
||||||
'diff_files': [os.path.join(dcmp.right, f)
|
'diff_files': [os.path.join(dcmp.right, f)
|
||||||
for f in dcmp.diff_files],
|
for f in dcmp.diff_files],
|
||||||
'left_only': [os.path.join(dcmp.right, f)
|
'left_only': [os.path.join(dcmp.right, f)
|
||||||
for f in dcmp.left_only],
|
for f in dcmp.left_only],
|
||||||
'right_only': [os.path.join(dcmp.right, f)
|
'right_only': [os.path.join(dcmp.right, f)
|
||||||
for f in dcmp.right_only],
|
for f in dcmp.right_only],
|
||||||
}
|
}
|
||||||
for sub_dcmp in dcmp.subdirs.values():
|
for sub_dcmp in dcmp.subdirs.values():
|
||||||
for k, v in recursiveDiff(sub_dcmp).items():
|
for k, v in recursiveDiff(sub_dcmp).items():
|
||||||
diff[k] += v
|
diff[k] += v
|
||||||
|
|
@ -60,9 +60,11 @@ class TestPelican(LoggedTestCase):
|
||||||
|
|
||||||
def assertDirsEqual(self, left_path, right_path):
|
def assertDirsEqual(self, left_path, right_path):
|
||||||
out, err = subprocess.Popen(
|
out, err = subprocess.Popen(
|
||||||
['git', 'diff', '--no-ext-diff', '--exit-code', '-w', left_path, right_path],
|
['git', 'diff', '--no-ext-diff', '--exit-code',
|
||||||
|
'-w', left_path, right_path],
|
||||||
env={str('PAGER'): str('')}, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
env={str('PAGER'): str('')}, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
).communicate()
|
).communicate()
|
||||||
|
|
||||||
def ignorable_git_crlf_errors(line):
|
def ignorable_git_crlf_errors(line):
|
||||||
# Work around for running tests on Windows
|
# Work around for running tests on Windows
|
||||||
for msg in [
|
for msg in [
|
||||||
|
|
@ -86,9 +88,9 @@ class TestPelican(LoggedTestCase):
|
||||||
generator_classes = pelican.get_generator_classes()
|
generator_classes = pelican.get_generator_classes()
|
||||||
|
|
||||||
self.assertTrue(generator_classes[-1] is StaticGenerator,
|
self.assertTrue(generator_classes[-1] is StaticGenerator,
|
||||||
"StaticGenerator must be the last generator, but it isn't!")
|
"StaticGenerator must be the last generator, but it isn't!")
|
||||||
self.assertIsInstance(generator_classes, collections.Sequence,
|
self.assertIsInstance(generator_classes, collections.Sequence,
|
||||||
"get_generator_classes() must return a Sequence to preserve order")
|
"get_generator_classes() must return a Sequence to preserve order")
|
||||||
|
|
||||||
def test_basic_generation_works(self):
|
def test_basic_generation_works(self):
|
||||||
# when running pelican without settings, it should pick up the default
|
# when running pelican without settings, it should pick up the default
|
||||||
|
|
@ -98,10 +100,11 @@ class TestPelican(LoggedTestCase):
|
||||||
'OUTPUT_PATH': self.temp_path,
|
'OUTPUT_PATH': self.temp_path,
|
||||||
'CACHE_PATH': self.temp_cache,
|
'CACHE_PATH': self.temp_cache,
|
||||||
'LOCALE': locale.normalize('en_US'),
|
'LOCALE': locale.normalize('en_US'),
|
||||||
})
|
})
|
||||||
pelican = Pelican(settings=settings)
|
pelican = Pelican(settings=settings)
|
||||||
mute(True)(pelican.run)()
|
mute(True)(pelican.run)()
|
||||||
self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, 'basic'))
|
self.assertDirsEqual(
|
||||||
|
self.temp_path, os.path.join(OUTPUT_PATH, 'basic'))
|
||||||
self.assertLogCountEqual(
|
self.assertLogCountEqual(
|
||||||
count=3,
|
count=3,
|
||||||
msg="Unable to find.*skipping url replacement",
|
msg="Unable to find.*skipping url replacement",
|
||||||
|
|
@ -114,10 +117,11 @@ class TestPelican(LoggedTestCase):
|
||||||
'OUTPUT_PATH': self.temp_path,
|
'OUTPUT_PATH': self.temp_path,
|
||||||
'CACHE_PATH': self.temp_cache,
|
'CACHE_PATH': self.temp_cache,
|
||||||
'LOCALE': locale.normalize('en_US'),
|
'LOCALE': locale.normalize('en_US'),
|
||||||
})
|
})
|
||||||
pelican = Pelican(settings=settings)
|
pelican = Pelican(settings=settings)
|
||||||
mute(True)(pelican.run)()
|
mute(True)(pelican.run)()
|
||||||
self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, 'custom'))
|
self.assertDirsEqual(
|
||||||
|
self.temp_path, os.path.join(OUTPUT_PATH, 'custom'))
|
||||||
|
|
||||||
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or
|
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or
|
||||||
locale_available('French'), 'French locale needed')
|
locale_available('French'), 'French locale needed')
|
||||||
|
|
@ -133,10 +137,11 @@ class TestPelican(LoggedTestCase):
|
||||||
'OUTPUT_PATH': self.temp_path,
|
'OUTPUT_PATH': self.temp_path,
|
||||||
'CACHE_PATH': self.temp_cache,
|
'CACHE_PATH': self.temp_cache,
|
||||||
'LOCALE': our_locale,
|
'LOCALE': our_locale,
|
||||||
})
|
})
|
||||||
pelican = Pelican(settings=settings)
|
pelican = Pelican(settings=settings)
|
||||||
mute(True)(pelican.run)()
|
mute(True)(pelican.run)()
|
||||||
self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, 'custom_locale'))
|
self.assertDirsEqual(
|
||||||
|
self.temp_path, os.path.join(OUTPUT_PATH, 'custom_locale'))
|
||||||
|
|
||||||
def test_theme_static_paths_copy(self):
|
def test_theme_static_paths_copy(self):
|
||||||
# the same thing with a specified set of settings should work
|
# the same thing with a specified set of settings should work
|
||||||
|
|
@ -147,7 +152,7 @@ class TestPelican(LoggedTestCase):
|
||||||
'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'very'),
|
'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'very'),
|
||||||
os.path.join(SAMPLES_PATH, 'kinda'),
|
os.path.join(SAMPLES_PATH, 'kinda'),
|
||||||
os.path.join(SAMPLES_PATH, 'theme_standard')]
|
os.path.join(SAMPLES_PATH, 'theme_standard')]
|
||||||
})
|
})
|
||||||
pelican = Pelican(settings=settings)
|
pelican = Pelican(settings=settings)
|
||||||
mute(True)(pelican.run)()
|
mute(True)(pelican.run)()
|
||||||
theme_output = os.path.join(self.temp_path, 'theme')
|
theme_output = os.path.join(self.temp_path, 'theme')
|
||||||
|
|
@ -166,7 +171,7 @@ class TestPelican(LoggedTestCase):
|
||||||
'OUTPUT_PATH': self.temp_path,
|
'OUTPUT_PATH': self.temp_path,
|
||||||
'CACHE_PATH': self.temp_cache,
|
'CACHE_PATH': self.temp_cache,
|
||||||
'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'theme_standard')]
|
'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'theme_standard')]
|
||||||
})
|
})
|
||||||
|
|
||||||
pelican = Pelican(settings=settings)
|
pelican = Pelican(settings=settings)
|
||||||
mute(True)(pelican.run)()
|
mute(True)(pelican.run)()
|
||||||
|
|
@ -184,9 +189,9 @@ class TestPelican(LoggedTestCase):
|
||||||
'WRITE_SELECTED': [
|
'WRITE_SELECTED': [
|
||||||
os.path.join(self.temp_path, 'oh-yeah.html'),
|
os.path.join(self.temp_path, 'oh-yeah.html'),
|
||||||
os.path.join(self.temp_path, 'categories.html'),
|
os.path.join(self.temp_path, 'categories.html'),
|
||||||
],
|
],
|
||||||
'LOCALE': locale.normalize('en_US'),
|
'LOCALE': locale.normalize('en_US'),
|
||||||
})
|
})
|
||||||
pelican = Pelican(settings=settings)
|
pelican = Pelican(settings=settings)
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
orig_level = logger.getEffectiveLevel()
|
orig_level = logger.getEffectiveLevel()
|
||||||
|
|
|
||||||
|
|
@ -32,19 +32,21 @@ class ReaderTest(unittest.TestCase):
|
||||||
'Expected %s to have value %s, but was %s' % (key, value, real_value))
|
'Expected %s to have value %s, but was %s' % (key, value, real_value))
|
||||||
else:
|
else:
|
||||||
self.fail(
|
self.fail(
|
||||||
'Expected %s to have value %s, but was not in Dict' % (key, value))
|
'Expected %s to have value %s, but was not in Dict' % (key, value))
|
||||||
|
|
||||||
|
|
||||||
class TestAssertDictHasSubset(ReaderTest):
|
class TestAssertDictHasSubset(ReaderTest):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.dictionary = {
|
self.dictionary = {
|
||||||
'key-a' : 'val-a',
|
'key-a': 'val-a',
|
||||||
'key-b' : 'val-b'}
|
'key-b': 'val-b'}
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.dictionary = None
|
self.dictionary = None
|
||||||
|
|
||||||
def test_subset(self):
|
def test_subset(self):
|
||||||
self.assertDictHasSubset(self.dictionary, {'key-a':'val-a'})
|
self.assertDictHasSubset(self.dictionary, {'key-a': 'val-a'})
|
||||||
|
|
||||||
def test_equal(self):
|
def test_equal(self):
|
||||||
self.assertDictHasSubset(self.dictionary, self.dictionary)
|
self.assertDictHasSubset(self.dictionary, self.dictionary)
|
||||||
|
|
@ -54,18 +56,19 @@ class TestAssertDictHasSubset(ReaderTest):
|
||||||
AssertionError,
|
AssertionError,
|
||||||
'Expected.*key-c.*to have value.*val-c.*but was not in Dict',
|
'Expected.*key-c.*to have value.*val-c.*but was not in Dict',
|
||||||
self.assertDictHasSubset,
|
self.assertDictHasSubset,
|
||||||
self.dictionary,
|
self.dictionary,
|
||||||
{'key-c':'val-c'}
|
{'key-c': 'val-c'}
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_fail_wrong_val(self):
|
def test_fail_wrong_val(self):
|
||||||
self.assertRaisesRegexp(
|
self.assertRaisesRegexp(
|
||||||
AssertionError,
|
AssertionError,
|
||||||
'Expected .*key-a.* to have value .*val-b.* but was .*val-a.*',
|
'Expected .*key-a.* to have value .*val-b.* but was .*val-a.*',
|
||||||
self.assertDictHasSubset,
|
self.assertDictHasSubset,
|
||||||
self.dictionary,
|
self.dictionary,
|
||||||
{'key-a':'val-b'}
|
{'key-a': 'val-b'}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DefaultReaderTest(ReaderTest):
|
class DefaultReaderTest(ReaderTest):
|
||||||
|
|
||||||
|
|
@ -153,17 +156,17 @@ class RstReaderTest(ReaderTest):
|
||||||
'(?P<date>\d{4}-\d{2}-\d{2})'
|
'(?P<date>\d{4}-\d{2}-\d{2})'
|
||||||
'_(?P<Slug>.*)'
|
'_(?P<Slug>.*)'
|
||||||
'#(?P<MyMeta>.*)-(?P<author>.*)'
|
'#(?P<MyMeta>.*)-(?P<author>.*)'
|
||||||
),
|
),
|
||||||
EXTRA_PATH_METADATA={
|
EXTRA_PATH_METADATA={
|
||||||
input_with_metadata: {
|
input_with_metadata: {
|
||||||
'key-1a': 'value-1a',
|
'key-1a': 'value-1a',
|
||||||
'key-1b': 'value-1b'
|
'key-1b': 'value-1b'
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
)
|
||||||
expected_metadata = {
|
expected_metadata = {
|
||||||
'category': 'yeah',
|
'category': 'yeah',
|
||||||
'author' : 'Alexis Métaireau',
|
'author': 'Alexis Métaireau',
|
||||||
'title': 'Rst with filename metadata',
|
'title': 'Rst with filename metadata',
|
||||||
'date': SafeDatetime(2012, 11, 29),
|
'date': SafeDatetime(2012, 11, 29),
|
||||||
'slug': 'rst_w_filename_meta',
|
'slug': 'rst_w_filename_meta',
|
||||||
|
|
@ -180,21 +183,21 @@ class RstReaderTest(ReaderTest):
|
||||||
EXTRA_PATH_METADATA={
|
EXTRA_PATH_METADATA={
|
||||||
input_file_path_without_metadata: {
|
input_file_path_without_metadata: {
|
||||||
'author': 'Charlès Overwrite'}
|
'author': 'Charlès Overwrite'}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expected_without_metadata = {
|
expected_without_metadata = {
|
||||||
'category' : 'misc',
|
'category': 'misc',
|
||||||
'author' : 'Charlès Overwrite',
|
'author': 'Charlès Overwrite',
|
||||||
'title' : 'Article title',
|
'title': 'Article title',
|
||||||
'reader' : 'rst',
|
'reader': 'rst',
|
||||||
}
|
}
|
||||||
self.assertDictHasSubset(
|
self.assertDictHasSubset(
|
||||||
page_without_metadata.metadata,
|
page_without_metadata.metadata,
|
||||||
expected_without_metadata)
|
expected_without_metadata)
|
||||||
|
|
||||||
def test_article_extra_path_metadata_dont_overwrite(self):
|
def test_article_extra_path_metadata_dont_overwrite(self):
|
||||||
#EXTRA_PATH_METADATA['author'] should get ignored
|
# EXTRA_PATH_METADATA['author'] should get ignored
|
||||||
#since we don't overwrite already set values
|
# since we don't overwrite already set values
|
||||||
input_file_path = '2012-11-29_rst_w_filename_meta#foo-bar.rst'
|
input_file_path = '2012-11-29_rst_w_filename_meta#foo-bar.rst'
|
||||||
page = self.read_file(
|
page = self.read_file(
|
||||||
path=input_file_path,
|
path=input_file_path,
|
||||||
|
|
@ -206,11 +209,11 @@ class RstReaderTest(ReaderTest):
|
||||||
input_file_path: {
|
input_file_path: {
|
||||||
'author': 'Charlès Overwrite',
|
'author': 'Charlès Overwrite',
|
||||||
'key-1b': 'value-1b'}
|
'key-1b': 'value-1b'}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expected = {
|
expected = {
|
||||||
'category': 'yeah',
|
'category': 'yeah',
|
||||||
'author' : 'Alexis Métaireau',
|
'author': 'Alexis Métaireau',
|
||||||
'title': 'Rst with filename metadata',
|
'title': 'Rst with filename metadata',
|
||||||
'date': SafeDatetime(2012, 11, 29),
|
'date': SafeDatetime(2012, 11, 29),
|
||||||
'slug': 'rst_w_filename_meta',
|
'slug': 'rst_w_filename_meta',
|
||||||
|
|
@ -273,7 +276,7 @@ class RstReaderTest(ReaderTest):
|
||||||
# typogrify should be able to ignore user specified tags,
|
# typogrify should be able to ignore user specified tags,
|
||||||
# but tries to be clever with widont extension
|
# but tries to be clever with widont extension
|
||||||
page = self.read_file(path='article.rst', TYPOGRIFY=True,
|
page = self.read_file(path='article.rst', TYPOGRIFY=True,
|
||||||
TYPOGRIFY_IGNORE_TAGS = ['p'])
|
TYPOGRIFY_IGNORE_TAGS=['p'])
|
||||||
expected = ('<p>THIS is some content. With some stuff to '
|
expected = ('<p>THIS is some content. With some stuff to '
|
||||||
'"typogrify"...</p>\n<p>Now with added '
|
'"typogrify"...</p>\n<p>Now with added '
|
||||||
'support for <abbr title="three letter acronym">'
|
'support for <abbr title="three letter acronym">'
|
||||||
|
|
@ -284,7 +287,7 @@ class RstReaderTest(ReaderTest):
|
||||||
# typogrify should ignore code blocks by default because
|
# typogrify should ignore code blocks by default because
|
||||||
# code blocks are composed inside the pre tag
|
# code blocks are composed inside the pre tag
|
||||||
page = self.read_file(path='article_with_code_block.rst',
|
page = self.read_file(path='article_with_code_block.rst',
|
||||||
TYPOGRIFY=True)
|
TYPOGRIFY=True)
|
||||||
|
|
||||||
expected = ('<p>An article with some code</p>\n'
|
expected = ('<p>An article with some code</p>\n'
|
||||||
'<div class="highlight"><pre><span class="n">x</span>'
|
'<div class="highlight"><pre><span class="n">x</span>'
|
||||||
|
|
@ -298,7 +301,7 @@ class RstReaderTest(ReaderTest):
|
||||||
|
|
||||||
# instruct typogrify to also ignore blockquotes
|
# instruct typogrify to also ignore blockquotes
|
||||||
page = self.read_file(path='article_with_code_block.rst',
|
page = self.read_file(path='article_with_code_block.rst',
|
||||||
TYPOGRIFY=True, TYPOGRIFY_IGNORE_TAGS = ['blockquote'])
|
TYPOGRIFY=True, TYPOGRIFY_IGNORE_TAGS=['blockquote'])
|
||||||
|
|
||||||
expected = ('<p>An article with some code</p>\n'
|
expected = ('<p>An article with some code</p>\n'
|
||||||
'<div class="highlight"><pre><span class="n">x</span>'
|
'<div class="highlight"><pre><span class="n">x</span>'
|
||||||
|
|
@ -339,6 +342,7 @@ class RstReaderTest(ReaderTest):
|
||||||
|
|
||||||
self.assertDictHasSubset(page.metadata, expected)
|
self.assertDictHasSubset(page.metadata, expected)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(readers.Markdown, "markdown isn't installed")
|
@unittest.skipUnless(readers.Markdown, "markdown isn't installed")
|
||||||
class MdReaderTest(ReaderTest):
|
class MdReaderTest(ReaderTest):
|
||||||
|
|
||||||
|
|
@ -502,6 +506,7 @@ class MdReaderTest(ReaderTest):
|
||||||
|
|
||||||
|
|
||||||
class HTMLReaderTest(ReaderTest):
|
class HTMLReaderTest(ReaderTest):
|
||||||
|
|
||||||
def test_article_with_comments(self):
|
def test_article_with_comments(self):
|
||||||
page = self.read_file(path='article_with_comments.html')
|
page = self.read_file(path='article_with_comments.html')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,10 @@ except ImportError:
|
||||||
Mock = False
|
Mock = False
|
||||||
from pelican.tests.support import unittest
|
from pelican.tests.support import unittest
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(Mock, 'Needs Mock module')
|
@unittest.skipUnless(Mock, 'Needs Mock module')
|
||||||
class Test_abbr_role(unittest.TestCase):
|
class Test_abbr_role(unittest.TestCase):
|
||||||
|
|
||||||
def call_it(self, text):
|
def call_it(self, text):
|
||||||
from pelican.rstdirectives import abbr_role
|
from pelican.rstdirectives import abbr_role
|
||||||
rawtext = text
|
rawtext = text
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,12 @@ from pelican.tests.support import unittest
|
||||||
|
|
||||||
|
|
||||||
class TestSettingsConfiguration(unittest.TestCase):
|
class TestSettingsConfiguration(unittest.TestCase):
|
||||||
|
|
||||||
"""Provided a file, it should read it, replace the default values,
|
"""Provided a file, it should read it, replace the default values,
|
||||||
append new values to the settings (if any), and apply basic settings
|
append new values to the settings (if any), and apply basic settings
|
||||||
optimizations.
|
optimizations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.old_locale = locale.setlocale(locale.LC_ALL)
|
self.old_locale = locale.setlocale(locale.LC_ALL)
|
||||||
locale.setlocale(locale.LC_ALL, str('C'))
|
locale.setlocale(locale.LC_ALL, str('C'))
|
||||||
|
|
@ -29,12 +31,12 @@ class TestSettingsConfiguration(unittest.TestCase):
|
||||||
def test_overwrite_existing_settings(self):
|
def test_overwrite_existing_settings(self):
|
||||||
self.assertEqual(self.settings.get('SITENAME'), "Alexis' log")
|
self.assertEqual(self.settings.get('SITENAME'), "Alexis' log")
|
||||||
self.assertEqual(self.settings.get('SITEURL'),
|
self.assertEqual(self.settings.get('SITEURL'),
|
||||||
'http://blog.notmyidea.org')
|
'http://blog.notmyidea.org')
|
||||||
|
|
||||||
def test_keep_default_settings(self):
|
def test_keep_default_settings(self):
|
||||||
# Keep default settings if not defined.
|
# Keep default settings if not defined.
|
||||||
self.assertEqual(self.settings.get('DEFAULT_CATEGORY'),
|
self.assertEqual(self.settings.get('DEFAULT_CATEGORY'),
|
||||||
DEFAULT_CONFIG['DEFAULT_CATEGORY'])
|
DEFAULT_CONFIG['DEFAULT_CATEGORY'])
|
||||||
|
|
||||||
def test_dont_copy_small_keys(self):
|
def test_dont_copy_small_keys(self):
|
||||||
# Do not copy keys not in caps.
|
# Do not copy keys not in caps.
|
||||||
|
|
@ -70,27 +72,27 @@ class TestSettingsConfiguration(unittest.TestCase):
|
||||||
def test_static_path_settings_safety(self):
|
def test_static_path_settings_safety(self):
|
||||||
# Disallow static paths from being strings
|
# Disallow static paths from being strings
|
||||||
settings = {'STATIC_PATHS': 'foo/bar',
|
settings = {'STATIC_PATHS': 'foo/bar',
|
||||||
'THEME_STATIC_PATHS': 'bar/baz',
|
'THEME_STATIC_PATHS': 'bar/baz',
|
||||||
# These 4 settings are required to run configure_settings
|
# These 4 settings are required to run configure_settings
|
||||||
'PATH': '.',
|
'PATH': '.',
|
||||||
'THEME': DEFAULT_THEME,
|
'THEME': DEFAULT_THEME,
|
||||||
'SITEURL': 'http://blog.notmyidea.org/',
|
'SITEURL': 'http://blog.notmyidea.org/',
|
||||||
'LOCALE': '',
|
'LOCALE': '',
|
||||||
}
|
}
|
||||||
configure_settings(settings)
|
configure_settings(settings)
|
||||||
self.assertEqual(settings['STATIC_PATHS'],
|
self.assertEqual(settings['STATIC_PATHS'],
|
||||||
DEFAULT_CONFIG['STATIC_PATHS'])
|
DEFAULT_CONFIG['STATIC_PATHS'])
|
||||||
self.assertEqual(settings['THEME_STATIC_PATHS'],
|
self.assertEqual(settings['THEME_STATIC_PATHS'],
|
||||||
DEFAULT_CONFIG['THEME_STATIC_PATHS'])
|
DEFAULT_CONFIG['THEME_STATIC_PATHS'])
|
||||||
|
|
||||||
def test_configure_settings(self):
|
def test_configure_settings(self):
|
||||||
# Manipulations to settings should be applied correctly.
|
# Manipulations to settings should be applied correctly.
|
||||||
settings = {
|
settings = {
|
||||||
'SITEURL': 'http://blog.notmyidea.org/',
|
'SITEURL': 'http://blog.notmyidea.org/',
|
||||||
'LOCALE': '',
|
'LOCALE': '',
|
||||||
'PATH': os.curdir,
|
'PATH': os.curdir,
|
||||||
'THEME': DEFAULT_THEME,
|
'THEME': DEFAULT_THEME,
|
||||||
}
|
}
|
||||||
configure_settings(settings)
|
configure_settings(settings)
|
||||||
|
|
||||||
# SITEURL should not have a trailing slash
|
# SITEURL should not have a trailing slash
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ from __future__ import unicode_literals
|
||||||
from pelican.urlwrappers import URLWrapper, Tag, Category
|
from pelican.urlwrappers import URLWrapper, Tag, Category
|
||||||
from pelican.tests.support import unittest
|
from pelican.tests.support import unittest
|
||||||
|
|
||||||
|
|
||||||
class TestURLWrapper(unittest.TestCase):
|
class TestURLWrapper(unittest.TestCase):
|
||||||
|
|
||||||
def test_ordering(self):
|
def test_ordering(self):
|
||||||
# URLWrappers are sorted by name
|
# URLWrappers are sorted by name
|
||||||
wrapper_a = URLWrapper(name='first', settings={})
|
wrapper_a = URLWrapper(name='first', settings={})
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ class TestUtils(LoggedTestCase):
|
||||||
'2012-11-22T22:11:10Z': date_hour_sec_z,
|
'2012-11-22T22:11:10Z': date_hour_sec_z,
|
||||||
'2012-11-22T22:11:10-0500': date_hour_sec_est,
|
'2012-11-22T22:11:10-0500': date_hour_sec_est,
|
||||||
'2012-11-22T22:11:10.123Z': date_hour_sec_frac_z,
|
'2012-11-22T22:11:10.123Z': date_hour_sec_frac_z,
|
||||||
}
|
}
|
||||||
|
|
||||||
# examples from http://www.w3.org/TR/NOTE-datetime
|
# examples from http://www.w3.org/TR/NOTE-datetime
|
||||||
iso_8601_date = utils.SafeDatetime(year=1997, month=7, day=16)
|
iso_8601_date = utils.SafeDatetime(year=1997, month=7, day=16)
|
||||||
|
|
@ -95,7 +95,6 @@ class TestUtils(LoggedTestCase):
|
||||||
# invalid ones
|
# invalid ones
|
||||||
invalid_dates = ['2010-110-12', 'yay']
|
invalid_dates = ['2010-110-12', 'yay']
|
||||||
|
|
||||||
|
|
||||||
for value, expected in dates.items():
|
for value, expected in dates.items():
|
||||||
self.assertEqual(utils.get_date(value), expected, value)
|
self.assertEqual(utils.get_date(value), expected, value)
|
||||||
|
|
||||||
|
|
@ -290,7 +289,8 @@ class TestUtils(LoggedTestCase):
|
||||||
self.assertEqual(utils.strftime(d, '%d/%m/%Y'), '29/08/2012')
|
self.assertEqual(utils.strftime(d, '%d/%m/%Y'), '29/08/2012')
|
||||||
|
|
||||||
# RFC 3339
|
# RFC 3339
|
||||||
self.assertEqual(utils.strftime(d, '%Y-%m-%dT%H:%M:%SZ'),'2012-08-29T00:00:00Z')
|
self.assertEqual(
|
||||||
|
utils.strftime(d, '%Y-%m-%dT%H:%M:%SZ'), '2012-08-29T00:00:00Z')
|
||||||
|
|
||||||
# % escaped
|
# % escaped
|
||||||
self.assertEqual(utils.strftime(d, '%d%%%m%%%y'), '29%08%12')
|
self.assertEqual(utils.strftime(d, '%d%%%m%%%y'), '29%08%12')
|
||||||
|
|
@ -316,7 +316,6 @@ class TestUtils(LoggedTestCase):
|
||||||
d = utils.SafeDatetime(2012, 8, 9)
|
d = utils.SafeDatetime(2012, 8, 9)
|
||||||
self.assertEqual(utils.strftime(d, '%-d/%-m/%y'), '9/8/12')
|
self.assertEqual(utils.strftime(d, '%-d/%-m/%y'), '9/8/12')
|
||||||
|
|
||||||
|
|
||||||
# test the output of utils.strftime in a different locale
|
# test the output of utils.strftime in a different locale
|
||||||
# Turkish locale
|
# Turkish locale
|
||||||
@unittest.skipUnless(locale_available('tr_TR.UTF-8') or
|
@unittest.skipUnless(locale_available('tr_TR.UTF-8') or
|
||||||
|
|
@ -340,16 +339,15 @@ class TestUtils(LoggedTestCase):
|
||||||
|
|
||||||
# with text
|
# with text
|
||||||
self.assertEqual(utils.strftime(d, 'Yayınlanma tarihi: %A, %d %B %Y'),
|
self.assertEqual(utils.strftime(d, 'Yayınlanma tarihi: %A, %d %B %Y'),
|
||||||
'Yayınlanma tarihi: Çarşamba, 29 Ağustos 2012')
|
'Yayınlanma tarihi: Çarşamba, 29 Ağustos 2012')
|
||||||
|
|
||||||
# non-ascii format candidate (someone might pass it... for some reason)
|
# non-ascii format candidate (someone might pass it... for some reason)
|
||||||
self.assertEqual(utils.strftime(d, '%Y yılında %üretim artışı'),
|
self.assertEqual(utils.strftime(d, '%Y yılında %üretim artışı'),
|
||||||
'2012 yılında %üretim artışı')
|
'2012 yılında %üretim artışı')
|
||||||
|
|
||||||
# restore locale back
|
# restore locale back
|
||||||
locale.setlocale(locale.LC_ALL, old_locale)
|
locale.setlocale(locale.LC_ALL, old_locale)
|
||||||
|
|
||||||
|
|
||||||
# test the output of utils.strftime in a different locale
|
# test the output of utils.strftime in a different locale
|
||||||
# French locale
|
# French locale
|
||||||
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or
|
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or
|
||||||
|
|
@ -374,23 +372,26 @@ class TestUtils(LoggedTestCase):
|
||||||
|
|
||||||
# with text
|
# with text
|
||||||
self.assertEqual(utils.strftime(d, 'Écrit le %d %B %Y'),
|
self.assertEqual(utils.strftime(d, 'Écrit le %d %B %Y'),
|
||||||
'Écrit le 29 août 2012')
|
'Écrit le 29 août 2012')
|
||||||
|
|
||||||
# non-ascii format candidate (someone might pass it... for some reason)
|
# non-ascii format candidate (someone might pass it... for some reason)
|
||||||
self.assertEqual(utils.strftime(d, '%écrits en %Y'),
|
self.assertEqual(utils.strftime(d, '%écrits en %Y'),
|
||||||
'%écrits en 2012')
|
'%écrits en 2012')
|
||||||
|
|
||||||
# restore locale back
|
# restore locale back
|
||||||
locale.setlocale(locale.LC_ALL, old_locale)
|
locale.setlocale(locale.LC_ALL, old_locale)
|
||||||
|
|
||||||
|
|
||||||
def test_maybe_pluralize(self):
|
def test_maybe_pluralize(self):
|
||||||
self.assertEqual(utils.maybe_pluralize(0, 'Article', 'Articles'), '0 Articles')
|
self.assertEqual(
|
||||||
self.assertEqual(utils.maybe_pluralize(1, 'Article', 'Articles'), '1 Article')
|
utils.maybe_pluralize(0, 'Article', 'Articles'), '0 Articles')
|
||||||
self.assertEqual(utils.maybe_pluralize(2, 'Article', 'Articles'), '2 Articles')
|
self.assertEqual(
|
||||||
|
utils.maybe_pluralize(1, 'Article', 'Articles'), '1 Article')
|
||||||
|
self.assertEqual(
|
||||||
|
utils.maybe_pluralize(2, 'Article', 'Articles'), '2 Articles')
|
||||||
|
|
||||||
|
|
||||||
class TestCopy(unittest.TestCase):
|
class TestCopy(unittest.TestCase):
|
||||||
|
|
||||||
'''Tests the copy utility'''
|
'''Tests the copy utility'''
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
@ -475,6 +476,7 @@ class TestCopy(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestDateFormatter(unittest.TestCase):
|
class TestDateFormatter(unittest.TestCase):
|
||||||
|
|
||||||
'''Tests that the output of DateFormatter jinja filter is same as
|
'''Tests that the output of DateFormatter jinja filter is same as
|
||||||
utils.strftime'''
|
utils.strftime'''
|
||||||
|
|
||||||
|
|
@ -491,35 +493,36 @@ class TestDateFormatter(unittest.TestCase):
|
||||||
template_file.write('date = {{ date|strftime("%A, %d %B %Y") }}')
|
template_file.write('date = {{ date|strftime("%A, %d %B %Y") }}')
|
||||||
self.date = utils.SafeDatetime(2012, 8, 29)
|
self.date = utils.SafeDatetime(2012, 8, 29)
|
||||||
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
shutil.rmtree(self.temp_content)
|
shutil.rmtree(self.temp_content)
|
||||||
shutil.rmtree(self.temp_output)
|
shutil.rmtree(self.temp_output)
|
||||||
# reset locale to default
|
# reset locale to default
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
locale.setlocale(locale.LC_ALL, '')
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or
|
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or
|
||||||
locale_available('French'),
|
locale_available('French'),
|
||||||
'French locale needed')
|
'French locale needed')
|
||||||
def test_french_strftime(self):
|
def test_french_strftime(self):
|
||||||
# This test tries to reproduce an issue that occurred with python3.3 under macos10 only
|
# This test tries to reproduce an issue that occurred with python3.3
|
||||||
|
# under macos10 only
|
||||||
if platform == 'win32':
|
if platform == 'win32':
|
||||||
locale.setlocale(locale.LC_ALL, str('French'))
|
locale.setlocale(locale.LC_ALL, str('French'))
|
||||||
else:
|
else:
|
||||||
locale.setlocale(locale.LC_ALL, str('fr_FR.UTF-8'))
|
locale.setlocale(locale.LC_ALL, str('fr_FR.UTF-8'))
|
||||||
date = utils.SafeDatetime(2014,8,14)
|
date = utils.SafeDatetime(2014, 8, 14)
|
||||||
# we compare the lower() dates since macos10 returns "Jeudi" for %A whereas linux reports "jeudi"
|
# we compare the lower() dates since macos10 returns "Jeudi" for %A
|
||||||
self.assertEqual( u'jeudi, 14 août 2014', utils.strftime(date, date_format="%A, %d %B %Y").lower() )
|
# whereas linux reports "jeudi"
|
||||||
|
self.assertEqual(u'jeudi, 14 août 2014', utils.strftime(
|
||||||
|
date, date_format="%A, %d %B %Y").lower())
|
||||||
df = utils.DateFormatter()
|
df = utils.DateFormatter()
|
||||||
self.assertEqual( u'jeudi, 14 août 2014', df(date, date_format="%A, %d %B %Y").lower() )
|
self.assertEqual(
|
||||||
|
u'jeudi, 14 août 2014', df(date, date_format="%A, %d %B %Y").lower())
|
||||||
# Let us now set the global locale to C:
|
# Let us now set the global locale to C:
|
||||||
locale.setlocale(locale.LC_ALL, str('C'))
|
locale.setlocale(locale.LC_ALL, str('C'))
|
||||||
# DateFormatter should still work as expected since it is the whole point of DateFormatter
|
# DateFormatter should still work as expected since it is the whole point of DateFormatter
|
||||||
# (This is where pre-2014/4/15 code fails on macos10)
|
# (This is where pre-2014/4/15 code fails on macos10)
|
||||||
df_date = df(date, date_format="%A, %d %B %Y").lower()
|
df_date = df(date, date_format="%A, %d %B %Y").lower()
|
||||||
self.assertEqual( u'jeudi, 14 août 2014', df_date )
|
self.assertEqual(u'jeudi, 14 août 2014', df_date)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or
|
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or
|
||||||
locale_available('French'),
|
locale_available('French'),
|
||||||
|
|
@ -530,8 +533,8 @@ class TestDateFormatter(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
locale_string = 'fr_FR.UTF-8'
|
locale_string = 'fr_FR.UTF-8'
|
||||||
settings = read_settings(
|
settings = read_settings(
|
||||||
override = {'LOCALE': locale_string,
|
override={'LOCALE': locale_string,
|
||||||
'TEMPLATE_PAGES': {'template/source.html':
|
'TEMPLATE_PAGES': {'template/source.html':
|
||||||
'generated/file.html'}})
|
'generated/file.html'}})
|
||||||
|
|
||||||
generator = TemplatePagesGenerator(
|
generator = TemplatePagesGenerator(
|
||||||
|
|
@ -543,7 +546,7 @@ class TestDateFormatter(unittest.TestCase):
|
||||||
generator.generate_output(writer)
|
generator.generate_output(writer)
|
||||||
|
|
||||||
output_path = os.path.join(
|
output_path = os.path.join(
|
||||||
self.temp_output, 'generated', 'file.html')
|
self.temp_output, 'generated', 'file.html')
|
||||||
|
|
||||||
# output file has been generated
|
# output file has been generated
|
||||||
self.assertTrue(os.path.exists(output_path))
|
self.assertTrue(os.path.exists(output_path))
|
||||||
|
|
@ -553,7 +556,6 @@ class TestDateFormatter(unittest.TestCase):
|
||||||
self.assertEqual(output_file,
|
self.assertEqual(output_file,
|
||||||
utils.strftime(self.date, 'date = %A, %d %B %Y'))
|
utils.strftime(self.date, 'date = %A, %d %B %Y'))
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(locale_available('tr_TR.UTF-8') or
|
@unittest.skipUnless(locale_available('tr_TR.UTF-8') or
|
||||||
locale_available('Turkish'),
|
locale_available('Turkish'),
|
||||||
'Turkish locale needed')
|
'Turkish locale needed')
|
||||||
|
|
@ -563,9 +565,9 @@ class TestDateFormatter(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
locale_string = 'tr_TR.UTF-8'
|
locale_string = 'tr_TR.UTF-8'
|
||||||
settings = read_settings(
|
settings = read_settings(
|
||||||
override = {'LOCALE': locale_string,
|
override={'LOCALE': locale_string,
|
||||||
'TEMPLATE_PAGES': {'template/source.html':
|
'TEMPLATE_PAGES': {'template/source.html':
|
||||||
'generated/file.html'}})
|
'generated/file.html'}})
|
||||||
|
|
||||||
generator = TemplatePagesGenerator(
|
generator = TemplatePagesGenerator(
|
||||||
{'date': self.date}, settings,
|
{'date': self.date}, settings,
|
||||||
|
|
@ -576,7 +578,7 @@ class TestDateFormatter(unittest.TestCase):
|
||||||
generator.generate_output(writer)
|
generator.generate_output(writer)
|
||||||
|
|
||||||
output_path = os.path.join(
|
output_path = os.path.join(
|
||||||
self.temp_output, 'generated', 'file.html')
|
self.temp_output, 'generated', 'file.html')
|
||||||
|
|
||||||
# output file has been generated
|
# output file has been generated
|
||||||
self.assertTrue(os.path.exists(output_path))
|
self.assertTrue(os.path.exists(output_path))
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ logger = logging.getLogger(__name__)
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
@functools.total_ordering
|
@functools.total_ordering
|
||||||
class URLWrapper(object):
|
class URLWrapper(object):
|
||||||
|
|
||||||
def __init__(self, name, settings):
|
def __init__(self, name, settings):
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
@ -105,7 +106,7 @@ class URLWrapper(object):
|
||||||
return value.format(**self.as_dict())
|
return value.format(**self.as_dict())
|
||||||
|
|
||||||
page_name = property(functools.partial(_from_settings, key='URL',
|
page_name = property(functools.partial(_from_settings, key='URL',
|
||||||
get_page_name=True))
|
get_page_name=True))
|
||||||
url = property(functools.partial(_from_settings, key='URL'))
|
url = property(functools.partial(_from_settings, key='URL'))
|
||||||
save_as = property(functools.partial(_from_settings, key='SAVE_AS'))
|
save_as = property(functools.partial(_from_settings, key='SAVE_AS'))
|
||||||
|
|
||||||
|
|
@ -115,6 +116,7 @@ class Category(URLWrapper):
|
||||||
|
|
||||||
|
|
||||||
class Tag(URLWrapper):
|
class Tag(URLWrapper):
|
||||||
|
|
||||||
def __init__(self, name, *args, **kwargs):
|
def __init__(self, name, *args, **kwargs):
|
||||||
super(Tag, self).__init__(name.strip(), *args, **kwargs)
|
super(Tag, self).__init__(name.strip(), *args, **kwargs)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ def strftime(date, date_format):
|
||||||
|
|
||||||
|
|
||||||
class SafeDatetime(datetime.datetime):
|
class SafeDatetime(datetime.datetime):
|
||||||
|
|
||||||
'''Subclass of datetime that works with utf-8 format strings on PY2'''
|
'''Subclass of datetime that works with utf-8 format strings on PY2'''
|
||||||
|
|
||||||
def strftime(self, fmt, safe=True):
|
def strftime(self, fmt, safe=True):
|
||||||
|
|
@ -103,6 +104,7 @@ class SafeDatetime(datetime.datetime):
|
||||||
|
|
||||||
|
|
||||||
class DateFormatter(object):
|
class DateFormatter(object):
|
||||||
|
|
||||||
'''A date formatter object used as a jinja filter
|
'''A date formatter object used as a jinja filter
|
||||||
|
|
||||||
Uses the `strftime` implementation and makes sure jinja uses the locale
|
Uses the `strftime` implementation and makes sure jinja uses the locale
|
||||||
|
|
@ -145,12 +147,14 @@ def python_2_unicode_compatible(klass):
|
||||||
|
|
||||||
|
|
||||||
class memoized(object):
|
class memoized(object):
|
||||||
|
|
||||||
"""Function decorator to cache return values.
|
"""Function decorator to cache return values.
|
||||||
|
|
||||||
If called later with the same arguments, the cached value is returned
|
If called later with the same arguments, the cached value is returned
|
||||||
(not reevaluated).
|
(not reevaluated).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, func):
|
def __init__(self, func):
|
||||||
self.func = func
|
self.func = func
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
|
|
@ -201,7 +205,7 @@ def deprecated_attribute(old, new, since=None, remove=None, doc=None):
|
||||||
message.append('. Use {} instead.'.format(new))
|
message.append('. Use {} instead.'.format(new))
|
||||||
logger.warning(''.join(message))
|
logger.warning(''.join(message))
|
||||||
logger.debug(''.join(
|
logger.debug(''.join(
|
||||||
six.text_type(x) for x in traceback.format_stack()))
|
six.text_type(x) for x in traceback.format_stack()))
|
||||||
|
|
||||||
def fget(self):
|
def fget(self):
|
||||||
_warn()
|
_warn()
|
||||||
|
|
@ -224,7 +228,7 @@ def get_date(string):
|
||||||
"""
|
"""
|
||||||
string = re.sub(' +', ' ', string)
|
string = re.sub(' +', ' ', string)
|
||||||
default = SafeDatetime.now().replace(hour=0, minute=0,
|
default = SafeDatetime.now().replace(hour=0, minute=0,
|
||||||
second=0, microsecond=0)
|
second=0, microsecond=0)
|
||||||
try:
|
try:
|
||||||
return dateutil.parser.parse(string, default=default)
|
return dateutil.parser.parse(string, default=default)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
|
|
@ -319,12 +323,12 @@ def copy(source, destination, ignores=None):
|
||||||
|
|
||||||
for src_dir, subdirs, others in os.walk(source_):
|
for src_dir, subdirs, others in os.walk(source_):
|
||||||
dst_dir = os.path.join(destination_,
|
dst_dir = os.path.join(destination_,
|
||||||
os.path.relpath(src_dir, source_))
|
os.path.relpath(src_dir, source_))
|
||||||
|
|
||||||
subdirs[:] = (s for s in subdirs if not any(fnmatch.fnmatch(s, i)
|
subdirs[:] = (s for s in subdirs if not any(fnmatch.fnmatch(s, i)
|
||||||
for i in ignores))
|
for i in ignores))
|
||||||
others[:] = (o for o in others if not any(fnmatch.fnmatch(o, i)
|
others[:] = (o for o in others if not any(fnmatch.fnmatch(o, i)
|
||||||
for i in ignores))
|
for i in ignores))
|
||||||
|
|
||||||
if not os.path.isdir(dst_dir):
|
if not os.path.isdir(dst_dir):
|
||||||
logger.info('Creating directory %s', dst_dir)
|
logger.info('Creating directory %s', dst_dir)
|
||||||
|
|
@ -341,6 +345,7 @@ def copy(source, destination, ignores=None):
|
||||||
logger.warning('Skipped copy %s (not a file or directory) to %s',
|
logger.warning('Skipped copy %s (not a file or directory) to %s',
|
||||||
src_path, dst_path)
|
src_path, dst_path)
|
||||||
|
|
||||||
|
|
||||||
def clean_output_dir(path, retention):
|
def clean_output_dir(path, retention):
|
||||||
"""Remove all files from output directory except those in retention list"""
|
"""Remove all files from output directory except those in retention list"""
|
||||||
|
|
||||||
|
|
@ -367,7 +372,7 @@ def clean_output_dir(path, retention):
|
||||||
logger.debug("Deleted directory %s", file)
|
logger.debug("Deleted directory %s", file)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Unable to delete directory %s; %s",
|
logger.error("Unable to delete directory %s; %s",
|
||||||
file, e)
|
file, e)
|
||||||
elif os.path.isfile(file) or os.path.islink(file):
|
elif os.path.isfile(file) or os.path.islink(file):
|
||||||
try:
|
try:
|
||||||
os.remove(file)
|
os.remove(file)
|
||||||
|
|
@ -509,9 +514,9 @@ def process_translations(content_list, order_by=None):
|
||||||
items = list(items)
|
items = list(items)
|
||||||
# items with `translation` metadata will be used as translations…
|
# items with `translation` metadata will be used as translations…
|
||||||
default_lang_items = list(filter(
|
default_lang_items = list(filter(
|
||||||
lambda i: i.metadata.get('translation', 'false').lower()
|
lambda i: i.metadata.get('translation', 'false').lower()
|
||||||
== 'false',
|
== 'false',
|
||||||
items))
|
items))
|
||||||
# …unless all items with that slug are translations
|
# …unless all items with that slug are translations
|
||||||
if not default_lang_items:
|
if not default_lang_items:
|
||||||
default_lang_items = items
|
default_lang_items = items
|
||||||
|
|
@ -522,13 +527,13 @@ def process_translations(content_list, order_by=None):
|
||||||
len_ = len(lang_items)
|
len_ = len(lang_items)
|
||||||
if len_ > 1:
|
if len_ > 1:
|
||||||
logger.warning('There are %s variants of "%s" with lang %s',
|
logger.warning('There are %s variants of "%s" with lang %s',
|
||||||
len_, slug, lang)
|
len_, slug, lang)
|
||||||
for x in lang_items:
|
for x in lang_items:
|
||||||
logger.warning('\t%s', x.source_path)
|
logger.warning('\t%s', x.source_path)
|
||||||
|
|
||||||
# find items with default language
|
# find items with default language
|
||||||
default_lang_items = list(filter(attrgetter('in_default_lang'),
|
default_lang_items = list(filter(attrgetter('in_default_lang'),
|
||||||
default_lang_items))
|
default_lang_items))
|
||||||
|
|
||||||
# if there is no article with default language, take an other one
|
# if there is no article with default language, take an other one
|
||||||
if not default_lang_items:
|
if not default_lang_items:
|
||||||
|
|
@ -536,10 +541,10 @@ def process_translations(content_list, order_by=None):
|
||||||
|
|
||||||
if not slug:
|
if not slug:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'empty slug for %s. '
|
'empty slug for %s. '
|
||||||
'You can fix this by adding a title or a slug to your '
|
'You can fix this by adding a title or a slug to your '
|
||||||
'content',
|
'content',
|
||||||
default_lang_items[0].source_path)
|
default_lang_items[0].source_path)
|
||||||
index.extend(default_lang_items)
|
index.extend(default_lang_items)
|
||||||
translations.extend([x for x in items if x not in default_lang_items])
|
translations.extend([x for x in items if x not in default_lang_items])
|
||||||
for a in items:
|
for a in items:
|
||||||
|
|
@ -568,10 +573,10 @@ def process_translations(content_list, order_by=None):
|
||||||
reverse=order_reversed)
|
reverse=order_reversed)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.warning('There is no "%s" attribute in the item '
|
logger.warning('There is no "%s" attribute in the item '
|
||||||
'metadata. Defaulting to slug order.', order_by)
|
'metadata. Defaulting to slug order.', order_by)
|
||||||
else:
|
else:
|
||||||
logger.warning('Invalid *_ORDER_BY setting (%s).'
|
logger.warning('Invalid *_ORDER_BY setting (%s).'
|
||||||
'Valid options are strings and functions.', order_by)
|
'Valid options are strings and functions.', order_by)
|
||||||
|
|
||||||
return index, translations
|
return index, translations
|
||||||
|
|
||||||
|
|
@ -590,7 +595,7 @@ def folder_watcher(path, extensions, ignores=[]):
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
if (f.endswith(tuple(extensions)) and
|
if (f.endswith(tuple(extensions)) and
|
||||||
not any(fnmatch.fnmatch(f, ignore) for ignore in ignores)):
|
not any(fnmatch.fnmatch(f, ignore) for ignore in ignores)):
|
||||||
try:
|
try:
|
||||||
yield os.stat(os.path.join(root, f)).st_mtime
|
yield os.stat(os.path.join(root, f)).st_mtime
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
|
|
||||||
|
|
@ -119,10 +119,10 @@ class Writer(object):
|
||||||
feed.write(fp, 'utf-8')
|
feed.write(fp, 'utf-8')
|
||||||
logger.info('Writing %s', complete_path)
|
logger.info('Writing %s', complete_path)
|
||||||
|
|
||||||
signals.feed_written.send(complete_path, context=context, feed=feed)
|
signals.feed_written.send(
|
||||||
|
complete_path, context=context, feed=feed)
|
||||||
return feed
|
return feed
|
||||||
|
|
||||||
|
|
||||||
def write_file(self, name, template, context, relative_urls=False,
|
def write_file(self, name, template, context, relative_urls=False,
|
||||||
paginated=None, override_output=False, **kwargs):
|
paginated=None, override_output=False, **kwargs):
|
||||||
"""Render the template and write the file.
|
"""Render the template and write the file.
|
||||||
|
|
@ -140,8 +140,8 @@ class Writer(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if name is False or name == "" or\
|
if name is False or name == "" or\
|
||||||
not is_selected_for_writing(self.settings,\
|
not is_selected_for_writing(self.settings,
|
||||||
os.path.join(self.output_path, name)):
|
os.path.join(self.output_path, name)):
|
||||||
return
|
return
|
||||||
elif not name:
|
elif not name:
|
||||||
# other stuff, just return for now
|
# other stuff, just return for now
|
||||||
|
|
@ -169,7 +169,8 @@ class Writer(object):
|
||||||
|
|
||||||
def _get_localcontext(context, name, kwargs, relative_urls):
|
def _get_localcontext(context, name, kwargs, relative_urls):
|
||||||
localcontext = context.copy()
|
localcontext = context.copy()
|
||||||
localcontext['localsiteurl'] = localcontext.get('localsiteurl', None)
|
localcontext['localsiteurl'] = localcontext.get(
|
||||||
|
'localsiteurl', None)
|
||||||
if relative_urls:
|
if relative_urls:
|
||||||
relative_url = path_to_url(get_relative_path(name))
|
relative_url = path_to_url(get_relative_path(name))
|
||||||
localcontext['SITEURL'] = relative_url
|
localcontext['SITEURL'] = relative_url
|
||||||
|
|
@ -201,11 +202,13 @@ class Writer(object):
|
||||||
'%s_previous_page' % key: previous_page,
|
'%s_previous_page' % key: previous_page,
|
||||||
'%s_next_page' % key: next_page})
|
'%s_next_page' % key: next_page})
|
||||||
|
|
||||||
localcontext = _get_localcontext(context, page.save_as, paginated_kwargs, relative_urls)
|
localcontext = _get_localcontext(
|
||||||
|
context, page.save_as, paginated_kwargs, relative_urls)
|
||||||
_write_file(template, localcontext, self.output_path,
|
_write_file(template, localcontext, self.output_path,
|
||||||
page.save_as, override_output)
|
page.save_as, override_output)
|
||||||
else:
|
else:
|
||||||
# no pagination
|
# no pagination
|
||||||
localcontext = _get_localcontext(context, name, kwargs, relative_urls)
|
localcontext = _get_localcontext(
|
||||||
|
context, name, kwargs, relative_urls)
|
||||||
_write_file(template, localcontext, self.output_path, name,
|
_write_file(template, localcontext, self.output_path, name,
|
||||||
override_output)
|
override_output)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue