diff --git a/.travis.yml b/.travis.yml index 8f5dc3a3..823c1172 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,3 +6,8 @@ install: - pip install nose unittest2 mock --use-mirrors - pip install . --use-mirrors script: nosetests -s tests +notifications: + irc: + channels: + - "irc.freenode.org#pelican" + on_success: change diff --git a/MANIFEST.in b/MANIFEST.in index fc46d905..a092ecd0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,4 +2,3 @@ include *.rst global-include *.py recursive-include pelican *.html *.css *png include LICENSE -global-include *.bat diff --git a/docs/conf.py b/docs/conf.py index 4c4530e2..ac2d67ee 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- import sys, os +sys.path.append(os.path.abspath('..')) + +from pelican import __version__, __major__ + # -- General configuration ----------------------------------------------------- templates_path = ['_templates'] extensions = ['sphinx.ext.autodoc',] @@ -9,12 +13,11 @@ master_doc = 'index' project = u'Pelican' copyright = u'2010, Alexis Metaireau and contributors' exclude_patterns = ['_build'] -version = "2" -release = version +version = __version__ +release = __major__ # -- Options for HTML output --------------------------------------------------- -sys.path.append(os.path.abspath('_themes')) html_theme_path = ['_themes'] html_theme = 'pelican' diff --git a/docs/settings.rst b/docs/settings.rst index 9eb46439..b1c35122 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -46,7 +46,7 @@ Setting name (default value) What does it do? `MARKUP` (``('rst', 'md')``) A list of available markup languages you want to use. For the moment, the only available values are `rst` and `md`. -`MD_EXTENSIONS` (``('codehilite','extra')``) A list of the extensions that the Markdown processor +`MD_EXTENSIONS` (``['codehilite','extra']``) A list of the extensions that the Markdown processor will use. Refer to the extensions chapter in the Python-Markdown documentation for a complete list of supported extensions. @@ -83,7 +83,7 @@ Setting name (default value) What does it do? .. [#] Default is the system locale. -URL Settings +URL settings ------------ You can customize the URL's and locations where files will be saved. The URL's and @@ -160,6 +160,8 @@ maintain multiple languages with different date formats, you can set this dict using language name (``lang`` in your posts) as key. Regarding available format codes, see `strftime document of python`_ : +.. parsed-literal:: + DATE_FORMAT = { 'en': '%a, %d %b %Y', 'jp': '%Y-%m-%d(%a)', @@ -167,6 +169,8 @@ codes, see `strftime document of python`_ : You can set locale to further control date format: +.. parsed-literal:: + LOCALE = ('usa', 'jpn', # On Windows 'en_US', 'ja_JP' # On Unix/Linux ) @@ -175,6 +179,7 @@ Also, it is possible to set different locale settings for each language. If you put (locale, format) tuples in the dict, this will override the LOCALE setting above: +.. parsed-literal:: # On Unix/Linux DATE_FORMAT = { 'en': ('en_US','%a, %d %b %Y'), diff --git a/pelican/__init__.py b/pelican/__init__.py index 0b53dbcc..780938a7 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -11,7 +11,9 @@ from pelican.utils import clean_output_dir, files_changed from pelican.writers import Writer from pelican import log -__version__ = "3.0" +__major__ = 3 +__minor__ = 0 +__version__ = "{0}.{1}".format(__major__, __minor__) class Pelican(object): @@ -135,7 +137,8 @@ class Pelican(object): def main(): parser = argparse.ArgumentParser(description="""A tool to generate a - static blog, with restructured text input files.""") + static blog, with restructured text input files.""", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument(dest='path', nargs='?', help='Path where to find the content files.') @@ -145,11 +148,11 @@ def main(): parser.add_argument('-o', '--output', dest='output', help='Where to output the generated files. If not specified, a ' 'directory will be created, named "output" in the current path.') - parser.add_argument('-m', '--markup', default=None, dest='markup', + parser.add_argument('-m', '--markup', dest='markup', help='The list of markup language to use (rst or md). Please indicate ' 'them separated by commas.') - parser.add_argument('-s', '--settings', dest='settings', default='', - help='The settings of the application. Default to False.') + parser.add_argument('-s', '--settings', dest='settings', + help='The settings of the application.') parser.add_argument('-d', '--delete-output-directory', dest='delete_outputdir', action='store_true', help='Delete the output directory.') diff --git a/pelican/contents.py b/pelican/contents.py index 4f424461..3386dba9 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -42,9 +42,10 @@ class Page(object): if 'AUTHOR' in settings: self.author = Author(settings['AUTHOR'], settings) else: + title = filename.decode('utf-8') if filename else self.title self.author = Author(getenv('USER', 'John Doe'), settings) warning(u"Author of `{0}' unknown, assuming that his name is " - "`{1}'".format(filename or self.title, self.author)) + "`{1}'".format(title, self.author)) # manage languages self.in_default_lang = True @@ -89,9 +90,9 @@ class Page(object): if hasattr(self, 'date') and self.date > datetime.now(): self.status = 'draft' - # set summary - if not hasattr(self, 'summary'): - self.summary = truncate_html_words(self.content, 50) + # store the summary metadata if it is set + if 'summary' in metadata: + self._summary = metadata['summary'] def check_properties(self): """test that each mandatory property is set.""" @@ -126,8 +127,12 @@ class Page(object): return content def _get_summary(self): - """Returns the summary of an article, based on to the content""" - return truncate_html_words(self.content, 50) + """Returns the summary of an article, based on the summary metadata + if it is set, else troncate the content.""" + if hasattr(self, '_summary'): + return self._summary + else: + return truncate_html_words(self.content, 50) def _set_summary(self, summary): """Dummy function""" diff --git a/pelican/generators.py b/pelican/generators.py index 2987dcfe..71208430 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -227,7 +227,7 @@ class ArticlesGenerator(Generator): continue # if no category is set, use the name of the path as a category - if 'category' not in metadata.keys(): + if 'category' not in metadata: if os.path.dirname(f) == self.path: category = self.settings['DEFAULT_CATEGORY'] @@ -238,8 +238,7 @@ class ArticlesGenerator(Generator): if category != '': metadata['category'] = Category(category, self.settings) - if 'date' not in metadata.keys()\ - and self.settings['FALLBACK_ON_FS_DATE']: + if 'date' not in metadata and self.settings['FALLBACK_ON_FS_DATE']: metadata['date'] = datetime.datetime.fromtimestamp( os.stat(f).st_ctime) diff --git a/pelican/log.py b/pelican/log.py index 1cb76e16..8811b372 100644 --- a/pelican/log.py +++ b/pelican/log.py @@ -1,38 +1,32 @@ import os import sys -from logging import CRITICAL, ERROR, WARN, INFO, DEBUG +from logging import CRITICAL, ERROR, WARN, INFO, DEBUG from logging import critical, error, info, warning, warn, debug from logging import Formatter, getLogger, StreamHandler -RESET_TERM = u'\033[1;m' +RESET_TERM = u'\033[0;m' -def term_color(code): - return lambda text: code + unicode(text) + RESET_TERM +def start_color(index): + return u'\033[1;{0}m'.format(index) + + +def term_color(color): + code = COLOR_CODES[color] + return lambda text: start_color(code) + unicode(text) + RESET_TERM COLOR_CODES = { - 'gray': u'\033[1;30m', - 'red': u'\033[1;31m', - 'green': u'\033[1;32m', - 'yellow': u'\033[1;33m', - 'blue': u'\033[1;34m', - 'magenta': u'\033[1;35m', - 'cyan': u'\033[1;36m', - 'white': u'\033[1;37m', - 'bgred': u'\033[1;41m', - 'bggreen': u'\033[1;42m', - 'bgbrown': u'\033[1;43m', - 'bgblue': u'\033[1;44m', - 'bgmagenta': u'\033[1;45m', - 'bgcyan': u'\033[1;46m', - 'bggray': u'\033[1;47m', - 'bgyellow': u'\033[1;43m', - 'bggrey': u'\033[1;100m', + 'red': 31, + 'yellow': 33, + 'cyan': 36, + 'white': 37, + 'bgred': 41, + 'bggrey': 100, } -ANSI = dict((col, term_color(code)) for col, code in COLOR_CODES.items()) +ANSI = dict((col, term_color(col)) for col in COLOR_CODES) class ANSIFormatter(Formatter): @@ -80,7 +74,7 @@ class DummyFormatter(object): and not sys.platform.startswith('win'): return ANSIFormatter(*args, **kwargs) else: - return TextFormatter( *args, **kwargs) + return TextFormatter(*args, **kwargs) def init(level=None, logger=getLogger(), handler=StreamHandler()): diff --git a/pelican/readers.py b/pelican/readers.py index a581e458..2e269647 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -36,8 +36,8 @@ class Reader(object): self.settings = settings def process_metadata(self, name, value): - if name.lower() in _METADATA_PROCESSORS: - return _METADATA_PROCESSORS[name.lower()](value, self.settings) + if name in _METADATA_PROCESSORS: + return _METADATA_PROCESSORS[name](value, self.settings) return value @@ -71,10 +71,14 @@ class RstReader(Reader): if element.tagname == 'field': # custom fields (e.g. summary) name_elem, body_elem = element.children name = name_elem.astext() - value = render_node_to_html(document, body_elem) + if name == 'summary': + value = render_node_to_html(document, body_elem) + else: + value = body_elem.astext() else: # standard fields (e.g. address) name = element.tagname value = element.astext() + name = name.lower() output[name] = self.process_metadata(name, value) return output @@ -144,7 +148,7 @@ def read_file(filename, fmt=None, settings=None): if not fmt: fmt = filename.split('.')[-1] - if fmt not in _EXTENSIONS.keys(): + if fmt not in _EXTENSIONS: raise TypeError('Pelican does not know how to parse %s' % filename) reader = _EXTENSIONS[fmt](settings) diff --git a/pelican/themes/notmyidea/templates/archives.html b/pelican/themes/notmyidea/templates/archives.html index 5ba2c817..f7f1c400 100644 --- a/pelican/themes/notmyidea/templates/archives.html +++ b/pelican/themes/notmyidea/templates/archives.html @@ -6,7 +6,7 @@
+TEST_CONTENT = str(generate_lorem_ipsum(n=1)) +TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False) class TestPage(TestCase): def setUp(self): super(TestPage, self).setUp() self.page_kwargs = { - 'content': 'content', + 'content': TEST_CONTENT, 'metadata': { + 'summary': TEST_SUMMARY, 'title': 'foo bar', 'author': 'Blogger', }, @@ -27,11 +33,11 @@ class TestPage(TestCase): """ metadata = {'foo': 'bar', 'foobar': 'baz', 'title': 'foobar', } - page = Page('content', metadata=metadata) + page = Page(TEST_CONTENT, metadata=metadata) for key, value in metadata.items(): self.assertTrue(hasattr(page, key)) self.assertEqual(value, getattr(page, key)) - self.assertEqual(page.content, 'content') + self.assertEqual(page.content, TEST_CONTENT) def test_mandatory_properties(self): """If the title is not set, must throw an exception.""" @@ -39,6 +45,11 @@ class TestPage(TestCase): page = Page(**self.page_kwargs) page.check_properties() + def test_summary_from_metadata(self): + """If a :summary: metadata is given, it should be used.""" + page = Page(**self.page_kwargs) + self.assertEqual(page.summary, TEST_SUMMARY) + def test_slug(self): """If a title is given, it should be used to generate the slug.""" page = Page(**self.page_kwargs) diff --git a/tests/test_readers.py b/tests/test_readers.py index c0b8cc41..4c04a212 100644 --- a/tests/test_readers.py +++ b/tests/test_readers.py @@ -30,11 +30,20 @@ class RstReaderTest(unittest.TestCase): ' inline markup.', 'date': datetime.datetime(2010, 12, 2, 10, 14), 'tags': ['foo', 'bar', 'foobar'], + 'custom_field': 'http://notmyidea.org', } for key, value in expected.items(): self.assertEquals(value, metadata[key], key) + def test_article_metadata_key_lowercase(self): + """Keys of metadata should be lowercase.""" + reader = readers.RstReader({}) + content, metadata = reader.read(_filename('article_with_uppercase_metadata.rst')) + + self.assertIn('category', metadata, "Key should be lowercase.") + self.assertEquals('Yeah', metadata.get('category'), "Value keeps cases.") + def test_typogrify(self): # if nothing is specified in the settings, the content should be # unmodified diff --git a/tests/test_utils.py b/tests/test_utils.py index 9654825e..40f710d9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,11 +3,12 @@ try: import unittest2 as unittest except ImportError: import unittest # NOQA + +import os import datetime +import time from pelican import utils -from pelican.contents import Article - from support import get_article @@ -73,3 +74,20 @@ class TestUtils(unittest.TestCase): self.assertIn(fr_article1, trans) self.assertNotIn(en_article1, trans) self.assertNotIn(fr_article1, index) + + def test_files_changed(self): + "Test if file changes are correctly detected" + + path = os.path.join(os.path.dirname(__file__), 'content') + filename = os.path.join(path, 'article_with_metadata.rst') + changed = utils.files_changed(path, 'rst') + self.assertEquals(changed, True) + + changed = utils.files_changed(path, 'rst') + self.assertEquals(changed, False) + + t = time.time() + os.utime(filename, (t, t)) + changed = utils.files_changed(path, 'rst') + self.assertEquals(changed, True) + self.assertAlmostEqual(utils.LAST_MTIME, t, places=2)