- {% if articles_page.has_previous() %} - {% if articles_page.previous_page_number() == 1 %} - « - {% else %} - « - {% endif %} - {% endif %} - Page {{ articles_page.number }} / {{ articles_paginator.num_pages }} - {% if articles_page.has_next() %} - » - {% endif %} -
+{% include 'pagination.html' %}so + # HTML is valid before conversion + paragraphs = content.split('\n\n') + paragraphs = [u'
{}
'.format(p) for p in paragraphs] + new_content = ''.join(paragraphs) + fp.write(content) cmd = 'pandoc --normalize --reference-links --from=html --to={0} -o "{1}" "{2}"'.format( diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 7ade62e9..cfd9bb4c 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -7,91 +7,9 @@ import argparse from pelican import __version__ -TEMPLATES = { - 'Makefile' : ''' -PELICAN=$pelican -PELICANOPTS=$pelicanopts +_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), \ + "templates") -BASEDIR=$$(PWD) -INPUTDIR=$$(BASEDIR)/src -OUTPUTDIR=$$(BASEDIR)/output -CONFFILE=$$(BASEDIR)/pelican.conf.py - -FTP_HOST=$ftp_host -FTP_USER=$ftp_user -FTP_TARGET_DIR=$ftp_target_dir - -SSH_HOST=$ssh_host -SSH_USER=$ssh_user -SSH_TARGET_DIR=$ssh_target_dir - -DROPBOX_DIR=$dropbox_dir - -help: -\t@echo 'Makefile for a pelican Web site ' -\t@echo ' ' -\t@echo 'Usage: ' -\t@echo ' make html (re)generate the web site ' -\t@echo ' make clean remove the generated files ' -\t@echo ' ftp_upload upload the web site using FTP ' -\t@echo ' ssh_upload upload the web site using SSH ' -\t@echo ' dropbox_upload upload the web site using Dropbox ' -\t@echo ' ' - - -html: clean $$(OUTPUTDIR)/index.html -\t@echo 'Done' - -$$(OUTPUTDIR)/%.html: -\t$$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS) - -clean: -\trm -fr $$(OUTPUTDIR) -\tmkdir $$(OUTPUTDIR) - -dropbox_upload: $$(OUTPUTDIR)/index.html -\tcp -r $$(OUTPUTDIR)/* $$(DROPBOX_DIR) - -ssh_upload: $$(OUTPUTDIR)/index.html -\tscp -r $$(OUTPUTDIR)/* $$(SSH_USER)@$$(SSH_HOST):$$(SSH_TARGET_DIR) - -ftp_upload: $$(OUTPUTDIR)/index.html -\tlftp ftp://$$(FTP_USER)@$$(FTP_HOST) -e "mirror -R $$(OUTPUTDIR) $$(FTP_TARGET_DIR) ; quit" - -github: $$(OUTPUTDIR)/index.html -\tghp-import $$(OUTPUTDIR) -\tgit push origin gh-pages - -.PHONY: html help clean ftp_upload ssh_upload dropbox_upload github -''', - - 'pelican.conf.py': '''#!/usr/bin/env python -# -*- coding: utf-8 -*- # - -AUTHOR = u"$author" -SITENAME = u"$sitename" -SITEURL = '/' - -TIMEZONE = 'Europe/Paris' - -DEFAULT_LANG='$lang' - -# Blogroll -LINKS = ( - ('Pelican', 'http://docs.notmyidea.org/alexis/pelican/'), - ('Python.org', 'http://python.org'), - ('Jinja2', 'http://jinja.pocoo.org'), - ('You can modify those links in your config file', '#') - ) - -# Social widget -SOCIAL = ( - ('You can add links in your config file', '#'), - ) - -DEFAULT_PAGINATION = $default_pagination -''' -} CONF = { 'pelican' : 'pelican', @@ -109,6 +27,20 @@ CONF = { } +def get_template(name): + template = os.path.join(_TEMPLATES_DIR, "{0}.in".format(name)) + + if not os.path.isfile(template): + raise RuntimeError("Cannot open {0}".format(template)) + + with open(template, 'r') as fd: + line = fd.readline() + while line: + yield line + line = fd.readline() + fd.close() + + def ask(question, answer=str, default=None, l=None): if answer == str: r = '' @@ -199,16 +131,16 @@ def main(): print('''Welcome to pelican-quickstart v{v}. -This script will help you creating a new Pelican based website. +This script will help you create a new Pelican-based website. Please answer the following questions so this script can generate the files needed by Pelican. '''.format(v=__version__)) CONF['basedir'] = os.path.abspath(ask('Where do you want to create your new Web site ?', answer=str, default=args.path)) - CONF['sitename'] = ask('How will you call your Web site ?', answer=str, default=args.title) + CONF['sitename'] = ask('What will be the title of this Web site ?', answer=str, default=args.title) CONF['author'] = ask('Who will be the author of this Web site ?', answer=str, default=args.author) - CONF['lang'] = ask('What will be the default language of this Web site ?', str, args.lang or CONF['lang'], 2) + CONF['lang'] = ask('What will be the default language of this Web site ?', str, args.lang or CONF['lang'], 2) CONF['with_pagination'] = ask('Do you want to enable article pagination ?', bool, bool(CONF['default_pagination'])) @@ -223,12 +155,12 @@ Please answer the following questions so this script can generate the files need if ask('Do you want to upload your website using FTP ?', answer=bool, default=False): CONF['ftp_host'] = ask('What is the hostname of your FTP server ?', str, CONF['ftp_host']) CONF['ftp_user'] = ask('What is your username on this server ?', str, CONF['ftp_user']) - CONF['ftp_traget_dir'] = ask('Where do you want to put your website on this server ?', str, CONF['ftp_target_dir']) + CONF['ftp_target_dir'] = ask('Where do you want to put your website on this server ?', str, CONF['ftp_target_dir']) if ask('Do you want to upload your website using SSH ?', answer=bool, default=False): CONF['ssh_host'] = ask('What is the hostname of your SSH server ?', str, CONF['ssh_host']) CONF['ssh_user'] = ask('What is your username on this server ?', str, CONF['ssh_user']) - CONF['ssh_traget_dir'] = ask('Where do you want to put your website on this server ?', str, CONF['ssh_target_dir']) + CONF['ssh_target_dir'] = ask('Where do you want to put your website on this server ?', str, CONF['ssh_target_dir']) if ask('Do you want to upload your website using Dropbox ?', answer=bool, default=False): CONF['dropbox_dir'] = ask('Where is your Dropbox directory ?', str, CONF['dropbox_dir']) @@ -243,20 +175,22 @@ Please answer the following questions so this script can generate the files need except OSError, e: print('Error: {0}'.format(e)) - conf = string.Template(TEMPLATES['pelican.conf.py']) try: with open(os.path.join(CONF['basedir'], 'pelican.conf.py'), 'w') as fd: - fd.write(conf.safe_substitute(CONF)) + for line in get_template('pelican.conf.py'): + template = string.Template(line) + fd.write(template.safe_substitute(CONF)) fd.close() except OSError, e: print('Error: {0}'.format(e)) if mkfile: - Makefile = string.Template(TEMPLATES['Makefile']) try: with open(os.path.join(CONF['basedir'], 'Makefile'), 'w') as fd: - fd.write(Makefile.safe_substitute(CONF)) + for line in get_template('Makefile'): + template = string.Template(line) + fd.write(template.safe_substitute(CONF)) fd.close() except OSError, e: print('Error: {0}'.format(e)) diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in new file mode 100644 index 00000000..998d8c97 --- /dev/null +++ b/pelican/tools/templates/Makefile.in @@ -0,0 +1,58 @@ +PELICAN=$pelican +PELICANOPTS=$pelicanopts + +BASEDIR=$$(PWD) +INPUTDIR=$$(BASEDIR)/src +OUTPUTDIR=$$(BASEDIR)/output +CONFFILE=$$(BASEDIR)/pelican.conf.py + +FTP_HOST=$ftp_host +FTP_USER=$ftp_user +FTP_TARGET_DIR=$ftp_target_dir + +SSH_HOST=$ssh_host +SSH_USER=$ssh_user +SSH_TARGET_DIR=$ssh_target_dir + +DROPBOX_DIR=$dropbox_dir + +help: + @echo 'Makefile for a pelican Web site ' + @echo ' ' + @echo 'Usage: ' + @echo ' make html (re)generate the web site ' + @echo ' make clean remove the generated files ' + @echo ' ftp_upload upload the web site using FTP ' + @echo ' ssh_upload upload the web site using SSH ' + @echo ' dropbox_upload upload the web site using Dropbox ' + @echo ' rsync_upload upload the web site using rsync/ssh' + @echo ' ' + + +html: clean $$(OUTPUTDIR)/index.html + @echo 'Done' + +$$(OUTPUTDIR)/%.html: + $$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS) + +clean: + rm -fr $$(OUTPUTDIR) + mkdir $$(OUTPUTDIR) + +dropbox_upload: $$(OUTPUTDIR)/index.html + cp -r $$(OUTPUTDIR)/* $$(DROPBOX_DIR) + +ssh_upload: $$(OUTPUTDIR)/index.html + scp -r $$(OUTPUTDIR)/* $$(SSH_USER)@$$(SSH_HOST):$$(SSH_TARGET_DIR) + +rsync_upload: $$(OUTPUTDIR)/index.html + rsync -e ssh -P -rvz --delete $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) + +ftp_upload: $$(OUTPUTDIR)/index.html + lftp ftp://$$(FTP_USER)@$$(FTP_HOST) -e "mirror -R $$(OUTPUTDIR) $$(FTP_TARGET_DIR) ; quit" + +github: $$(OUTPUTDIR)/index.html + ghp-import $$(OUTPUTDIR) + git push origin gh-pages + +.PHONY: html help clean ftp_upload ssh_upload rsync_upload dropbox_upload github diff --git a/pelican/tools/templates/pelican.conf.py.in b/pelican/tools/templates/pelican.conf.py.in new file mode 100644 index 00000000..ee56048e --- /dev/null +++ b/pelican/tools/templates/pelican.conf.py.in @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- # + +AUTHOR = u"$author" +SITENAME = u"$sitename" +SITEURL = '/' + +TIMEZONE = 'Europe/Paris' + +DEFAULT_LANG='$lang' + +# Blogroll +LINKS = ( + ('Pelican', 'http://docs.notmyidea.org/alexis/pelican/'), + ('Python.org', 'http://python.org'), + ('Jinja2', 'http://jinja.pocoo.org'), + ('You can modify those links in your config file', '#') + ) + +# Social widget +SOCIAL = ( + ('You can add links in your config file', '#'), + ) + +DEFAULT_PAGINATION = $default_pagination diff --git a/pelican/utils.py b/pelican/utils.py index 18730e6c..d4e34842 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -8,6 +8,7 @@ import logging from codecs import open as _open from datetime import datetime from itertools import groupby +from jinja2 import Markup from operator import attrgetter logger = logging.getLogger(__name__) @@ -44,6 +45,7 @@ def slugify(value): Took from django sources. """ + value = Markup(value).striptags() if type(value) == unicode: import unicodedata value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') diff --git a/pelican/writers.py b/pelican/writers.py index faca46bd..75971ee9 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -8,8 +8,8 @@ import logging from codecs import open from functools import partial - from feedgenerator import Atom1Feed, Rss201rev2Feed +from jinja2 import Markup from pelican.paginator import Paginator from pelican.utils import get_relative_path, set_date_tzinfo @@ -25,8 +25,9 @@ class Writer(object): def _create_new_feed(self, feed_type, context): feed_class = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed + sitename = Markup(context['SITENAME']).striptags() feed = feed_class( - title=context['SITENAME'], + title=sitename, link=(self.site_url + '/'), feed_url=self.feed_url, description=context.get('SITESUBTITLE', '')) @@ -34,9 +35,10 @@ class Writer(object): def _add_item_to_the_feed(self, feed, item): + title = Markup(item.title).striptags() feed.add_item( - title=item.title, - link='%s%s' % (self.site_url, item.url), + title=title, + link='%s/%s' % (self.site_url, item.url), unique_id='tag:%s,%s:%s' % (self.site_url.replace('http://', ''), item.date.date(), item.url), description=item.content, @@ -99,6 +101,12 @@ class Writer(object): :param **kwargs: additional variables to pass to the templates """ + if name is False: + return + elif not name: + # other stuff, just return for now + return + def _write_file(template, localcontext, output_path, name): """Render the template write the file.""" old_locale = locale.setlocale(locale.LC_ALL) diff --git a/tests/content/article_with_md_extension.md b/tests/content/article_with_md_extension.md new file mode 100644 index 00000000..11aa22a2 --- /dev/null +++ b/tests/content/article_with_md_extension.md @@ -0,0 +1,10 @@ +title: Test md File +category: test + +Test Markdown File Header +========================= + +Used for pelican test +--------------------- + +The quick brown fox jumped over the lazy dog's back. diff --git a/tests/content/article_with_mkd_extension.mkd b/tests/content/article_with_mkd_extension.mkd new file mode 100644 index 00000000..c946cb87 --- /dev/null +++ b/tests/content/article_with_mkd_extension.mkd @@ -0,0 +1,10 @@ +title: Test mkd File +category: test + +Test Markdown File Header +========================= + +Used for pelican test +--------------------- + +This is another markdown test file. Uses the mkd extension. diff --git a/tests/support.py b/tests/support.py index 4eb07ec4..f2b4a075 100644 --- a/tests/support.py +++ b/tests/support.py @@ -4,6 +4,8 @@ __all__ = [ 'unittest', ] +import os +import subprocess from contextlib import contextmanager from tempfile import mkdtemp from shutil import rmtree @@ -35,3 +37,21 @@ def get_article(title, slug, content, lang, extra_metadata=None): if extra_metadata is not None: metadata.update(extra_metadata) return Article(content, metadata=metadata) + + +def skipIfNoExecutable(executable, valid_exit_code=1): + """Tries to run an executable to make sure it's in the path, Skips the tests + if not found. + """ + + # calling with no params the command should exit with 1 + with open(os.devnull, 'w') as fnull: + try: + res = subprocess.call(executable, stdout=fnull, stderr=fnull) + except OSError: + res = None + + if res != valid_exit_code: + return unittest.skip('{0} compiler not found'.format(executable)) + + return lambda func: func diff --git a/tests/test_generators.py b/tests/test_generators.py index bc5c8b73..e62551fa 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -2,13 +2,15 @@ from mock import MagicMock import os +import re -from pelican.generators import ArticlesGenerator +from pelican.generators import ArticlesGenerator, LessCSSGenerator from pelican.settings import _DEFAULT_CONFIG -from .support import unittest +from .support import unittest, temporary_folder, skipIfNoExecutable CUR_DIR = os.path.dirname(__file__) + class TestArticlesGenerator(unittest.TestCase): def test_generate_feeds(self): @@ -46,3 +48,94 @@ class TestArticlesGenerator(unittest.TestCase): elif relfilepath == "article_without_category.rst": self.assertEquals(article.category.name, 'Default') + categories = [cat.name for cat, _ in generator.categories] + # assert that the categories are ordered as expected + self.assertEquals( + categories, ['Default', 'TestCategory', 'Yeah', 'test', + 'yeah']) + + def test_direct_templates_save_as_default(self): + + settings = _DEFAULT_CONFIG.copy() + settings['DIRECT_TEMPLATES'] = ['archives'] + generator = ArticlesGenerator(settings.copy(), settings, None, + _DEFAULT_CONFIG['THEME'], None, + _DEFAULT_CONFIG['MARKUP']) + write = MagicMock() + generator.generate_direct_templates(write) + write.assert_called_with("archives.html", + generator.get_template("archives"), settings, + blog=True, paginated={}, page_name='archives') + + def test_direct_templates_save_as_modified(self): + + settings = _DEFAULT_CONFIG.copy() + settings['DIRECT_TEMPLATES'] = ['archives'] + settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' + generator = ArticlesGenerator(settings, settings, None, + _DEFAULT_CONFIG['THEME'], None, + _DEFAULT_CONFIG['MARKUP']) + write = MagicMock() + generator.generate_direct_templates(write) + write.assert_called_with("archives/index.html", + generator.get_template("archives"), settings, + blog=True, paginated={}, page_name='archives') + + def test_direct_templates_save_as_false(self): + + settings = _DEFAULT_CONFIG.copy() + settings['DIRECT_TEMPLATES'] = ['archives'] + settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' + generator = ArticlesGenerator(settings, settings, None, + _DEFAULT_CONFIG['THEME'], None, + _DEFAULT_CONFIG['MARKUP']) + write = MagicMock() + generator.generate_direct_templates(write) + write.assert_called_count == 0 + + +class TestLessCSSGenerator(unittest.TestCase): + + LESS_CONTENT = """ + @color: #4D926F; + + #header { + color: @color; + } + h2 { + color: @color; + } + """ + + @skipIfNoExecutable('lessc') + def test_less_compiler(self): + + settings = _DEFAULT_CONFIG.copy() + settings['STATIC_PATHS'] = ['static'] + settings['LESS_GENERATOR'] = True + + # we'll nest here for py < 2.7 compat + with temporary_folder() as temp_content: + with temporary_folder() as temp_output: + generator = LessCSSGenerator(None, settings, temp_content, + _DEFAULT_CONFIG['THEME'], temp_output, None) + + # create a dummy less file + less_dir = os.path.join(temp_content, 'static', 'css') + less_filename = os.path.join(less_dir, 'test.less') + + less_output = os.path.join(temp_output, 'static', 'css', + 'test.css') + + os.makedirs(less_dir) + with open(less_filename, 'w') as less_file: + less_file.write(self.LESS_CONTENT) + + generator.generate_output() + + # we have the file ? + self.assertTrue(os.path.exists(less_output)) + + # was it compiled ? + self.assertIsNotNone(re.search(r'^\s+color:\s*#4D926F;$', + open(less_output).read(), re.MULTILINE | re.IGNORECASE)) diff --git a/tests/test_readers.py b/tests/test_readers.py index 7b9316b5..de2e9c32 100644 --- a/tests/test_readers.py +++ b/tests/test_readers.py @@ -61,3 +61,25 @@ class RstReaderTest(unittest.TestCase): self.assertEqual(content, expected) except ImportError: return unittest.skip('need the typogrify distribution') + +class MdReaderTest(unittest.TestCase): + + def test_article_with_md_extention(self): + # test to ensure the md extension is being processed by the correct reader + reader = readers.MarkdownReader({}) + content, metadata = reader.read(_filename('article_with_md_extension.md')) + expected = "The quick brown fox jumped over the lazy dog's back.
" + + self.assertEqual(content, expected) + + def test_article_with_mkd_extension(self): + # test to ensure the mkd extension is being processed by the correct reader + reader = readers.MarkdownReader({}) + content, metadata = reader.read(_filename('article_with_mkd_extension.mkd')) + expected = "This is another markdown test file. Uses the mkd extension.
" + + self.assertEqual(content, expected)
Comments !
- - -