From 0c69f4ad847c5f4b1dc06bd03d070b819a641d1f Mon Sep 17 00:00:00 2001 From: David Marble Date: Thu, 14 Nov 2013 12:29:01 -0800 Subject: [PATCH 0001/1173] Support ordering pages and articles when iterating in templates. Order can be set to a metadata attribute or a sorting function. Default to order by slug for articles and order by filename for pages. --- docs/settings.rst | 12 ++++++++++++ pelican/generators.py | 6 ++++-- pelican/settings.py | 2 ++ pelican/utils.py | 26 +++++++++++++++++++++++++- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 82752436..3f11437c 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -236,6 +236,11 @@ Setting name (default value) What does it do? ==================================================== ===================================================== `ARTICLE_URL` (``'{slug}.html'``) The URL to refer to an ARTICLE. `ARTICLE_SAVE_AS` (``'{slug}.html'``) The place where we will save an article. +`ARTICLE_ORDER_BY` (``'slug'``) The metadata attribute used to sort articles. By default + the articles_page.object_list template variable is + ordered by slug. If you modify this, make sure all + articles contain the attribute you specify. You can + also specify a sorting function. `ARTICLE_LANG_URL` (``'{slug}-{lang}.html'``) The URL to refer to an ARTICLE which doesn't use the default language. `ARTICLE_LANG_SAVE_AS` (``'{slug}-{lang}.html'``) The place where we will save an article which @@ -244,6 +249,13 @@ Setting name (default value) What does it do? `PAGE_SAVE_AS` (``'pages/{slug}.html'``) The location we will save the page. This value has to be the same as PAGE_URL or you need to use a rewrite in your server config. +`PAGE_ORDER_BY` (``'filename'``) The metadata attribute used to sort pages. By default + the PAGES template variable is ordered by filename + (path not included). Note that the option 'filename' + is a special option supported in the source code. If + you modify this settings, make sure all pages contain + the attribute you specify. You can also specify a + sorting function. `PAGE_LANG_URL` (``'pages/{slug}-{lang}.html'``) The URL we will use to link to a page which doesn't use the default language. `PAGE_LANG_SAVE_AS` (``'pages/{slug}-{lang}.html'``) The location we will save the page which doesn't diff --git a/pelican/generators.py b/pelican/generators.py index c55cdc37..2cdd4f89 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -409,7 +409,8 @@ class ArticlesGenerator(Generator): (repr(article.status), repr(f))) - self.articles, self.translations = process_translations(all_articles) + self.articles, self.translations = process_translations(all_articles, + order_by=self.settings['ARTICLE_ORDER_BY']) for article in self.articles: # only main articles are listed in categories and tags @@ -517,7 +518,8 @@ class PagesGenerator(Generator): (repr(page.status), repr(f))) - self.pages, self.translations = process_translations(all_pages) + self.pages, self.translations = process_translations(all_pages, + order_by=self.settings['PAGE_ORDER_BY']) self.hidden_pages, self.hidden_translations = ( process_translations(hidden_pages)) diff --git a/pelican/settings.py b/pelican/settings.py index 99828935..fbef91fc 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -57,10 +57,12 @@ DEFAULT_CONFIG = { 'OUTPUT_RETENTION': (), 'ARTICLE_URL': '{slug}.html', 'ARTICLE_SAVE_AS': '{slug}.html', + 'ARTICLE_ORDER_BY': 'slug', 'ARTICLE_LANG_URL': '{slug}-{lang}.html', 'ARTICLE_LANG_SAVE_AS': '{slug}-{lang}.html', 'PAGE_URL': 'pages/{slug}.html', 'PAGE_SAVE_AS': os.path.join('pages', '{slug}.html'), + 'PAGE_ORDER_BY': 'filename', 'PAGE_LANG_URL': 'pages/{slug}-{lang}.html', 'PAGE_LANG_SAVE_AS': os.path.join('pages', '{slug}-{lang}.html'), 'STATIC_URL': '{path}', diff --git a/pelican/utils.py b/pelican/utils.py index 4b25ec7f..e1a16fdb 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -430,7 +430,7 @@ def truncate_html_words(s, num, end_text='...'): return out -def process_translations(content_list): +def process_translations(content_list, order_by=None): """ Finds translation and returns them. Returns a tuple with two lists (index, translations). Index list includes @@ -440,6 +440,14 @@ def process_translations(content_list): the same slug have that metadata. For each content_list item, sets the 'translations' attribute. + + order_by can be a string of an attribute or sorting function. If order_by + is defined, content will be ordered by that attribute or sorting function. + By default, content is ordered by slug. + + Different content types can have default order_by attributes defined + in settings, e.g. PAGES_ORDER_BY='sort-order', in which case `sort-order` + should be a defined metadata attribute in each page. """ content_list.sort(key=attrgetter('slug')) grouped_by_slugs = groupby(content_list, attrgetter('slug')) @@ -485,6 +493,22 @@ def process_translations(content_list): translations.extend([x for x in items if x not in default_lang_items]) for a in items: a.translations = [x for x in items if x != a] + + if order_by: + if hasattr(order_by, '__call__'): + try: + index.sort(key=order_by) + except: + if hasattr(order_by, 'func_name'): + logger.error("Error sorting with function %s" % order_by.func_name) + else: + logger.error("Error sorting with function %r" % order_by) + elif order_by == 'filename': + index.sort(key=lambda x:os.path.basename( + x.source_path or '')) + elif order_by != 'slug': + index.sort(key=attrgetter(order_by)) + return index, translations From 69ff7dd634970c7aabab2d0f74fd34ff2108899a Mon Sep 17 00:00:00 2001 From: Mark Lee Date: Thu, 14 Nov 2013 12:37:22 -0800 Subject: [PATCH 0002/1173] Add test for PAGE_ORDER_BY --- .../TestPages/page_used_for_sorting_test.rst | 6 +++ pelican/tests/test_generators.py | 48 +++++++++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 pelican/tests/TestPages/page_used_for_sorting_test.rst diff --git a/pelican/tests/TestPages/page_used_for_sorting_test.rst b/pelican/tests/TestPages/page_used_for_sorting_test.rst new file mode 100644 index 00000000..40cdc7ea --- /dev/null +++ b/pelican/tests/TestPages/page_used_for_sorting_test.rst @@ -0,0 +1,6 @@ +A Page (Test) for sorting +######################### + +:slug: zzzz + +When using title, should be first. When using slug, should be last. diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 150d30f5..f8a5fc3f 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -7,6 +7,7 @@ try: from unittest.mock import MagicMock except ImportError: from mock import MagicMock +from operator import itemgetter from shutil import rmtree from tempfile import mkdtemp @@ -47,8 +48,12 @@ class TestArticlesGenerator(unittest.TestCase): context=settings.copy(), settings=settings, path=CONTENT_DIR, theme=settings['THEME'], output_path=None) cls.generator.generate_context() - cls.articles = [[page.title, page.status, page.category.name, - page.template] for page in cls.generator.articles] + cls.articles = cls.distill_articles(cls.generator.articles) + + @staticmethod + def distill_articles(articles): + return [[article.title, article.status, article.category.name, + article.template] for article in articles] def test_generate_feeds(self): settings = get_settings() @@ -223,7 +228,8 @@ class TestPageGenerator(unittest.TestCase): ['This is a test page', 'published', 'page'], ['This is a markdown test page', 'published', 'page'], ['This is a test page with a preset template', 'published', - 'custom'] + 'custom'], + ['A Page (Test) for sorting', 'published', 'page'], ] hidden_pages_expected = [ ['This is a test hidden page', 'hidden', 'page'], @@ -235,6 +241,42 @@ class TestPageGenerator(unittest.TestCase): self.assertEqual(sorted(pages_expected), sorted(pages)) self.assertEqual(sorted(hidden_pages_expected), sorted(hidden_pages)) + def test_generate_sorted(self): + settings = get_settings(filenames={}) + settings['PAGE_DIR'] = 'TestPages' # relative to CUR_DIR + settings['DEFAULT_DATE'] = (1970, 1, 1) + + # default sort (filename) + pages_expected_sorted_by_filename = [ + ['This is a test page', 'published', 'page'], + ['This is a markdown test page', 'published', 'page'], + ['A Page (Test) for sorting', 'published', 'page'], + ['This is a test page with a preset template', 'published', + 'custom'], + ] + generator = PagesGenerator( + context=settings.copy(), settings=settings, + path=CUR_DIR, theme=settings['THEME'], output_path=None) + generator.generate_context() + pages = self.distill_pages(generator.pages) + self.assertEqual(pages_expected_sorted_by_filename, pages) + + # sort by title + pages_expected_sorted_by_title = [ + ['A Page (Test) for sorting', 'published', 'page'], + ['This is a markdown test page', 'published', 'page'], + ['This is a test page', 'published', 'page'], + ['This is a test page with a preset template', 'published', + 'custom'], + ] + settings['PAGE_ORDER_BY'] = 'title' + generator = PagesGenerator( + context=settings.copy(), settings=settings, + path=CUR_DIR, theme=settings['THEME'], output_path=None) + generator.generate_context() + pages = self.distill_pages(generator.pages) + self.assertEqual(pages_expected_sorted_by_title, pages) + class TestTemplatePagesGenerator(unittest.TestCase): From 9a753f4fa97c6b086ba8129043b00d1fba55290e Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Fri, 9 May 2014 18:14:24 +0200 Subject: [PATCH 0003/1173] Fix intrasite links substitions in content The Content.__eq__ method would indirectly call _update_content too soon, resulting in failed intrasite links substitution This effectively reverts fd779267000ac539ee0a9ba5856d103fbbc7cd7c for pelican/contents.py, it was unnecessary anyways. Thanks to Strom for finding this. --- pelican/contents.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index c02047b8..615a7fd8 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -325,13 +325,6 @@ class Content(object): os.path.abspath(self.settings['PATH'])) ) - def __eq__(self, other): - """Compare with metadata and content of other Content object""" - return other and self.metadata == other.metadata and self.content == other.content - - # keep basic hashing functionality for caching to work - __hash__ = object.__hash__ - class Page(Content): mandatory_properties = ('title',) From 7cbf750329b6b6d42cfcca12449d6e6419fbf2fa Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Sat, 10 May 2014 17:38:58 +0200 Subject: [PATCH 0004/1173] Fix typo in command-line option description. --- pelican/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index d6417391..e9fef163 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -265,7 +265,7 @@ def parse_arguments(): parser.add_argument('-D', '--debug', action='store_const', const=logging.DEBUG, dest='verbosity', - help='Show all message, including debug messages.') + help='Show all messages, including debug messages.') parser.add_argument('--version', action='version', version=__version__, help='Print the pelican version and exit.') From 7313d327fb21fbdaa4c39819db6297f4cd635b2c Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 11 May 2014 18:14:58 -0700 Subject: [PATCH 0005/1173] Prepare for splitting up Getting Started docs --- docs/{getting_started.rst => content.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{getting_started.rst => content.rst} (100%) diff --git a/docs/getting_started.rst b/docs/content.rst similarity index 100% rename from docs/getting_started.rst rename to docs/content.rst From 86e11c619d0208d9bc8418821c2e2a31e6f991ea Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 12 May 2014 07:48:37 -0700 Subject: [PATCH 0006/1173] Split Getting Started docs into separate sections The "Getting Started" docs became overly long and unwieldy over time. This splits it into separate sections, including: * Quickstart * Installation * Writing content * Publish your site --- docs/content.rst | 307 ++-------------------------------------- docs/faq.rst | 12 +- docs/importer.rst | 6 +- docs/index.rst | 22 +-- docs/install.rst | 122 ++++++++++++++++ docs/pelican-themes.rst | 9 -- docs/publish.rst | 174 +++++++++++++++++++++++ docs/quickstart.rst | 73 ++++++++++ docs/settings.rst | 10 +- docs/themes.rst | 18 ++- 10 files changed, 420 insertions(+), 333 deletions(-) create mode 100644 docs/install.rst create mode 100644 docs/publish.rst create mode 100644 docs/quickstart.rst diff --git a/docs/content.rst b/docs/content.rst index 8ee37162..24fc6e9b 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -1,289 +1,8 @@ -Getting started +Writing content ############### -Installing Pelican -================== - -Pelican currently runs best on Python 2.7.x; earlier versions of Python are -not supported. There is provisional support for Python 3.3, although there may -be rough edges, particularly with regards to optional 3rd-party components. - -You can install Pelican via several different methods. The simplest is via -`pip `_:: - - $ pip install pelican - -If you don't have ``pip`` installed, an alternative method is -``easy_install``:: - - $ easy_install pelican - -(Keep in mind that operating systems will often require you to prefix the above -commands with ``sudo`` in order to install Pelican system-wide.) - -While the above is the simplest method, the recommended approach is to create -a virtual environment for Pelican via virtualenv_ before installing Pelican. -Assuming you have virtualenv_ installed, you can then open a new terminal -session and create a new virtual environment for Pelican:: - - $ virtualenv ~/virtualenvs/pelican - $ cd ~/virtualenvs/pelican - $ . bin/activate - -Once the virtual environment has been created and activated, Pelican can be -be installed via ``pip install pelican`` as noted above. Alternatively, if -you have the project source, you can install Pelican using the distutils -method:: - - $ cd path-to-Pelican-source - $ python setup.py install - -If you have Git installed and prefer to install the latest bleeding-edge -version of Pelican rather than a stable release, use the following command:: - - $ pip install -e git+https://github.com/getpelican/pelican.git#egg=pelican - -If you plan on using Markdown as a markup format, you'll need to install the -Markdown library as well:: - - $ pip install Markdown - -If you want to use AsciiDoc_ you need to install it from `source -`_ or use your operating -system's package manager. - -Basic usage ------------ - -Once Pelican is installed, you can use it to convert your Markdown or reST -content into HTML via the ``pelican`` command, specifying the path to your -content and (optionally) the path to your settings file:: - -$ pelican /path/to/your/content/ [-s path/to/your/settings.py] - -The above command will generate your site and save it in the ``output/`` -folder, using the default theme to produce a simple site. The default theme -consists of very simple HTML without styling and is provided so folks may use -it as a basis for creating their own themes. - -You can also tell Pelican to watch for your modifications, instead of -manually re-running it every time you want to see your changes. To enable this, -run the ``pelican`` command with the ``-r`` or ``--autoreload`` option. - -Pelican has other command-line switches available. Have a look at the help to -see all the options you can use:: - - $ pelican --help - -Continue reading below for more detail, and check out the Pelican wiki's -`Tutorials `_ page for -links to community-published tutorials. - -Viewing the generated files ---------------------------- - -The files generated by Pelican are static files, so you don't actually need -anything special to view them. You can use your browser to open the generated -HTML files directly:: - - firefox output/index.html - -Because the above method may have trouble locating your CSS and other linked -assets, running a simple web server using Python will often provide a more -reliable previewing experience:: - - cd output && python -m SimpleHTTPServer - -Once the ``SimpleHTTPServer`` has been started, you can preview your site at -http://localhost:8000/ - -Upgrading ---------- - -If you installed a stable Pelican release via ``pip`` or ``easy_install`` and -wish to upgrade to the latest stable release, you can do so by adding -``--upgrade`` to the relevant command. For pip, that would be:: - - $ pip install --upgrade pelican - -If you installed Pelican via distutils or the bleeding-edge method, simply -perform the same step to install the most recent version. - -Dependencies ------------- - -When Pelican is installed, the following dependent Python packages should be -automatically installed without any action on your part: - -* `feedgenerator `_, to generate the - Atom feeds -* `jinja2 `_, for templating support -* `pygments `_, for syntax highlighting -* `docutils `_, for supporting - reStructuredText as an input format -* `pytz `_, for timezone definitions -* `blinker `_, an object-to-object and - broadcast signaling system -* `unidecode `_, for ASCII - transliterations of Unicode text -* `six `_, for Python 2 and 3 compatibility - utilities -* `MarkupSafe `_, for a markup safe - string implementation -* `python-dateutil `_, to read - the date metadata - -If you want the following optional packages, you will need to install them -manually via ``pip``: - -* `markdown `_, for supporting Markdown as - an input format -* `typogrify `_, for - typographical enhancements - -Kickstart your site -=================== - -Once Pelican has been installed, you can create a skeleton project via the -``pelican-quickstart`` command, which begins by asking some questions about -your site:: - - $ pelican-quickstart - -Once you finish answering all the questions, your project will consist of the -following hierarchy (except for "pages", which you can optionally add yourself -if you plan to create non-chronological content):: - - yourproject/ - ├── content - │   └── (pages) - ├── output - ├── develop_server.sh - ├── fabfile.py - ├── Makefile - ├── pelicanconf.py # Main settings file - └── publishconf.py # Settings to use when ready to publish - -The next step is to begin to adding content to the *content* folder that has -been created for you. (See the **Writing content using Pelican** section below -for more information about how to format your content.) - -Once you have written some content to generate, you can use the ``pelican`` -command to generate your site, which will be placed in the output folder. - -Automation tools -================ - -While the ``pelican`` command is the canonical way to generate your site, -automation tools can be used to streamline the generation and publication -flow. One of the questions asked during the ``pelican-quickstart`` process -described above pertains to whether you want to automate site generation and -publication. If you answered "yes" to that question, a ``fabfile.py`` and -``Makefile`` will be generated in the root of your project. These files, -pre-populated with certain information gleaned from other answers provided -during the ``pelican-quickstart`` process, are meant as a starting point and -should be customized to fit your particular needs and usage patterns. If you -find one or both of these automation tools to be of limited utility, these -files can deleted at any time and will not affect usage of the canonical -``pelican`` command. - -Following are automation tools that "wrap" the ``pelican`` command and can -simplify the process of generating, previewing, and uploading your site. - -Fabric ------- - -The advantage of Fabric_ is that it is written in Python and thus can be used -in a wide range of environments. The downside is that it must be installed -separately. Use the following command to install Fabric, prefixing with -``sudo`` if your environment requires it:: - - $ pip install Fabric - -Take a moment to open the ``fabfile.py`` file that was generated in your -project root. You will see a number of commands, any one of which can be -renamed, removed, and/or customized to your liking. Using the out-of-the-box -configuration, you can generate your site via:: - - $ fab build - -If you'd prefer to have Pelican automatically regenerate your site every time a -change is detected (which is handy when testing locally), use the following -command instead:: - - $ fab regenerate - -To serve the generated site so it can be previewed in your browser at -http://localhost:8000/:: - - $ fab serve - -If during the ``pelican-quickstart`` process you answered "yes" when asked -whether you want to upload your site via SSH, you can use the following command -to publish your site via rsync over SSH:: - - $ fab publish - -These are just a few of the commands available by default, so feel free to -explore ``fabfile.py`` and see what other commands are available. More -importantly, don't hesitate to customize ``fabfile.py`` to suit your specific -needs and preferences. - -Make ----- - -A ``Makefile`` is also automatically created for you when you say "yes" to -the relevant question during the ``pelican-quickstart`` process. The advantage -of this method is that the ``make`` command is built into most POSIX systems -and thus doesn't require installing anything else in order to use it. The -downside is that non-POSIX systems (e.g., Windows) do not include ``make``, -and installing it on those systems can be a non-trivial task. - -If you want to use ``make`` to generate your site, run:: - - $ make html - -If you'd prefer to have Pelican automatically regenerate your site every time a -change is detected (which is handy when testing locally), use the following -command instead:: - - $ make regenerate - -To serve the generated site so it can be previewed in your browser at -http://localhost:8000/:: - - $ make serve - -Normally you would need to run ``make regenerate`` and ``make serve`` in two -separate terminal sessions, but you can run both at once via:: - - $ make devserver - -The above command will simultaneously run Pelican in regeneration mode as well -as serve the output at http://localhost:8000. Once you are done testing your -changes, you should stop the development server via:: - - $ ./develop_server.sh stop - -When you're ready to publish your site, you can upload it via the method(s) you -chose during the ``pelican-quickstart`` questionnaire. For this example, we'll -use rsync over ssh:: - - $ make rsync_upload - -That's it! Your site should now be live. - -(The default ``Makefile`` and ``devserver.sh`` scripts use the ``python`` and -``pelican`` executables to complete its tasks. If you want to use different -executables, such as ``python3``, you can set the ``PY`` and ``PELICAN`` -environment variables, respectively, to override the default executable names.) - - -Writing content using Pelican -============================= - Articles and pages ------------------- +================== Pelican considers "articles" to be chronological content, such as posts on a blog, and thus associated with a date. @@ -295,7 +14,7 @@ pages). .. _internal_metadata: File metadata -------------- +============= Pelican tries to be smart enough to get the information it needs from the file system (for instance, about the category of your articles), but some @@ -400,7 +119,7 @@ Please note that the metadata available inside your files takes precedence over the metadata extracted from the filename. Pages ------ +===== If you create a folder named ``pages`` inside the content folder, all the files in it will be used to generate static pages, such as **About** or @@ -416,7 +135,7 @@ things like making error pages that fit the generated theme of your site. .. _ref-linking-to-internal-content: Linking to internal content ---------------------------- +=========================== From Pelican 3.1 onwards, it is now possible to specify intra-site links to files in the *source content* hierarchy instead of files in the *generated* @@ -494,14 +213,14 @@ curly braces (``{}``). For example: ``|filename|an_article.rst``, ``|tag|tagname``, ``|category|foobar``. The syntax was changed from ``||`` to ``{}`` to avoid collision with Markdown extensions or reST directives. -Importing an existing blog --------------------------- +Importing an existing site +========================== -It is possible to import your blog from Dotclear, WordPress, and RSS feeds using -a simple script. See :ref:`import`. +It is possible to import your site from WordPress, Tumblr, Dotclear, and RSS +feeds using a simple script. See :ref:`import`. Translations ------------- +============ It is possible to translate articles. To do so, you need to add a ``lang`` meta attribute to your articles/pages and set a ``DEFAULT_LANG`` setting (which is @@ -559,7 +278,7 @@ which posts are translations:: .. _internal_pygments_options: Syntax highlighting -------------------- +=================== Pelican is able to provide colorized syntax highlighting for your code blocks. To do so, you have to use the following conventions inside your content files. @@ -641,14 +360,12 @@ If specified, settings for individual code blocks will override the defaults in your settings file. Publishing drafts ------------------ +================= If you want to publish an article as a draft (for friends to review before publishing, for example), you can add a ``Status: draft`` attribute to its metadata. That article will then be output to the ``drafts`` folder and not listed on the index page nor on any category or tag page. -.. _virtualenv: http://www.virtualenv.org/ .. _W3C ISO 8601: http://www.w3.org/TR/NOTE-datetime -.. _Fabric: http://fabfile.org/ .. _AsciiDoc: http://www.methods.co.nz/asciidoc/ diff --git a/docs/faq.rst b/docs/faq.rst index bf468c51..3a5cfec5 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -65,9 +65,11 @@ How do I create my own theme? Please refer to :ref:`theming-pelican`. -I'm using Markdown and getting ``No valid files found in content`` errors. +I want to use Markdown, but I got an error. ========================================================================== +If you try to generate Markdown content without first installing the Markdown +library, may see a message that says ``No valid files found in content``. Markdown is not a hard dependency for Pelican, so if you have content in Markdown format, you will need to explicitly install the Markdown library. You can do so by typing the following command, prepending ``sudo`` if @@ -75,10 +77,6 @@ permissions require it:: pip install markdown -If you don't have ``pip`` installed, consider installing it via:: - - easy_install pip - Can I use arbitrary metadata in my templates? ============================================== @@ -157,8 +155,8 @@ disable all feed generation, you only need to specify the following settings:: CATEGORY_FEED_ATOM = None TRANSLATION_FEED_ATOM = None -Please note that ``None`` and ``''`` are not the same thing. The word ``None`` -should not be surrounded by quotes. +The word ``None`` should not be surrounded by quotes. Please note that ``None`` +and ``''`` are not the same thing. I'm getting a warning about feeds generated without SITEURL being set properly ============================================================================== diff --git a/docs/importer.rst b/docs/importer.rst index b1d1b926..309ca144 100644 --- a/docs/importer.rst +++ b/docs/importer.rst @@ -1,9 +1,7 @@ .. _import: -================================= - Import from other blog software -================================= - +Importing an existing site +########################## Description =========== diff --git a/docs/index.rst b/docs/index.rst index aa30b1f0..36a3282b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,30 +10,32 @@ Pelican |release| Were you looking for version |last_stable| documentation? -Pelican is a static site generator, written in Python_. +Pelican is a static site generator, written in Python_. Highlights include: -* Write your content directly with your editor of choice (vim!) +* Write your content directly with your editor of choice in reStructuredText_, Markdown_, or AsciiDoc_ formats * Includes a simple CLI tool to (re)generate your site * Easy to interface with distributed version control systems and web hooks * Completely static output is easy to host anywhere +Ready to get started? Check out the :doc:`Quickstart` guide. + Features -------- Pelican |version| currently supports: * Articles (e.g., blog posts) and pages (e.g., "About", "Projects", "Contact") -* Comments, via an external service (Disqus). (Please note that while - useful, Disqus is an external service, and thus the comment data will be - somewhat outside of your control and potentially subject to data loss.) +* Comments, via an external service (Disqus). If you prefer to have more + control over your comment data, self-hosted comments are another option. + Check out the `Pelican Plugins`_ repository for more details. * Theming support (themes are created using Jinja2_ templates) * Publication of articles in multiple languages * Atom/RSS feeds * Code syntax highlighting * Import from WordPress, Dotclear, or RSS feeds * Integration with external tools: Twitter, Google Analytics, etc. (optional) -* Fast rebuild times thanks to content caching and selective output writing. +* Fast rebuild times thanks to content caching and selective output writing Why the name "Pelican"? ----------------------- @@ -66,16 +68,19 @@ Documentation .. toctree:: :maxdepth: 2 - getting_started + quickstart + install + content + publish settings themes plugins - internals pelican-themes importer faq tips contribute + internals report changelog @@ -88,5 +93,6 @@ Documentation .. _Jinja2: http://jinja.pocoo.org/ .. _`Pelican documentation`: http://docs.getpelican.com/latest/ .. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html +.. _`Pelican plugins`: https://github.com/getpelican/pelican-plugins .. _`#pelican on Freenode`: irc://irc.freenode.net/pelican .. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4 diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 00000000..34cd33ea --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,122 @@ +Installing Pelican +################## + +Pelican currently runs best on Python 2.7.x; earlier versions of Python are +not supported. There is provisional support for Python 3.3+, although there may +be rough edges, particularly with regards to optional 3rd-party components. + +You can install Pelican via several different methods. The simplest is via +`pip `_:: + + pip install pelican + +(Keep in mind that operating systems will often require you to prefix the above +command with ``sudo`` in order to install Pelican system-wide.) + +While the above is the simplest method, the recommended approach is to create +a virtual environment for Pelican via virtualenv_ before installing Pelican. +Assuming you have virtualenv_ installed, you can then open a new terminal +session and create a new virtual environment for Pelican:: + + virtualenv ~/virtualenvs/pelican + cd ~/virtualenvs/pelican + source bin/activate + +Once the virtual environment has been created and activated, Pelican can be +be installed via ``pip install pelican`` as noted above. Alternatively, if +you have the project source, you can install Pelican using the distutils +method:: + + cd path-to-Pelican-source + python setup.py install + +If you have Git installed and prefer to install the latest bleeding-edge +version of Pelican rather than a stable release, use the following command:: + + pip install -e "git+https://github.com/getpelican/pelican.git#egg=pelican" + +Once Pelican is installed, you can run ``pelican --help`` to see basic usage +options. For more detail, refer to the :doc:`Publish` section. + +Optional packages +----------------- + +If you plan on using `Markdown `_ as a +markup format, you'll need to install the Markdown library:: + + pip install Markdown + +Typographical enhancements can be enabled in your settings file, but first the +requisite `Typogrify `_ library must be +installed:: + + pip install typogrify + +If you want to use AsciiDoc_ you need to install it from `source +`_ or use your operating +system's package manager. + +Dependencies +------------ + +When Pelican is installed, the following dependent Python packages should be +automatically installed without any action on your part: + +* `feedgenerator `_, to generate the + Atom feeds +* `jinja2 `_, for templating support +* `pygments `_, for syntax highlighting +* `docutils `_, for supporting + reStructuredText as an input format +* `pytz `_, for timezone definitions +* `blinker `_, an object-to-object and + broadcast signaling system +* `unidecode `_, for ASCII + transliterations of Unicode text +* `six `_, for Python 2 and 3 compatibility + utilities +* `MarkupSafe `_, for a markup safe + string implementation +* `python-dateutil `_, to read + the date metadata + +Upgrading +--------- + +If you installed a stable Pelican release via ``pip`` or ``easy_install`` and +wish to upgrade to the latest stable release, you can do so by adding +``--upgrade`` to the relevant command. For pip, that would be:: + + pip install --upgrade pelican + +If you installed Pelican via distutils or the bleeding-edge method, simply +perform the same step to install the most recent version. + +Kickstart your site +=================== + +Once Pelican has been installed, you can create a skeleton project via the +``pelican-quickstart`` command, which begins by asking some questions about +your site:: + + pelican-quickstart + +Once you finish answering all the questions, your project will consist of the +following hierarchy (except for *pages* — shown in parentheses below — which you +can optionally add yourself if you plan to create non-chronological content):: + + yourproject/ + ├── content + │   └── (pages) + ├── output + ├── develop_server.sh + ├── fabfile.py + ├── Makefile + ├── pelicanconf.py # Main settings file + └── publishconf.py # Settings to use when ready to publish + +The next step is to begin to adding content to the *content* folder that has +been created for you. + +.. _virtualenv: http://www.virtualenv.org/ +.. _AsciiDoc: http://www.methods.co.nz/asciidoc/ diff --git a/docs/pelican-themes.rst b/docs/pelican-themes.rst index 23be8355..7090c648 100644 --- a/docs/pelican-themes.rst +++ b/docs/pelican-themes.rst @@ -153,12 +153,3 @@ The ``--install``, ``--remove`` and ``--symlink`` option are not mutually exclus --verbose In this example, the theme ``notmyidea-cms`` is replaced by the theme ``notmyidea-cms-fr`` - - - - -See also -======== - -- http://docs.notmyidea.org/alexis/pelican/ -- ``/usr/share/doc/pelican/`` if you have installed Pelican using the `APT repository `_ diff --git a/docs/publish.rst b/docs/publish.rst new file mode 100644 index 00000000..266009e4 --- /dev/null +++ b/docs/publish.rst @@ -0,0 +1,174 @@ +Publish your site +################# + +Site generation +=============== + +Once Pelican is installed and you have some content (e.g., in Markdown or reST +format), you can convert your content into HTML via the ``pelican`` command, +specifying the path to your content and (optionally) the path to your +:doc:`settings` file:: + + pelican /path/to/your/content/ [-s path/to/your/settings.py] + +The above command will generate your site and save it in the ``output/`` +folder, using the default theme to produce a simple site. The default theme +consists of very simple HTML without styling and is provided so folks may use +it as a basis for creating their own themes. + +You can also tell Pelican to watch for your modifications, instead of +manually re-running it every time you want to see your changes. To enable this, +run the ``pelican`` command with the ``-r`` or ``--autoreload`` option. + +Pelican has other command-line switches available. Have a look at the help to +see all the options you can use:: + + pelican --help + +Viewing the generated files +--------------------------- + +The files generated by Pelican are static files, so you don't actually need +anything special to view them. You can use your browser to open the generated +HTML files directly:: + + firefox output/index.html + +Because the above method may have trouble locating your CSS and other linked +assets, running a simple web server using Python will often provide a more +reliable previewing experience:: + + cd output + python -m SimpleHTTPServer + +Once the ``SimpleHTTPServer`` has been started, you can preview your site at +http://localhost:8000/ + +Deployment +========== + +After you have generated your site, previewed it in your local development +environment, and are ready to deploy it to production, you might first +re-generate your site with any production-specific settings (e.g., analytics +feeds, etc.) that you may have defined:: + + pelican content -s publishconf.py + +The steps for deploying your site will depend on where it will be hosted. +If you have SSH access to a server running Nginx or Apache, you might use the +``rsync`` tool to transmit your site files:: + + rsync --avc --delete output/ host.example.com:/var/www/your-site/ + +There are many other deployment options, some of which can be configured when +first setting up your site via the ``pelican-quickstart`` command. See the +:doc:`Tips` page for detail on publishing via GitHub Pages. + +Automation +========== + +While the ``pelican`` command is the canonical way to generate your site, +automation tools can be used to streamline the generation and publication +flow. One of the questions asked during the ``pelican-quickstart`` process +pertains to whether you want to automate site generation and publication. +If you answered "yes" to that question, a ``fabfile.py`` and +``Makefile`` will be generated in the root of your project. These files, +pre-populated with certain information gleaned from other answers provided +during the ``pelican-quickstart`` process, are meant as a starting point and +should be customized to fit your particular needs and usage patterns. If you +find one or both of these automation tools to be of limited utility, these +files can deleted at any time and will not affect usage of the canonical +``pelican`` command. + +Following are automation tools that "wrap" the ``pelican`` command and can +simplify the process of generating, previewing, and uploading your site. + +Fabric +------ + +The advantage of Fabric_ is that it is written in Python and thus can be used +in a wide range of environments. The downside is that it must be installed +separately. Use the following command to install Fabric, prefixing with +``sudo`` if your environment requires it:: + + pip install Fabric + +Take a moment to open the ``fabfile.py`` file that was generated in your +project root. You will see a number of commands, any one of which can be +renamed, removed, and/or customized to your liking. Using the out-of-the-box +configuration, you can generate your site via:: + + fab build + +If you'd prefer to have Pelican automatically regenerate your site every time a +change is detected (which is handy when testing locally), use the following +command instead:: + + fab regenerate + +To serve the generated site so it can be previewed in your browser at +http://localhost:8000/:: + + fab serve + +If during the ``pelican-quickstart`` process you answered "yes" when asked +whether you want to upload your site via SSH, you can use the following command +to publish your site via rsync over SSH:: + + fab publish + +These are just a few of the commands available by default, so feel free to +explore ``fabfile.py`` and see what other commands are available. More +importantly, don't hesitate to customize ``fabfile.py`` to suit your specific +needs and preferences. + +Make +---- + +A ``Makefile`` is also automatically created for you when you say "yes" to +the relevant question during the ``pelican-quickstart`` process. The advantage +of this method is that the ``make`` command is built into most POSIX systems +and thus doesn't require installing anything else in order to use it. The +downside is that non-POSIX systems (e.g., Windows) do not include ``make``, +and installing it on those systems can be a non-trivial task. + +If you want to use ``make`` to generate your site, run:: + + make html + +If you'd prefer to have Pelican automatically regenerate your site every time a +change is detected (which is handy when testing locally), use the following +command instead:: + + make regenerate + +To serve the generated site so it can be previewed in your browser at +http://localhost:8000/:: + + make serve + +Normally you would need to run ``make regenerate`` and ``make serve`` in two +separate terminal sessions, but you can run both at once via:: + + make devserver + +The above command will simultaneously run Pelican in regeneration mode as well +as serve the output at http://localhost:8000. Once you are done testing your +changes, you should stop the development server via:: + + ./develop_server.sh stop + +When you're ready to publish your site, you can upload it via the method(s) you +chose during the ``pelican-quickstart`` questionnaire. For this example, we'll +use rsync over ssh:: + + make rsync_upload + +That's it! Your site should now be live. + +(The default ``Makefile`` and ``devserver.sh`` scripts use the ``python`` and +``pelican`` executables to complete its tasks. If you want to use different +executables, such as ``python3``, you can set the ``PY`` and ``PELICAN`` +environment variables, respectively, to override the default executable names.) + +.. _Fabric: http://fabfile.org/ diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 00000000..44f99dd2 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,73 @@ +Quickstart +########## + +Reading through all the documentation is highly recommended, but for the truly +impatient, following are some quick steps to get started. + +Installation +------------ + +Install Pelican on Python 2.7.x or Python 3.3+ by running the following command +in your preferred terminal, prefixing with ``sudo`` if permissions warrant:: + + pip install pelican markdown + +Create a project +---------------- + +First, choose a name for your project, create an appropriately-named directory +for your it, and switch to that directory:: + + mkdir -p ~/projects/yoursite + cd ~/projects/yoursite + +Create a skeleton project via the ``pelican-quickstart`` command, which begins +by asking some questions about your site:: + + pelican-quickstart + +For questions that have default values denoted in brackets, feel free to use +the Return key to accept those default values. When asked for your URL prefix, +enter your domain name as indicated (e.g., ``http://example.com``). + +Create an article +----------------- + +You cannot run Pelican until you have created some content. Use your preferred +text editor to create your first article with the following content:: + + Title: My First Review + Date: 2010-12-03 10:20 + Category: Review + + Following is a review of my favorite mechanical keyboard. + +Given that this example article is in Markdown format, save it as +``~/projects/yoursite/content/keyboard-review.md``. + +Generate your site +------------------ + +From your project directory, run the ``pelican`` command to generate your site:: + + pelican content + +Your site has now been generated inside the ``output`` directory. (You may see a +warning related to feeds, but that is normal when developing locally and can be +ignored for now.) + +Preview your site +----------------- + +Open a new terminal session and run the following commands to switch to your +``output`` directory and launch Python's built-in web server:: + + cd ~/projects/yoursite/output + python -m SimpleHTTPServer + +Preview your site by navigating to http://localhost:8000/ in your browser. + +Continue reading the other documentation sections for more detail, and check out +the Pelican wiki's Tutorials_ page for links to community-published tutorials. + +.. _Tutorials: https://github.com/getpelican/pelican/wiki/Tutorials diff --git a/docs/settings.rst b/docs/settings.rst index 2782977c..7a69784e 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -1,10 +1,10 @@ Settings ######## -Pelican is configurable thanks to a configuration file you can pass to +Pelican is configurable thanks to a settings file you can pass to the command line:: - $ pelican content -s path/to/your/settingsfile.py + pelican content -s path/to/your/settingsfile.py (If you used the ``pelican-quickstart`` command, your primary settings file will be named ``pelicanconf.py`` by default.) @@ -201,8 +201,8 @@ for URL formation: *relative* and *absolute*. Relative URLs are useful when testing locally, and absolute URLs are reliable and most useful when publishing. One method of supporting both is to have one Pelican configuration file for local development and another for publishing. To see an example of this -type of setup, use the ``pelican-quickstart`` script as described at the top of -the :doc:`Getting Started ` page, which will produce two separate +type of setup, use the ``pelican-quickstart`` script as described in the +:doc:`Installation ` section, which will produce two separate configuration files for local development and publishing, respectively. You can customize the URLs and locations where files will be saved. The @@ -603,7 +603,7 @@ For example:: Translations ============ -Pelican offers a way to translate articles. See the :doc:`Getting Started ` section for +Pelican offers a way to translate articles. See the :doc:`Content ` section for more information. ======================================================== ===================================================== diff --git a/docs/themes.rst b/docs/themes.rst index b029e816..7fcba671 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -1,13 +1,21 @@ .. _theming-pelican: -How to create themes for Pelican -################################ +Creating themes +############### -Pelican uses the great `Jinja2 `_ templating engine to -generate its HTML output. Jinja2 syntax is really simple. If you want to -create your own theme, feel free to take inspiration from the `"simple" theme +To generate its HTML output, Pelican uses the `Jinja `_ +templating engine due to its flexibility and straightforward syntax. If you want +to create your own theme, feel free to take inspiration from the `"simple" theme `_. +To generate your site using a theme you have created (or downloaded manually and +then modified), you can specify that theme via the ``-t`` flag:: + + pelican content -s pelicanconf.py -t /projects/your-site/themes/your-theme + +If you'd rather not specify the theme on every invocation, you can define +``THEME`` in your settings to point to the location of your preferred theme. + Structure ========= From 9d2a129832174c8a65c615148cfbc0a650ecb85c Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 13 May 2014 07:18:33 -0700 Subject: [PATCH 0007/1173] If PATH is undefined, Pelican uses PWD as content --- docs/settings.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 7a69784e..4701e92d 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -94,7 +94,7 @@ Setting name (followed by default value, if any) ``READERS = {}`` A dictionary of file extensions / Reader classes for Pelican to process or ignore. For example, to avoid processing .html files, set: ``READERS = {'html': None}``. To add a custom reader for the - `foo` extension, set: ``READERS = {'foo': FooReader}`` + ``foo`` extension, set: ``READERS = {'foo': FooReader}`` ``IGNORE_FILES = ['.#*']`` A list of file globbing patterns to match against the source files to be ignored by the processor. For example, the default ``['.#*']`` will ignore emacs lock files. @@ -108,10 +108,12 @@ Setting name (followed by default value, if any) include them explicitly and enumerate the full list of desired Markdown extensions.) ``OUTPUT_PATH = 'output/'`` Where to output the generated files. -``PATH = None`` Path to content directory to be processed by Pelican. -``PAGE_DIR = 'pages'`` Directory to look at for pages, relative to `PATH`. +``PATH`` Path to content directory to be processed by Pelican. If undefined, + and content path is not specified via an argument to the ``pelican`` + command, Pelican will use the current working directory. +``PAGE_DIR = 'pages'`` Directory to look at for pages, relative to ``PATH``. ``PAGE_EXCLUDES = ()`` A list of directories to exclude when looking for pages. -``ARTICLE_DIR = ''`` Directory to look at for articles, relative to `PATH`. +``ARTICLE_DIR = ''`` Directory to look at for articles, relative to ``PATH``. ``ARTICLE_EXCLUDES = ('pages',)`` A list of directories to exclude when looking for articles. ``OUTPUT_SOURCES = False`` Set to True if you want to copy the articles and pages in their original format (e.g. Markdown or reStructuredText) to the From bf6a4ad74741a954fecefa4536637190ef23fb5e Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Wed, 14 May 2014 11:11:58 +0200 Subject: [PATCH 0008/1173] Add missing methods --- pelican/tests/test_generators.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 659383ac..07871cef 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -58,6 +58,12 @@ class TestArticlesGenerator(unittest.TestCase): cls.generator.generate_context() cls.articles = cls.distill_articles(cls.generator.articles) + def setUp(self): + self.temp_cache = mkdtemp(prefix='pelican_cache.') + + def tearDown(self): + rmtree(self.temp_cache) + @staticmethod def distill_articles(articles): return [[article.title, article.status, article.category.name, From d635a347d1a5bb7336edb712356237b141efb4d1 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Mon, 21 Apr 2014 11:36:17 +0200 Subject: [PATCH 0009/1173] move {ARTICLE,PAGE}_DIR -> {ARTICLE,PAGE}_PATHS Instead of one path a list can be given. This is due to popular request. Should help people not wanting to use Pelican for blogging. Maintain backward compatibility though. Thanks to @ingwinlu for pointing out the change in StaticGenerator. --- docs/settings.rst | 4 +-- pelican/generators.py | 60 +++++++++++++++----------------- pelican/settings.py | 26 ++++++++++---- pelican/tests/test_generators.py | 2 +- 4 files changed, 51 insertions(+), 41 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 4701e92d..33117a7f 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -111,9 +111,9 @@ Setting name (followed by default value, if any) ``PATH`` Path to content directory to be processed by Pelican. If undefined, and content path is not specified via an argument to the ``pelican`` command, Pelican will use the current working directory. -``PAGE_DIR = 'pages'`` Directory to look at for pages, relative to ``PATH``. +``PAGE_PATHS = ['pages']`` A list of directories to look at for pages, relative to ``PATH``. ``PAGE_EXCLUDES = ()`` A list of directories to exclude when looking for pages. -``ARTICLE_DIR = ''`` Directory to look at for articles, relative to ``PATH``. +``ARTICLE_PATHS = ['']`` A list of directories to look at for articles, relative to ``PATH``. ``ARTICLE_EXCLUDES = ('pages',)`` A list of directories to exclude when looking for articles. ``OUTPUT_SOURCES = False`` Set to True if you want to copy the articles and pages in their original format (e.g. Markdown or reStructuredText) to the diff --git a/pelican/generators.py b/pelican/generators.py index 7c6ba66b..deb237a2 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -110,29 +110,30 @@ class Generator(object): return True return False - def get_files(self, path, exclude=[], extensions=None): + def get_files(self, paths, exclude=[], extensions=None): """Return a list of files to use, based on rules - :param path: the path to search (relative to self.path) + :param paths: the list pf paths to search (relative to self.path) :param exclude: the list of path to exclude :param extensions: the list of allowed extensions (if False, all extensions are allowed) """ files = [] - root = os.path.join(self.path, path) + for path in paths: + root = os.path.join(self.path, path) - if os.path.isdir(root): - for dirpath, dirs, temp_files in os.walk(root, followlinks=True): - for e in exclude: - if e in dirs: - dirs.remove(e) - reldir = os.path.relpath(dirpath, self.path) - for f in temp_files: - fp = os.path.join(reldir, f) - if self._include_path(fp, extensions): - files.append(fp) - elif os.path.exists(root) and self._include_path(path, extensions): - files.append(path) # can't walk non-directories + if os.path.isdir(root): + for dirpath, dirs, temp_files in os.walk(root, followlinks=True): + for e in exclude: + if e in dirs: + dirs.remove(e) + reldir = os.path.relpath(dirpath, self.path) + for f in temp_files: + fp = os.path.join(reldir, f) + if self._include_path(fp, extensions): + files.append(fp) + elif os.path.exists(root) and self._include_path(path, extensions): + files.append(path) # can't walk non-directories return files def add_source_path(self, content): @@ -462,7 +463,7 @@ class ArticlesGenerator(CachingGenerator): all_articles = [] all_drafts = [] for f in self.get_files( - self.settings['ARTICLE_DIR'], + self.settings['ARTICLE_PATHS'], exclude=self.settings['ARTICLE_EXCLUDES']): article = self.get_cached_data(f, None) if article is None: @@ -586,7 +587,7 @@ class PagesGenerator(CachingGenerator): all_pages = [] hidden_pages = [] for f in self.get_files( - self.settings['PAGE_DIR'], + self.settings['PAGE_PATHS'], exclude=self.settings['PAGE_EXCLUDES']): page = self.get_cached_data(f, None) if page is None: @@ -660,20 +661,17 @@ class StaticGenerator(Generator): def generate_context(self): self.staticfiles = [] - - # walk static paths - for static_path in self.settings['STATIC_PATHS']: - for f in self.get_files( - static_path, extensions=False): - static = self.readers.read_file( - base_path=self.path, path=f, content_class=Static, - fmt='static', context=self.context, - preread_signal=signals.static_generator_preread, - preread_sender=self, - context_signal=signals.static_generator_context, - context_sender=self) - self.staticfiles.append(static) - self.add_source_path(static) + for f in self.get_files(self.settings['STATIC_PATHS'], + extensions=False): + static = self.readers.read_file( + base_path=self.path, path=f, content_class=Static, + fmt='static', context=self.context, + preread_signal=signals.static_generator_preread, + preread_sender=self, + context_signal=signals.static_generator_context, + context_sender=self) + self.staticfiles.append(static) + self.add_source_path(static) self._update_context(('staticfiles',)) signals.static_generator_finalized.send(self) diff --git a/pelican/settings.py b/pelican/settings.py index f759ff9e..219ebbd0 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -29,9 +29,9 @@ DEFAULT_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'themes', 'notmyidea') DEFAULT_CONFIG = { 'PATH': os.curdir, - 'ARTICLE_DIR': '', + 'ARTICLE_PATHS': [''], 'ARTICLE_EXCLUDES': ('pages',), - 'PAGE_DIR': 'pages', + 'PAGE_PATHS': ['pages'], 'PAGE_EXCLUDES': (), 'THEME': DEFAULT_THEME, 'OUTPUT_PATH': 'output', @@ -311,6 +311,16 @@ def configure_settings(settings): key=lambda r: r[0], ) + # move {ARTICLE,PAGE}_DIR -> {ARTICLE,PAGE}_PATHS + for key in ['ARTICLE', 'PAGE']: + old_key = key + '_DIR' + new_key = key + '_PATHS' + if old_key in settings: + logger.warning('Deprecated setting {}, moving it to {} list'.format( + old_key, new_key)) + settings[new_key] = [settings[old_key]] # also make a list + del settings[old_key] + # Save people from accidentally setting a string rather than a list path_keys = ( 'ARTICLE_EXCLUDES', @@ -324,13 +334,15 @@ def configure_settings(settings): 'PLUGINS', 'STATIC_PATHS', 'THEME_STATIC_PATHS', + 'ARTICLE_PATHS', + 'PAGE_PATHS', ) for PATH_KEY in filter(lambda k: k in settings, path_keys): - if isinstance(settings[PATH_KEY], six.string_types): - logger.warning("Detected misconfiguration with %s setting " - "(must be a list), falling back to the default" - % PATH_KEY) - settings[PATH_KEY] = DEFAULT_CONFIG[PATH_KEY] + if isinstance(settings[PATH_KEY], six.string_types): + logger.warning("Detected misconfiguration with %s setting " + "(must be a list), falling back to the default" + % PATH_KEY) + settings[PATH_KEY] = DEFAULT_CONFIG[PATH_KEY] for old, new, doc in [ ('LESS_GENERATOR', 'the Webassets plugin', None), diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 7b79e8f3..668a0093 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -372,8 +372,8 @@ class TestPageGenerator(unittest.TestCase): def test_generate_context(self): settings = get_settings(filenames={}) - settings['PAGE_DIR'] = 'TestPages' # relative to CUR_DIR settings['CACHE_PATH'] = self.temp_cache + settings['PAGE_PATHS'] = ['TestPages'] # relative to CUR_DIR settings['DEFAULT_DATE'] = (1970, 1, 1) generator = PagesGenerator( From 21882fd4a00ffdcee7029d7d5ee5ed355ef34a94 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Wed, 14 May 2014 14:06:58 +0200 Subject: [PATCH 0010/1173] Fix #1344 move PLUGIN_PATH -> PLUGIN_PATHS Pelican uses *_PATHS names for settings that represent a list of paths. --- docs/plugins.rst | 6 +++--- docs/settings.rst | 1 + pelican/__init__.py | 4 ++-- pelican/settings.py | 20 ++++++++++++-------- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/docs/plugins.rst b/docs/plugins.rst index 717019a8..a13d9dce 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -21,10 +21,10 @@ Alternatively, another method is to import them and add them to the list:: PLUGINS = [myplugin,] If your plugins are not in an importable path, you can specify a list of paths -via the ``PLUGIN_PATH`` setting. As shown in the following example, paths in -the ``PLUGIN_PATH`` list can be absolute or relative to the settings file:: +via the ``PLUGIN_PATHS`` setting. As shown in the following example, paths in +the ``PLUGIN_PATHS`` list can be absolute or relative to the settings file:: - PLUGIN_PATH = ["plugins", "/srv/pelican/plugins"] + PLUGIN_PATHS = ["plugins", "/srv/pelican/plugins"] PLUGINS = ["assets", "liquid_tags", "sitemap"] Where to find plugins diff --git a/docs/settings.rst b/docs/settings.rst index 33117a7f..34245ff4 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -125,6 +125,7 @@ Setting name (followed by default value, if any) not. Only set this to ``True`` when developing/testing and only if you fully understand the effect it can have on links/feeds. ``PLUGINS = []`` The list of plugins to load. See :ref:`plugins`. +``PLUGIN_PATHS = []`` A list of directories where to look for plugins. See :ref:`plugins`. ``SITENAME = 'A Pelican Blog'`` Your site name ``SITEURL`` Base URL of your website. Not defined by default, so it is best to specify your SITEURL; if you do not, feeds diff --git a/pelican/__init__.py b/pelican/__init__.py index e9fef163..082e5a58 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -63,9 +63,9 @@ class Pelican(object): def init_plugins(self): self.plugins = [] - logger.debug('Temporarily adding PLUGIN_PATH to system path') + logger.debug('Temporarily adding PLUGIN_PATHS to system path') _sys_path = sys.path[:] - for pluginpath in self.settings['PLUGIN_PATH']: + for pluginpath in self.settings['PLUGIN_PATHS']: sys.path.insert(0, pluginpath) for plugin in self.settings['PLUGINS']: # if it's a string, then import it diff --git a/pelican/settings.py b/pelican/settings.py index 219ebbd0..c93050ad 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -114,7 +114,7 @@ DEFAULT_CONFIG = { 'ARTICLE_PERMALINK_STRUCTURE': '', 'TYPOGRIFY': False, 'SUMMARY_MAX_LENGTH': 50, - 'PLUGIN_PATH': [], + 'PLUGIN_PATHS': [], 'PLUGINS': [], 'PYGMENTS_RST_OPTIONS': {}, 'TEMPLATE_PAGES': {}, @@ -147,13 +147,17 @@ def read_settings(path=None, override=None): if p not in ('THEME') or os.path.exists(absp): local_settings[p] = absp - if isinstance(local_settings['PLUGIN_PATH'], six.string_types): - logger.warning("Defining %s setting as string has been deprecated (should be a list)" % 'PLUGIN_PATH') - local_settings['PLUGIN_PATH'] = [local_settings['PLUGIN_PATH']] - else: - if 'PLUGIN_PATH' in local_settings and local_settings['PLUGIN_PATH'] is not None: - local_settings['PLUGIN_PATH'] = [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_PATH']] + if 'PLUGIN_PATH' in local_settings: + logger.warning('PLUGIN_PATH setting has been replaced by ' + 'PLUGIN_PATHS, moving it to the new setting name.') + local_settings['PLUGIN_PATHS'] = local_settings['PLUGIN_PATH'] + del local_settings['PLUGIN_PATH'] + if isinstance(local_settings['PLUGIN_PATHS'], six.string_types): + logger.warning("Defining %s setting as string has been deprecated (should be a list)" % 'PLUGIN_PATHS') + local_settings['PLUGIN_PATHS'] = [local_settings['PLUGIN_PATHS']] + 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))) + if not isabs(pluginpath) else pluginpath for pluginpath in local_settings['PLUGIN_PATHS']] else: local_settings = copy.deepcopy(DEFAULT_CONFIG) From 6aa0e4346de498ada234f772163579e677cfd10b Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Wed, 14 May 2014 14:30:21 +0200 Subject: [PATCH 0011/1173] Add {PAGE,ARTICLE}_PATHS to {ARTICLE,PAGE}_EXCLUDES automatically This makes it easier for someone to change PAGE_PATHS without the need to change ARTICLE_EXCLUDES accordingly. --- docs/settings.rst | 6 ++++-- pelican/settings.py | 16 ++++++++++++++-- pelican/tests/test_settings.py | 5 ++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 34245ff4..fd5f488b 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -112,9 +112,11 @@ Setting name (followed by default value, if any) and content path is not specified via an argument to the ``pelican`` command, Pelican will use the current working directory. ``PAGE_PATHS = ['pages']`` A list of directories to look at for pages, relative to ``PATH``. -``PAGE_EXCLUDES = ()`` A list of directories to exclude when looking for pages. +``PAGE_EXCLUDES = []`` A list of directories to exclude when looking for pages in addition + to ``ARTICLE_PATHS``. ``ARTICLE_PATHS = ['']`` A list of directories to look at for articles, relative to ``PATH``. -``ARTICLE_EXCLUDES = ('pages',)`` A list of directories to exclude when looking for articles. +``ARTICLE_EXCLUDES = []`` A list of directories to exclude when looking for articles in addition + to ``PAGE_PATHS``. ``OUTPUT_SOURCES = False`` Set to True if you want to copy the articles and pages in their original format (e.g. Markdown or reStructuredText) to the specified ``OUTPUT_PATH``. diff --git a/pelican/settings.py b/pelican/settings.py index c93050ad..69ade05f 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -30,9 +30,9 @@ DEFAULT_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)), DEFAULT_CONFIG = { 'PATH': os.curdir, 'ARTICLE_PATHS': [''], - 'ARTICLE_EXCLUDES': ('pages',), + 'ARTICLE_EXCLUDES': [], 'PAGE_PATHS': ['pages'], - 'PAGE_EXCLUDES': (), + 'PAGE_EXCLUDES': [], 'THEME': DEFAULT_THEME, 'OUTPUT_PATH': 'output', 'READERS': {}, @@ -348,6 +348,18 @@ def configure_settings(settings): % PATH_KEY) settings[PATH_KEY] = DEFAULT_CONFIG[PATH_KEY] + # Add {PAGE,ARTICLE}_PATHS to {ARTICLE,PAGE}_EXCLUDES + mutually_exclusive = ('ARTICLE', 'PAGE') + for type_1, type_2 in [mutually_exclusive, mutually_exclusive[::-1]]: + try: + includes = settings[type_1 + '_PATHS'] + excludes = settings[type_2 + '_EXCLUDES'] + for path in includes: + if path not in excludes: + excludes.append(path) + except KeyError: + continue # setting not specified, nothing to do + for old, new, doc in [ ('LESS_GENERATOR', 'the Webassets plugin', None), ('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA', diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 930e0fea..260eff05 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -43,7 +43,10 @@ class TestSettingsConfiguration(unittest.TestCase): # Providing no file should return the default values. settings = read_settings(None) expected = copy.deepcopy(DEFAULT_CONFIG) - expected['FEED_DOMAIN'] = '' # Added by configure settings + # Added by configure settings + expected['FEED_DOMAIN'] = '' + expected['ARTICLE_EXCLUDES'] = ['pages'] + expected['PAGE_EXCLUDES'] = [''] self.maxDiff = None self.assertDictEqual(settings, expected) From 144cddaf39ad6577cfa4a11ef003f6fa44c81d58 Mon Sep 17 00:00:00 2001 From: Mark Lee Date: Tue, 20 May 2014 13:53:02 -0700 Subject: [PATCH 0012/1173] Address code review comments from PR getpelican/pelican#1348 The text about the sort-by-key function comes from: https://docs.python.org/2/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer-xrange Minor style cleanup as well. --- docs/settings.rst | 36 +++++++++++++++++++++--------------- pelican/utils.py | 14 +++++--------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 55740296..0c804f1c 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -260,16 +260,19 @@ posts for the month at ``posts/2011/Aug/index.html``. arrive at an appropriate archive of posts, without having to specify a page name. -====================================================== ======================================================== +====================================================== ============================================================== Setting name (followed by default value, if any) What does it do? -====================================================== ======================================================== +====================================================== ============================================================== ``ARTICLE_URL = '{slug}.html'`` The URL to refer to an article. ``ARTICLE_SAVE_AS = '{slug}.html'`` The place where we will save an article. -``ARTICLE_ORDER_BY = 'slug'`` The metadata attribute used to sort articles. By default - the articles_page.object_list template variable is - ordered by slug. If you modify this, make sure all - articles contain the attribute you specify. You can - also specify a sorting function. +``ARTICLE_ORDER_BY = 'slug'`` The metadata attribute used to sort articles. By default, + the ``articles_page.object_list`` template variable is + ordered by slug. If you modify this, make sure all + articles contain the attribute you specify. You can also + specify a "sorting" function of one argument that is used + to extract a comparison key from each article. For example, + sorting by title without using the built-in functionality + would use the function ``operator.attrgetter('title')``. ``ARTICLE_LANG_URL = '{slug}-{lang}.html'`` The URL to refer to an article which doesn't use the default language. ``ARTICLE_LANG_SAVE_AS = '{slug}-{lang}.html'`` The place where we will save an article which @@ -285,13 +288,16 @@ Setting name (followed by default value, if any) What does it do? the same as PAGE_URL or you need to use a rewrite in your server config. -``PAGE_ORDER_BY = 'filename'`` The metadata attribute used to sort pages. By default - the PAGES template variable is ordered by filename - (path not included). Note that the option 'filename' - is a special option supported in the source code. If - you modify this settings, make sure all pages contain - the attribute you specify. You can also specify a - sorting function. +``PAGE_ORDER_BY = 'basename'`` The metadata attribute used to sort pages. By default + the ``PAGES`` template variable is ordered by basename + (i.e., path not included). Note that the option ``'basename'`` + is a special option supported in the source code. If + you modify this setting, make sure all pages contain + the attribute you specify. You can also specify a "sorting" + function of one argument that is used to extract a comparison + key from each page. For example, the basename function looks + similar to + ``lambda x: os.path.basename(getattr(x, 'source_path', ''))``. ``PAGE_LANG_URL = 'pages/{slug}-{lang}.html'`` The URL we will use to link to a page which doesn't use the default language. ``PAGE_LANG_SAVE_AS = 'pages/{slug}-{lang}.html'`` The location we will save the page which doesn't @@ -309,7 +315,7 @@ Setting name (followed by default value, if any) What does it do? non-alphanumerics when generating slugs. Specified as a list of 2-tuples of ``(from, to)`` which are applied in order. -====================================================== ======================================================== +====================================================== ============================================================== .. note:: diff --git a/pelican/utils.py b/pelican/utils.py index ecdf5e0d..076c41ea 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -465,17 +465,13 @@ def process_translations(content_list, order_by=None): a.translations = [x for x in items if x != a] if order_by: - if hasattr(order_by, '__call__'): + if callable(order_by): try: index.sort(key=order_by) - except: - if hasattr(order_by, 'func_name'): - logger.error("Error sorting with function %s" % order_by.func_name) - else: - logger.error("Error sorting with function %r" % order_by) - elif order_by == 'filename': - index.sort(key=lambda x:os.path.basename( - x.source_path or '')) + except Exception: + logger.error('Error sorting with function {}'.format(order_by)) + elif order_by == 'basename': + index.sort(key=lambda x: os.path.basename(x.source_path or '')) elif order_by != 'slug': index.sort(key=attrgetter(order_by)) From b8db970455100b3b9393fcb20cc1b7fd9c3730f4 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Sun, 25 May 2014 09:12:35 +0200 Subject: [PATCH 0013/1173] Fix RstReader authors metadata processing The reader would return a list of authors already, but METADATA_PROCESSORS['authors'] expects a string. Added a test case for this (only the HTMLReader had it). --- pelican/readers.py | 1 + pelican/tests/test_generators.py | 1 + pelican/tests/test_readers.py | 9 +++++++++ 3 files changed, 11 insertions(+) diff --git a/pelican/readers.py b/pelican/readers.py index c63b8981..60df8551 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -145,6 +145,7 @@ class RstReader(BaseReader): elif element.tagname == 'authors': # author list name = element.tagname value = [element.astext() for element in element.children] + value = ','.join(value) # METADATA_PROCESSORS expects a string else: # standard fields (e.g. address) name = element.tagname value = element.astext() diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 7b79e8f3..156f7b50 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -109,6 +109,7 @@ class TestArticlesGenerator(unittest.TestCase): ['This is an article with category !', 'published', 'yeah', 'article'], ['This is an article with multiple authors!', 'published', 'Default', 'article'], + ['This is an article with multiple authors!', 'published', 'Default', 'article'], ['This is an article without category !', 'published', 'Default', 'article'], ['This is an article without category !', 'published', diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index fd30e9b9..3533cd31 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -150,6 +150,15 @@ class RstReaderTest(ReaderTest): except ImportError: return unittest.skip('need the typogrify distribution') + def test_article_with_multiple_authors(self): + page = self.read_file(path='article_with_multiple_authors.rst') + expected = { + 'authors': ['First Author', 'Second Author'] + } + + for key, value in expected.items(): + self.assertEqual(value, page.metadata[key], key) + class MdReaderTest(ReaderTest): From 43701cae7c0c7fc4617374eab13695da8372e104 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Tue, 27 May 2014 12:15:50 -0400 Subject: [PATCH 0014/1173] Docs update for *_{previous|next}_page variables --- docs/themes.rst | 56 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/docs/themes.rst b/docs/themes.rst index 7fcba671..4be9a8e5 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -119,17 +119,25 @@ This is the home page of your blog, generated at output/index.html. If pagination is active, subsequent pages will reside in output/index`n`.html. -=================== =================================================== +====================== =================================================== Variable Description -=================== =================================================== +====================== =================================================== articles_paginator A paginator object for the list of articles articles_page The current page of articles +articles_previous_page The previous page of articles (``None`` if page does + not exist) +articles_next_page The next page of articles (``None`` if page does + not exist) dates_paginator A paginator object for the article list, ordered by date, ascending. dates_page The current page of articles, ordered by date, ascending. +dates_previous_page The previous page of articles, ordered by date, + ascending (``None`` if page does not exist) +dates_next_page The next page of articles, ordered by date, + ascending (``None`` if page does not exist) page_name 'index' -- useful for pagination links -=================== =================================================== +====================== =================================================== author.html ------------- @@ -140,22 +148,30 @@ output generated at output/author/`author_name`.html. If pagination is active, subsequent pages will reside as defined by setting AUTHOR_SAVE_AS (`Default:` output/author/`author_name'n'`.html). -=================== =================================================== +====================== =================================================== Variable Description -=================== =================================================== +====================== =================================================== author The name of the author being processed articles Articles by this author dates Articles by this author, but ordered by date, ascending articles_paginator A paginator object for the list of articles articles_page The current page of articles +articles_previous_page The previous page of articles (``None`` if page does + not exist) +articles_next_page The next page of articles (``None`` if page does + not exist) dates_paginator A paginator object for the article list, ordered by date, ascending. dates_page The current page of articles, ordered by date, ascending. +dates_previous_page The previous page of articles, ordered by date, + ascending (``None`` if page does not exist) +dates_next_page The next page of articles, ordered by date, + ascending (``None`` if page does not exist) page_name AUTHOR_URL where everything after `{slug}` is removed -- useful for pagination links -=================== =================================================== +====================== =================================================== category.html ------------- @@ -166,22 +182,30 @@ output generated at output/category/`category_name`.html. If pagination is active, subsequent pages will reside as defined by setting CATEGORY_SAVE_AS (`Default:` output/category/`category_name'n'`.html). -=================== =================================================== +====================== =================================================== Variable Description -=================== =================================================== +====================== =================================================== category The name of the category being processed articles Articles for this category dates Articles for this category, but ordered by date, ascending articles_paginator A paginator object for the list of articles articles_page The current page of articles +articles_previous_page The previous page of articles (``None`` if page does + not exist) +articles_next_page The next page of articles (``None`` if page does + not exist) dates_paginator A paginator object for the list of articles, ordered by date, ascending dates_page The current page of articles, ordered by date, ascending +dates_previous_page The previous page of articles, ordered by date, + ascending (``None`` if page does not exist) +dates_next_page The next page of articles, ordered by date, + ascending (``None`` if page does not exist) page_name CATEGORY_URL where everything after `{slug}` is removed -- useful for pagination links -=================== =================================================== +====================== =================================================== article.html ------------- @@ -244,22 +268,30 @@ saved as output/tag/`tag_name`.html. If pagination is active, subsequent pages will reside as defined in setting TAG_SAVE_AS (`Default:` output/tag/`tag_name'n'`.html). -=================== =================================================== +====================== =================================================== Variable Description -=================== =================================================== +====================== =================================================== tag The name of the tag being processed articles Articles related to this tag dates Articles related to this tag, but ordered by date, ascending articles_paginator A paginator object for the list of articles articles_page The current page of articles +articles_previous_page The previous page of articles (``None`` if page does + not exist) +articles_next_page The next page of articles (``None`` if page does + not exist) dates_paginator A paginator object for the list of articles, ordered by date, ascending dates_page The current page of articles, ordered by date, ascending +dates_previous_page The previous page of articles, ordered by date, + ascending (``None`` if page does not exist) +dates_next_page The next page of articles, ordered by date, + ascending (``None`` if page does not exist) page_name TAG_URL where everything after `{slug}` is removed -- useful for pagination links -=================== =================================================== +====================== =================================================== period_archives.html -------------------- From b572cbeef15ac2e4fe6107110f52fbdc212cd52a Mon Sep 17 00:00:00 2001 From: Mark Lee Date: Tue, 27 May 2014 11:42:37 -0700 Subject: [PATCH 0015/1173] Addressed comments from @avaris in PR getpelican/pelican#1348 --- pelican/settings.py | 2 +- pelican/utils.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pelican/settings.py b/pelican/settings.py index f49d3bd4..6845b830 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -70,7 +70,7 @@ DEFAULT_CONFIG = { 'DRAFT_LANG_SAVE_AS': os.path.join('drafts', '{slug}-{lang}.html'), 'PAGE_URL': 'pages/{slug}.html', 'PAGE_SAVE_AS': os.path.join('pages', '{slug}.html'), - 'PAGE_ORDER_BY': 'filename', + 'PAGE_ORDER_BY': 'basename', 'PAGE_LANG_URL': 'pages/{slug}-{lang}.html', 'PAGE_LANG_SAVE_AS': os.path.join('pages', '{slug}-{lang}.html'), 'STATIC_URL': '{path}', diff --git a/pelican/utils.py b/pelican/utils.py index 076c41ea..c2d6ca22 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -473,7 +473,12 @@ def process_translations(content_list, order_by=None): elif order_by == 'basename': index.sort(key=lambda x: os.path.basename(x.source_path or '')) elif order_by != 'slug': - index.sort(key=attrgetter(order_by)) + try: + index.sort(key=attrgetter(order_by)) + except AttributeError: + error_msg = ('There is no "{}" attribute in the item metadata.' + 'Defaulting to slug order.') + logger.warning(error_msg.format(order_by)) return index, translations From ef967056778a6610fe7b61c349acec0990073dd7 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Sun, 8 Jun 2014 11:32:10 +0200 Subject: [PATCH 0016/1173] catch arbitrary exceptions during cache unpickling It is hard to forsee what could be raised. --- pelican/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/utils.py b/pelican/utils.py index 2af34ecf..84b3a41e 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -577,7 +577,7 @@ class FileDataCacher(object): 'run). Proceeding with empty cache.\n{}').format( self._cache_path, err)) self._cache = {} - except pickle.UnpicklingError as err: + except Exception as err: logger.warning(('Cannot unpickle cache {}, cache may be using ' 'an incompatible protocol (see pelican caching docs). ' 'Proceeding with empty cache.\n{}').format( From def654434c49a9a17c31ba4b6d970a517e5af320 Mon Sep 17 00:00:00 2001 From: OGINO Masanori Date: Tue, 10 Jun 2014 08:28:10 +0900 Subject: [PATCH 0017/1173] Require six version 1.4.0 or later. six.moves.urllib.parse is available since version 1.4.0. Signed-off-by: OGINO Masanori --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e989d549..a2bcaeaa 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,8 @@ from setuptools import setup requires = ['feedgenerator >= 1.6', 'jinja2 >= 2.7', 'pygments', 'docutils', - 'pytz >= 0a', 'blinker', 'unidecode', 'six', 'python-dateutil'] + 'pytz >= 0a', 'blinker', 'unidecode', 'six >= 1.4', + 'python-dateutil'] entry_points = { 'console_scripts': [ From ca3aa1e75fac0b54feb7170e6a5f1c03c1097145 Mon Sep 17 00:00:00 2001 From: OGINO Masanori Date: Tue, 10 Jun 2014 17:30:17 +0900 Subject: [PATCH 0018/1173] Use six.moves.urllib. Signed-off-by: OGINO Masanori --- pelican/contents.py | 8 +------- pelican/tools/pelican_import.py | 9 +++------ pelican/writers.py | 4 +--- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 615a7fd8..220db611 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, print_function import six -from six.moves.urllib.parse import unquote +from six.moves.urllib.parse import (unquote, urlparse, urlunparse) import copy import locale @@ -11,14 +11,8 @@ import os import re import sys -try: - from urlparse import urlparse, urlunparse -except ImportError: - from urllib.parse import urlparse, urlunparse - from datetime import datetime - from pelican import signals from pelican.settings import DEFAULT_CONFIG from pelican.utils import (slugify, truncate_html_words, memoized, strftime, diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 27e47754..7c8662c9 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -6,15 +6,9 @@ import argparse try: # py3k import from html.parser import HTMLParser - from urllib.request import urlretrieve - from urllib.parse import urlparse - from urllib.error import URLError except ImportError: # py2 import from HTMLParser import HTMLParser # NOQA - from urllib import urlretrieve - from urlparse import urlparse - from urllib2 import URLError import os import re import subprocess @@ -23,6 +17,9 @@ import time import logging from codecs import open +from six.moves.urllib.error import URLError +from six.moves.urllib.parse import urlparse +from six.moves.urllib.request import urlretrieve from pelican.utils import slugify from pelican.log import init diff --git a/pelican/writers.py b/pelican/writers.py index a92feee4..3e01ee6c 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -8,12 +8,10 @@ import logging if not six.PY3: from codecs import open - from urlparse import urlparse -else: - from urllib.parse import urlparse from feedgenerator import Atom1Feed, Rss201rev2Feed from jinja2 import Markup +from six.moves.urllib.parse import urlparse from pelican.paginator import Paginator from pelican.utils import (get_relative_path, path_to_url, set_date_tzinfo, From bf9316bb7e4a7aa2a6e335c681d4255d1deed23e Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Tue, 10 Jun 2014 18:05:29 -0400 Subject: [PATCH 0019/1173] Remove AsciiDocReader from core. Fixes #1355 --- .travis.yml | 2 - docs/content.rst | 5 ++- docs/index.rst | 3 +- docs/install.rst | 5 --- docs/internals.rst | 4 +- docs/settings.rst | 2 - pelican/readers.py | 39 ------------------- pelican/settings.py | 1 - .../content/article_with_asc_extension.asc | 12 ------ .../content/article_with_asc_options.asc | 9 ----- pelican/tests/test_readers.py | 36 ----------------- 11 files changed, 6 insertions(+), 112 deletions(-) delete mode 100644 pelican/tests/content/article_with_asc_extension.asc delete mode 100644 pelican/tests/content/article_with_asc_options.asc diff --git a/.travis.yml b/.travis.yml index 41ad82b2..54dcf0ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,8 @@ python: - "3.4" before_install: - sudo apt-get update -qq - - sudo apt-get install -qq --no-install-recommends asciidoc - sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8 install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then ln -s /usr/share/asciidoc/asciidocapi.py ~/virtualenv/python2.7/lib/python2.7/site-packages/; fi - pip install . - pip install -r dev_requirements.txt - pip install nose-cov diff --git a/docs/content.rst b/docs/content.rst index 24fc6e9b..ad81bed1 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -57,8 +57,8 @@ pattern:: This is the content of my super blog post. -Conventions for AsciiDoc_ posts, which should have an ``.asc`` extension, can -be found on the AsciiDoc_ site. +Readers for additional formats (such as AsciiDoc_) are available via plugins. +Refer to `pelican-plugins`_ repository for those. Pelican can also process HTML files ending in ``.html`` and ``.htm``. Pelican interprets the HTML in a very straightforward manner, reading metadata from @@ -369,3 +369,4 @@ listed on the index page nor on any category or tag page. .. _W3C ISO 8601: http://www.w3.org/TR/NOTE-datetime .. _AsciiDoc: http://www.methods.co.nz/asciidoc/ +.. _pelican-plugins: http://github.com/getpelican/pelican-plugins diff --git a/docs/index.rst b/docs/index.rst index 36a3282b..2beb8b20 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,7 +13,7 @@ Pelican |release| Pelican is a static site generator, written in Python_. Highlights include: * Write your content directly with your editor of choice - in reStructuredText_, Markdown_, or AsciiDoc_ formats + in reStructuredText_ or Markdown_ formats * Includes a simple CLI tool to (re)generate your site * Easy to interface with distributed version control systems and web hooks * Completely static output is easy to host anywhere @@ -89,7 +89,6 @@ Documentation .. _Python: http://www.python.org/ .. _reStructuredText: http://docutils.sourceforge.net/rst.html .. _Markdown: http://daringfireball.net/projects/markdown/ -.. _AsciiDoc: http://www.methods.co.nz/asciidoc/index.html .. _Jinja2: http://jinja.pocoo.org/ .. _`Pelican documentation`: http://docs.getpelican.com/latest/ .. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html diff --git a/docs/install.rst b/docs/install.rst index 34cd33ea..418c8ca6 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -52,10 +52,6 @@ installed:: pip install typogrify -If you want to use AsciiDoc_ you need to install it from `source -`_ or use your operating -system's package manager. - Dependencies ------------ @@ -119,4 +115,3 @@ The next step is to begin to adding content to the *content* folder that has been created for you. .. _virtualenv: http://www.virtualenv.org/ -.. _AsciiDoc: http://www.methods.co.nz/asciidoc/ diff --git a/docs/internals.rst b/docs/internals.rst index f69a9bb8..303a327f 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -13,7 +13,7 @@ Overall structure ================= What Pelican does is take a list of files and process them into some sort of -output. Usually, the input files are reStructuredText, Markdown and AsciiDoc +output. Usually, the input files are reStructuredText and Markdown files, and the output is a blog, but both input and output can be anything you want. @@ -23,7 +23,7 @@ The logic is separated into different classes and concepts: on. Since those operations are commonly used, the object is created once and then passed to the generators. -* **Readers** are used to read from various formats (AsciiDoc, HTML, Markdown and +* **Readers** are used to read from various formats (HTML, Markdown and reStructuredText for now, but the system is extensible). Given a file, they return metadata (author, tags, category, etc.) and content (HTML-formatted). diff --git a/docs/settings.rst b/docs/settings.rst index 4701e92d..5d9c574f 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -159,8 +159,6 @@ Setting name (followed by default value, if any) Can be used to separate templates from the theme. Example: projects, resume, profile ... These templates need to use ``DIRECT_TEMPLATES`` setting. -``ASCIIDOC_OPTIONS = []`` A list of options to pass to AsciiDoc. See the `manpage - `_. ``WITH_FUTURE_DATES = True`` If disabled, content with dates in the future will get a default status of ``draft``. See :ref:`reading_only_modified_content` for caveats. diff --git a/pelican/readers.py b/pelican/readers.py index 60df8551..431e6937 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -17,11 +17,6 @@ try: from markdown import Markdown except ImportError: Markdown = False # NOQA -try: - from asciidocapi import AsciiDocAPI - asciidoc = True -except ImportError: - asciidoc = False try: from html import escape except ImportError: @@ -349,40 +344,6 @@ class HTMLReader(BaseReader): return parser.body, metadata -class AsciiDocReader(BaseReader): - """Reader for AsciiDoc files""" - - enabled = bool(asciidoc) - file_extensions = ['asc', 'adoc', 'asciidoc'] - default_options = ["--no-header-footer", "-a newline=\\n"] - - def read(self, source_path): - """Parse content and metadata of asciidoc files""" - from cStringIO import StringIO - with pelican_open(source_path) as source: - text = StringIO(source) - content = StringIO() - ad = AsciiDocAPI() - - options = self.settings['ASCIIDOC_OPTIONS'] - if isinstance(options, (str, unicode)): - options = [m.strip() for m in options.split(',')] - options = self.default_options + options - for o in options: - ad.options(*o.split()) - - ad.execute(text, content, backend="html4") - content = content.getvalue() - - metadata = {} - for name, value in ad.asciidoc.document.attributes.items(): - name = name.lower() - metadata[name] = self.process_metadata(name, value) - if 'doctitle' in metadata: - metadata['title'] = metadata['doctitle'] - return content, metadata - - class Readers(FileStampDataCacher): """Interface for all readers. diff --git a/pelican/settings.py b/pelican/settings.py index f759ff9e..a94e4bf2 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -98,7 +98,6 @@ DEFAULT_CONFIG = { 'PELICAN_CLASS': 'pelican.Pelican', 'DEFAULT_DATE_FORMAT': '%a %d %B %Y', 'DATE_FORMATS': {}, - 'ASCIIDOC_OPTIONS': [], 'MD_EXTENSIONS': ['codehilite(css_class=highlight)', 'extra'], 'JINJA_EXTENSIONS': [], 'JINJA_FILTERS': {}, diff --git a/pelican/tests/content/article_with_asc_extension.asc b/pelican/tests/content/article_with_asc_extension.asc deleted file mode 100644 index 9ce2166c..00000000 --- a/pelican/tests/content/article_with_asc_extension.asc +++ /dev/null @@ -1,12 +0,0 @@ -Test AsciiDoc File Header -========================= -:Author: Author O. Article -:Email: -:Date: 2011-09-15 09:05 -:Category: Blog -:Tags: Linux, Python, Pelican - -Used for pelican test ---------------------- - -The quick brown fox jumped over the lazy dog's back. diff --git a/pelican/tests/content/article_with_asc_options.asc b/pelican/tests/content/article_with_asc_options.asc deleted file mode 100644 index bafb3a4a..00000000 --- a/pelican/tests/content/article_with_asc_options.asc +++ /dev/null @@ -1,9 +0,0 @@ -Test AsciiDoc File Header -========================= - -Used for pelican test ---------------------- - -version {revision} - -The quick brown fox jumped over the lazy dog's back. diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index 3533cd31..6228989b 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -333,42 +333,6 @@ class MdReaderTest(ReaderTest): self.assertEqual(value, page.metadata[key], key) -class AdReaderTest(ReaderTest): - - @unittest.skipUnless(readers.asciidoc, "asciidoc isn't installed") - def test_article_with_asc_extension(self): - # Ensure the asc extension is being processed by the correct reader - page = self.read_file( - path='article_with_asc_extension.asc') - expected = ('
\n

' - 'Used for pelican test

\n' - '

The quick brown fox jumped over' - ' the lazy dog’s back.

\n') - self.assertEqual(page.content, expected) - expected = { - 'category': 'Blog', - 'author': 'Author O. Article', - 'title': 'Test AsciiDoc File Header', - 'date': datetime.datetime(2011, 9, 15, 9, 5), - 'tags': ['Linux', 'Python', 'Pelican'], - } - - for key, value in expected.items(): - self.assertEqual(value, page.metadata[key], key) - - @unittest.skipUnless(readers.asciidoc, "asciidoc isn't installed") - def test_article_with_asc_options(self): - # test to ensure the ASCIIDOC_OPTIONS is being used - reader = readers.AsciiDocReader( - dict(ASCIIDOC_OPTIONS=["-a revision=1.0.42"])) - content, metadata = reader.read(_path('article_with_asc_options.asc')) - expected = ('
\n

Used for' - ' pelican test

\n

version 1.0.42

\n' - '

The quick brown fox jumped over the lazy' - ' dog’s back.

\n') - self.assertEqual(content, expected) - - class HTMLReaderTest(ReaderTest): def test_article_with_comments(self): page = self.read_file(path='article_with_comments.html') From 24106081b5bbb110eed16d2714f65aa1ea2a9fc2 Mon Sep 17 00:00:00 2001 From: Kyle Fuller Date: Tue, 24 Jun 2014 22:29:36 +0100 Subject: [PATCH 0020/1173] [coveralls] Exclude tests from coverage --- .coveragerc | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..2cb24879 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[report] +omit = pelican/tests/* + From 3f6b130d6ed427f19e444057958661ee00875ca0 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Sun, 27 Apr 2014 10:25:57 +0200 Subject: [PATCH 0021/1173] Fix #1198, enable custom locale in template rendering, fixes links reverts getpelican/pelican@ddcccfeaa952d2e1e24ceac94e5d66c73b57c01b If one used a locale that made use of unicode characters (like fr_FR.UTF-8) the files on disk would be in correct locale while links would be to C. Uses a SafeDatetime class that works with unicode format strigns by using custom strftime to prevent ascii decoding errors with Python2. Also added unicode decoding for the calendar module to fix period archives. --- docs/contribute.rst | 2 + pelican/contents.py | 7 +- pelican/generators.py | 32 +- pelican/readers.py | 9 +- .../tests/output/custom_locale/archives.html | 100 ++++ .../author/alexis-metaireau.html | 173 +++++++ .../author/alexis-metaireau2.html | 187 ++++++++ .../author/alexis-metaireau3.html | 138 ++++++ .../tests/output/custom_locale/authors.html | 82 ++++ .../output/custom_locale/categories.html | 80 ++++ .../output/custom_locale/category/bar.html | 101 ++++ .../output/custom_locale/category/cat1.html | 170 +++++++ .../output/custom_locale/category/misc.html | 181 +++++++ .../output/custom_locale/category/yeah.html | 109 +++++ .../custom_locale/drafts/a-draft-article.html | 100 ++++ .../feeds/alexis-metaireau.atom.xml | 61 +++ .../feeds/alexis-metaireau.rss.xml | 61 +++ .../custom_locale/feeds/all-en.atom.xml | 61 +++ .../custom_locale/feeds/all-fr.atom.xml | 4 + .../output/custom_locale/feeds/all.atom.xml | 63 +++ .../output/custom_locale/feeds/all.rss.xml | 63 +++ .../output/custom_locale/feeds/bar.atom.xml | 8 + .../output/custom_locale/feeds/bar.rss.xml | 8 + .../output/custom_locale/feeds/cat1.atom.xml | 7 + .../output/custom_locale/feeds/cat1.rss.xml | 7 + .../output/custom_locale/feeds/misc.atom.xml | 38 ++ .../output/custom_locale/feeds/misc.rss.xml | 38 ++ .../output/custom_locale/feeds/yeah.atom.xml | 14 + .../output/custom_locale/feeds/yeah.rss.xml | 14 + pelican/tests/output/custom_locale/index.html | 173 +++++++ .../tests/output/custom_locale/index2.html | 187 ++++++++ .../tests/output/custom_locale/index3.html | 138 ++++++ .../output/custom_locale/jinja2_template.html | 77 +++ .../output/custom_locale/oh-yeah-fr.html | 116 +++++ .../output/custom_locale/override/index.html | 81 ++++ .../pages/this-is-a-test-hidden-page.html | 81 ++++ .../pages/this-is-a-test-page.html | 81 ++++ .../output/custom_locale/pictures/Fat_Cat.jpg | Bin 0 -> 62675 bytes .../output/custom_locale/pictures/Sushi.jpg | Bin 0 -> 28992 bytes .../custom_locale/pictures/Sushi_Macro.jpg | Bin 0 -> 38594 bytes .../02/this-is-a-super-article/index.html | 129 +++++ .../2010/octobre/15/unbelievable/index.html | 146 ++++++ .../posts/2010/octobre/20/oh-yeah/index.html | 121 +++++ .../20/a-markdown-powered-article/index.html | 115 +++++ .../2011/février/17/article-1/index.html | 114 +++++ .../2011/février/17/article-2/index.html | 114 +++++ .../2011/février/17/article-3/index.html | 114 +++++ .../2012/février/29/second-article/index.html | 116 +++++ .../30/filename_metadata-example/index.html | 114 +++++ pelican/tests/output/custom_locale/robots.txt | 2 + .../custom_locale/second-article-fr.html | 116 +++++ .../tests/output/custom_locale/tag/bar.html | 160 +++++++ .../tests/output/custom_locale/tag/baz.html | 114 +++++ .../tests/output/custom_locale/tag/foo.html | 130 +++++ .../output/custom_locale/tag/foobar.html | 109 +++++ .../tests/output/custom_locale/tag/oh.html | 80 ++++ .../tests/output/custom_locale/tag/yeah.html | 101 ++++ pelican/tests/output/custom_locale/tags.html | 87 ++++ .../output/custom_locale/theme/css/main.css | 451 ++++++++++++++++++ .../custom_locale/theme/css/pygment.css | 205 ++++++++ .../output/custom_locale/theme/css/reset.css | 52 ++ .../custom_locale/theme/css/typogrify.css | 3 + .../output/custom_locale/theme/css/wide.css | 48 ++ .../theme/images/icons/aboutme.png | Bin 0 -> 751 bytes .../theme/images/icons/bitbucket.png | Bin 0 -> 3714 bytes .../theme/images/icons/delicious.png | Bin 0 -> 958 bytes .../theme/images/icons/facebook.png | Bin 0 -> 202 bytes .../theme/images/icons/github.png | Bin 0 -> 1714 bytes .../theme/images/icons/gitorious.png | Bin 0 -> 227 bytes .../theme/images/icons/gittip.png | Bin 0 -> 487 bytes .../theme/images/icons/google-groups.png | Bin 0 -> 803 bytes .../theme/images/icons/google-plus.png | Bin 0 -> 527 bytes .../theme/images/icons/hackernews.png | Bin 0 -> 3273 bytes .../theme/images/icons/lastfm.png | Bin 0 -> 975 bytes .../theme/images/icons/linkedin.png | Bin 0 -> 896 bytes .../theme/images/icons/reddit.png | Bin 0 -> 693 bytes .../custom_locale/theme/images/icons/rss.png | Bin 0 -> 879 bytes .../theme/images/icons/slideshare.png | Bin 0 -> 535 bytes .../theme/images/icons/speakerdeck.png | Bin 0 -> 1049 bytes .../theme/images/icons/stackoverflow.png | Bin 0 -> 916 bytes .../theme/images/icons/twitter.png | Bin 0 -> 830 bytes .../theme/images/icons/vimeo.png | Bin 0 -> 544 bytes .../theme/images/icons/youtube.png | Bin 0 -> 458 bytes pelican/tests/test_contents.py | 5 +- pelican/tests/test_pelican.py | 25 +- pelican/tests/test_readers.py | 30 +- pelican/tests/test_utils.py | 35 +- pelican/tools/pelican_import.py | 6 +- pelican/utils.py | 21 +- pelican/writers.py | 7 +- samples/pelican_FR.conf.py | 59 +++ 91 files changed, 5704 insertions(+), 77 deletions(-) create mode 100644 pelican/tests/output/custom_locale/archives.html create mode 100644 pelican/tests/output/custom_locale/author/alexis-metaireau.html create mode 100644 pelican/tests/output/custom_locale/author/alexis-metaireau2.html create mode 100644 pelican/tests/output/custom_locale/author/alexis-metaireau3.html create mode 100644 pelican/tests/output/custom_locale/authors.html create mode 100644 pelican/tests/output/custom_locale/categories.html create mode 100644 pelican/tests/output/custom_locale/category/bar.html create mode 100644 pelican/tests/output/custom_locale/category/cat1.html create mode 100644 pelican/tests/output/custom_locale/category/misc.html create mode 100644 pelican/tests/output/custom_locale/category/yeah.html create mode 100644 pelican/tests/output/custom_locale/drafts/a-draft-article.html create mode 100644 pelican/tests/output/custom_locale/feeds/alexis-metaireau.atom.xml create mode 100644 pelican/tests/output/custom_locale/feeds/alexis-metaireau.rss.xml create mode 100644 pelican/tests/output/custom_locale/feeds/all-en.atom.xml create mode 100644 pelican/tests/output/custom_locale/feeds/all-fr.atom.xml create mode 100644 pelican/tests/output/custom_locale/feeds/all.atom.xml create mode 100644 pelican/tests/output/custom_locale/feeds/all.rss.xml create mode 100644 pelican/tests/output/custom_locale/feeds/bar.atom.xml create mode 100644 pelican/tests/output/custom_locale/feeds/bar.rss.xml create mode 100644 pelican/tests/output/custom_locale/feeds/cat1.atom.xml create mode 100644 pelican/tests/output/custom_locale/feeds/cat1.rss.xml create mode 100644 pelican/tests/output/custom_locale/feeds/misc.atom.xml create mode 100644 pelican/tests/output/custom_locale/feeds/misc.rss.xml create mode 100644 pelican/tests/output/custom_locale/feeds/yeah.atom.xml create mode 100644 pelican/tests/output/custom_locale/feeds/yeah.rss.xml create mode 100644 pelican/tests/output/custom_locale/index.html create mode 100644 pelican/tests/output/custom_locale/index2.html create mode 100644 pelican/tests/output/custom_locale/index3.html create mode 100644 pelican/tests/output/custom_locale/jinja2_template.html create mode 100644 pelican/tests/output/custom_locale/oh-yeah-fr.html create mode 100644 pelican/tests/output/custom_locale/override/index.html create mode 100644 pelican/tests/output/custom_locale/pages/this-is-a-test-hidden-page.html create mode 100644 pelican/tests/output/custom_locale/pages/this-is-a-test-page.html create mode 100644 pelican/tests/output/custom_locale/pictures/Fat_Cat.jpg create mode 100644 pelican/tests/output/custom_locale/pictures/Sushi.jpg create mode 100644 pelican/tests/output/custom_locale/pictures/Sushi_Macro.jpg create mode 100644 pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html create mode 100644 pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html create mode 100644 pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html create mode 100644 pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html create mode 100644 pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html create mode 100644 pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html create mode 100644 pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html create mode 100644 pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html create mode 100644 pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html create mode 100644 pelican/tests/output/custom_locale/robots.txt create mode 100644 pelican/tests/output/custom_locale/second-article-fr.html create mode 100644 pelican/tests/output/custom_locale/tag/bar.html create mode 100644 pelican/tests/output/custom_locale/tag/baz.html create mode 100644 pelican/tests/output/custom_locale/tag/foo.html create mode 100644 pelican/tests/output/custom_locale/tag/foobar.html create mode 100644 pelican/tests/output/custom_locale/tag/oh.html create mode 100644 pelican/tests/output/custom_locale/tag/yeah.html create mode 100644 pelican/tests/output/custom_locale/tags.html create mode 100644 pelican/tests/output/custom_locale/theme/css/main.css create mode 100644 pelican/tests/output/custom_locale/theme/css/pygment.css create mode 100644 pelican/tests/output/custom_locale/theme/css/reset.css create mode 100644 pelican/tests/output/custom_locale/theme/css/typogrify.css create mode 100644 pelican/tests/output/custom_locale/theme/css/wide.css create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/aboutme.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/bitbucket.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/delicious.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/facebook.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/github.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/gitorious.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/gittip.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/google-groups.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/google-plus.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/hackernews.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/lastfm.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/linkedin.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/reddit.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/rss.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/slideshare.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/speakerdeck.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/stackoverflow.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/twitter.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/vimeo.png create mode 100644 pelican/tests/output/custom_locale/theme/images/icons/youtube.png create mode 100644 samples/pelican_FR.conf.py diff --git a/docs/contribute.rst b/docs/contribute.rst index 57349156..044ef924 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -90,6 +90,8 @@ functional tests. To do so, you can use the following two commands:: $ LC_ALL=en_US.utf8 pelican -o pelican/tests/output/custom/ \ -s samples/pelican.conf.py samples/content/ + $ LC_ALL=fr_FR.utf8 pelican -o pelican/tests/output/custom_locale/ \ + -s samples/pelican_FR.conf.py samples/content/ $ LC_ALL=en_US.utf8 pelican -o pelican/tests/output/basic/ \ samples/content/ diff --git a/pelican/contents.py b/pelican/contents.py index 220db611..297a537b 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -11,13 +11,12 @@ import os import re import sys -from datetime import datetime from pelican import signals from pelican.settings import DEFAULT_CONFIG from pelican.utils import (slugify, truncate_html_words, memoized, strftime, python_2_unicode_compatible, deprecated_attribute, - path_to_url) + path_to_url, SafeDatetime) # Import these so that they're avalaible when you import from pelican.contents. from pelican.urlwrappers import (URLWrapper, Author, Category, Tag) # NOQA @@ -127,7 +126,7 @@ class Content(object): if not hasattr(self, 'status'): self.status = settings['DEFAULT_STATUS'] if not settings['WITH_FUTURE_DATES']: - if hasattr(self, 'date') and self.date > datetime.now(): + if hasattr(self, 'date') and self.date > SafeDatetime.now(): self.status = 'draft' # store the summary metadata if it is set @@ -161,7 +160,7 @@ class Content(object): 'path': path_to_url(path), 'slug': getattr(self, 'slug', ''), 'lang': getattr(self, 'lang', 'en'), - 'date': getattr(self, 'date', datetime.now()), + 'date': getattr(self, 'date', SafeDatetime.now()), 'author': slugify( getattr(self, 'author', ''), slug_substitutions diff --git a/pelican/generators.py b/pelican/generators.py index deb237a2..865cc6f8 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals, print_function import os +import six import math import random import logging @@ -348,31 +349,22 @@ class ArticlesGenerator(CachingGenerator): # format string syntax can be used for specifying the # period archive dates date = archive[0].date - # Under python 2, with non-ascii locales, u"{:%b}".format(date) might raise UnicodeDecodeError - # because u"{:%b}".format(date) will call date.__format__(u"%b"), which will return a byte string - # and not a unicode string. - # eg: - # locale.setlocale(locale.LC_ALL, 'ja_JP.utf8') - # date.__format__(u"%b") == '12\xe6\x9c\x88' # True - try: - save_as = save_as_fmt.format(date=date) - except UnicodeDecodeError: - # Python2 only: - # Let date.__format__() work with byte strings instead of characters since it fails to work with characters - bytes_save_as_fmt = save_as_fmt.encode('utf8') - bytes_save_as = bytes_save_as_fmt.format(date=date) - save_as = unicode(bytes_save_as,'utf8') + save_as = save_as_fmt.format(date=date) context = self.context.copy() if key == period_date_key['year']: context["period"] = (_period,) - elif key == period_date_key['month']: - context["period"] = (_period[0], - calendar.month_name[_period[1]]) else: - context["period"] = (_period[0], - calendar.month_name[_period[1]], - _period[2]) + month_name = calendar.month_name[_period[1]] + if not six.PY3: + month_name = month_name.decode('utf-8') + if key == period_date_key['month']: + context["period"] = (_period[0], + month_name) + else: + context["period"] = (_period[0], + month_name, + _period[2]) write(save_as, template, context, dates=archive, blog=True) diff --git a/pelican/readers.py b/pelican/readers.py index 431e6937..e977b349 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, print_function -import datetime import logging import os import re @@ -28,7 +27,7 @@ except ImportError: from pelican import signals from pelican.contents import Page, Category, Tag, Author -from pelican.utils import get_date, pelican_open, FileStampDataCacher +from pelican.utils import get_date, pelican_open, FileStampDataCacher, SafeDatetime METADATA_PROCESSORS = { @@ -494,7 +493,7 @@ def default_metadata(settings=None, process=None): value = process('category', value) metadata['category'] = value if settings.get('DEFAULT_DATE', None) and settings['DEFAULT_DATE'] != 'fs': - metadata['date'] = datetime.datetime(*settings['DEFAULT_DATE']) + metadata['date'] = SafeDatetime(*settings['DEFAULT_DATE']) return metadata @@ -502,7 +501,7 @@ def path_metadata(full_path, source_path, settings=None): metadata = {} if settings: if settings.get('DEFAULT_DATE', None) == 'fs': - metadata['date'] = datetime.datetime.fromtimestamp( + metadata['date'] = SafeDatetime.fromtimestamp( os.stat(full_path).st_ctime) metadata.update(settings.get('EXTRA_PATH_METADATA', {}).get( source_path, {})) @@ -525,7 +524,7 @@ def parse_path_metadata(source_path, settings=None, process=None): ... process=reader.process_metadata) >>> pprint.pprint(metadata) # doctest: +ELLIPSIS {'category': , - 'date': datetime.datetime(2013, 1, 1, 0, 0), + 'date': SafeDatetime(2013, 1, 1, 0, 0), 'slug': 'my-slug'} """ metadata = {} diff --git a/pelican/tests/output/custom_locale/archives.html b/pelican/tests/output/custom_locale/archives.html new file mode 100644 index 00000000..a7b96336 --- /dev/null +++ b/pelican/tests/output/custom_locale/archives.html @@ -0,0 +1,100 @@ + + + + + Alexis' log + + + + + + + + + +Fork me on GitHub + + +
+

Archives for Alexis' log

+ +
+
ven. 30 novembre 2012
+
FILENAME_METADATA example
+
mer. 29 février 2012
+
Second article
+
mer. 20 avril 2011
+
A markdown powered article
+
jeu. 17 février 2011
+
Article 1
+
jeu. 17 février 2011
+
Article 2
+
jeu. 17 février 2011
+
Article 3
+
jeu. 02 décembre 2010
+
This is a super article !
+
mer. 20 octobre 2010
+
Oh yeah !
+
ven. 15 octobre 2010
+
Unbelievable !
+
dim. 14 mars 2010
+
The baz tag
+
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau.html b/pelican/tests/output/custom_locale/author/alexis-metaireau.html new file mode 100644 index 00000000..b54446c8 --- /dev/null +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau.html @@ -0,0 +1,173 @@ + + + + + Alexis' log - Alexis Métaireau + + + + + + + + + +Fork me on GitHub + + + + +
+

Other articles

+
+
    + +
  1. + +
  2. + +
  3. +
+

+ Page 1 / 3 + » +

+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau2.html b/pelican/tests/output/custom_locale/author/alexis-metaireau2.html new file mode 100644 index 00000000..07020512 --- /dev/null +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau2.html @@ -0,0 +1,187 @@ + + + + + Alexis' log - Alexis Métaireau + + + + + + + + + +Fork me on GitHub + + + +
+
    +
  1. + +
  2. + +
  3. + +
  4. +
    +

    Oh yeah !

    +
    + +
    +
    +

    Why not ?

    +

    After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !

    +alternate text +
    + + read more +

    There are comments.

    +
  5. +
+

+ « + Page 2 / 3 + » +

+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html new file mode 100644 index 00000000..9578e3d6 --- /dev/null +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html @@ -0,0 +1,138 @@ + + + + + Alexis' log - Alexis Métaireau + + + + + + + + + +Fork me on GitHub + + + +
+
    +
  1. + +
  2. +
+

+ « + Page 3 / 3 +

+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/authors.html b/pelican/tests/output/custom_locale/authors.html new file mode 100644 index 00000000..2558c4d8 --- /dev/null +++ b/pelican/tests/output/custom_locale/authors.html @@ -0,0 +1,82 @@ + + + + + Alexis' log - Authors + + + + + + + + + +Fork me on GitHub + + + +
+

Authors on Alexis' log

+ +
+ +
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/categories.html b/pelican/tests/output/custom_locale/categories.html new file mode 100644 index 00000000..17d9de76 --- /dev/null +++ b/pelican/tests/output/custom_locale/categories.html @@ -0,0 +1,80 @@ + + + + + Alexis' log + + + + + + + + + +Fork me on GitHub + + + +
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/category/bar.html b/pelican/tests/output/custom_locale/category/bar.html new file mode 100644 index 00000000..d9fc6acb --- /dev/null +++ b/pelican/tests/output/custom_locale/category/bar.html @@ -0,0 +1,101 @@ + + + + + Alexis' log - bar + + + + + + + + + +Fork me on GitHub + + + + +
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/category/cat1.html b/pelican/tests/output/custom_locale/category/cat1.html new file mode 100644 index 00000000..1b09acfe --- /dev/null +++ b/pelican/tests/output/custom_locale/category/cat1.html @@ -0,0 +1,170 @@ + + + + + Alexis' log - cat1 + + + + + + + + + +Fork me on GitHub + + + + +
+

Other articles

+
+
    + +
  1. + +
  2. + +
  3. +
+

+ Page 1 / 1 +

+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/category/misc.html b/pelican/tests/output/custom_locale/category/misc.html new file mode 100644 index 00000000..bcaec248 --- /dev/null +++ b/pelican/tests/output/custom_locale/category/misc.html @@ -0,0 +1,181 @@ + + + + + Alexis' log - misc + + + + + + + + + +Fork me on GitHub + + + + +
+

Other articles

+
+
    + +
  1. + +
  2. + +
  3. +
+

+ Page 1 / 1 +

+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/category/yeah.html b/pelican/tests/output/custom_locale/category/yeah.html new file mode 100644 index 00000000..7f881612 --- /dev/null +++ b/pelican/tests/output/custom_locale/category/yeah.html @@ -0,0 +1,109 @@ + + + + + Alexis' log - yeah + + + + + + + + + +Fork me on GitHub + + + + +
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/drafts/a-draft-article.html b/pelican/tests/output/custom_locale/drafts/a-draft-article.html new file mode 100644 index 00000000..82fb057b --- /dev/null +++ b/pelican/tests/output/custom_locale/drafts/a-draft-article.html @@ -0,0 +1,100 @@ + + + + + A draft article + + + + + + + + + +Fork me on GitHub + + +
+
+
+

+ A draft article

+
+ +
+

This is a draft article, it should live under the /drafts/ folder and not be +listed anywhere else.

+ +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/alexis-metaireau.atom.xml b/pelican/tests/output/custom_locale/feeds/alexis-metaireau.atom.xml new file mode 100644 index 00000000..202b9f71 --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/alexis-metaireau.atom.xml @@ -0,0 +1,61 @@ + +Alexis' loghttp://blog.notmyidea.org/2013-11-17T23:29:00+01:00FILENAME_METADATA example2012-11-30T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-11-30:posts/2012/November/30/filename_metadata-example/<p>Some cool stuff!</p> +Second article2012-02-29T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-02-29:posts/2012/February/29/second-article/<p>This is some article, in english</p> +A markdown powered article2011-04-20T00:00:00+02:00Alexis Métaireautag:blog.notmyidea.org,2011-04-20:posts/2011/April/20/a-markdown-powered-article/<p>You're mutually oblivious.</p> +<p><a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a root-relative link to unbelievable</a> +<a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a file-relative link to unbelievable</a></p>Article 12011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-1/<p>Article 1</p> +Article 22011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-2/<p>Article 2</p> +Article 32011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-3/<p>Article 3</p> +This is a super article !2013-11-17T23:29:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-12-02:posts/2010/December/02/this-is-a-super-article/<p>Some content here !</p> +<div class="section" id="this-is-a-simple-title"> +<h2>This is a simple title</h2> +<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" /> +<pre class="literal-block"> +&gt;&gt;&gt; from ipdb import set_trace +&gt;&gt;&gt; set_trace() +</pre> +<p>→ And now try with some utf8 hell: ééé</p> +</div> +Oh yeah !2010-10-20T10:14:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-20:posts/2010/October/20/oh-yeah/<div class="section" id="why-not"> +<h2>Why not ?</h2> +<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +</div> +Unbelievable !2010-10-15T20:30:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-15:posts/2010/October/15/unbelievable/<p>Or completely awesome. Depends the needs.</p> +<p><a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a root-relative link to markdown-article</a> +<a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> +<div class="section" id="testing-sourcecode-directive"> +<h2>Testing sourcecode directive</h2> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table></div> +<div class="section" id="testing-another-case"> +<h2>Testing another case</h2> +<p>This will now have a line number in 'custom' since it's the default in +pelican.conf, it will have nothing in default.</p> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table><p>Lovely.</p> +</div> +<div class="section" id="testing-more-sourcecode-directives"> +<h2>Testing more sourcecode directives</h2> +<div class="highlight"><pre><span id="foo-8"><a name="foo-8"></a><span class="lineno special"> 8</span> <span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><span class="lineno special">10</span> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><span class="lineno"> </span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><span class="lineno special">12</span> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><span class="lineno"> </span> <span class="testingc"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><span class="lineno special">14</span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><span class="lineno"> </span> <br></span><span id="foo-16"><a name="foo-16"></a><span class="lineno special">16</span> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><span class="lineno special">18</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><span class="lineno"> </span> <br></span><span id="foo-20"><a name="foo-20"></a><span class="lineno special">20</span> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><span class="lineno"> </span> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><span class="lineno special">22</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingbp">True</span><br></span><span id="foo-23"><a name="foo-23"></a><span class="lineno"> </span> <br></span><span id="foo-24"><a name="foo-24"></a><span class="lineno special">24</span> <span class="testingc"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><span class="lineno"> </span> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingbp">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><span class="lineno special">26</span> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings">&#39;</span><span class="testingse">\n</span><span class="testings">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><span class="lineno"> </span> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingn">format</span><span class="testingo">=</span><span class="testings">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<p>Lovely.</p> +</div> +<div class="section" id="testing-even-more-sourcecode-directives"> +<h2>Testing even more sourcecode directives</h2> +<span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +<p>Lovely.</p> +</div> +<div class="section" id="testing-overriding-config-defaults"> +<h2>Testing overriding config defaults</h2> +<p>Even if the default is line numbers, we can override it here</p> +<div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +<p>Lovely.</p> +</div> +The baz tag2010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/alexis-metaireau.rss.xml b/pelican/tests/output/custom_locale/feeds/alexis-metaireau.rss.xml new file mode 100644 index 00000000..dfb83630 --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/alexis-metaireau.rss.xml @@ -0,0 +1,61 @@ + +Alexis' loghttp://blog.notmyidea.org/Sun, 17 Nov 2013 23:29:00 +0100FILENAME_METADATA examplehttp://blog.notmyidea.org/posts/2012/November/30/filename_metadata-example/<p>Some cool stuff!</p> +Alexis MétaireauFri, 30 Nov 2012 00:00:00 +0100tag:blog.notmyidea.org,2012-11-30:posts/2012/November/30/filename_metadata-example/Second articlehttp://blog.notmyidea.org/posts/2012/February/29/second-article/<p>This is some article, in english</p> +Alexis MétaireauWed, 29 Feb 2012 00:00:00 +0100tag:blog.notmyidea.org,2012-02-29:posts/2012/February/29/second-article/foobarbazA markdown powered articlehttp://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/<p>You're mutually oblivious.</p> +<p><a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a root-relative link to unbelievable</a> +<a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a file-relative link to unbelievable</a></p>Alexis MétaireauWed, 20 Apr 2011 00:00:00 +0200tag:blog.notmyidea.org,2011-04-20:posts/2011/April/20/a-markdown-powered-article/Article 1http://blog.notmyidea.org/posts/2011/February/17/article-1/<p>Article 1</p> +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-1/Article 2http://blog.notmyidea.org/posts/2011/February/17/article-2/<p>Article 2</p> +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-2/Article 3http://blog.notmyidea.org/posts/2011/February/17/article-3/<p>Article 3</p> +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-3/This is a super article !http://blog.notmyidea.org/posts/2010/December/02/this-is-a-super-article/<p>Some content here !</p> +<div class="section" id="this-is-a-simple-title"> +<h2>This is a simple title</h2> +<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" /> +<pre class="literal-block"> +&gt;&gt;&gt; from ipdb import set_trace +&gt;&gt;&gt; set_trace() +</pre> +<p>→ And now try with some utf8 hell: ééé</p> +</div> +Alexis MétaireauSun, 17 Nov 2013 23:29:00 +0100tag:blog.notmyidea.org,2010-12-02:posts/2010/December/02/this-is-a-super-article/foobarfoobarOh yeah !http://blog.notmyidea.org/posts/2010/October/20/oh-yeah/<div class="section" id="why-not"> +<h2>Why not ?</h2> +<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +</div> +Alexis MétaireauWed, 20 Oct 2010 10:14:00 +0200tag:blog.notmyidea.org,2010-10-20:posts/2010/October/20/oh-yeah/ohbaryeahUnbelievable !http://blog.notmyidea.org/posts/2010/October/15/unbelievable/<p>Or completely awesome. Depends the needs.</p> +<p><a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a root-relative link to markdown-article</a> +<a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> +<div class="section" id="testing-sourcecode-directive"> +<h2>Testing sourcecode directive</h2> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table></div> +<div class="section" id="testing-another-case"> +<h2>Testing another case</h2> +<p>This will now have a line number in 'custom' since it's the default in +pelican.conf, it will have nothing in default.</p> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table><p>Lovely.</p> +</div> +<div class="section" id="testing-more-sourcecode-directives"> +<h2>Testing more sourcecode directives</h2> +<div class="highlight"><pre><span id="foo-8"><a name="foo-8"></a><span class="lineno special"> 8</span> <span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><span class="lineno special">10</span> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><span class="lineno"> </span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><span class="lineno special">12</span> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><span class="lineno"> </span> <span class="testingc"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><span class="lineno special">14</span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><span class="lineno"> </span> <br></span><span id="foo-16"><a name="foo-16"></a><span class="lineno special">16</span> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><span class="lineno special">18</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><span class="lineno"> </span> <br></span><span id="foo-20"><a name="foo-20"></a><span class="lineno special">20</span> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><span class="lineno"> </span> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><span class="lineno special">22</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingbp">True</span><br></span><span id="foo-23"><a name="foo-23"></a><span class="lineno"> </span> <br></span><span id="foo-24"><a name="foo-24"></a><span class="lineno special">24</span> <span class="testingc"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><span class="lineno"> </span> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingbp">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><span class="lineno special">26</span> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings">&#39;</span><span class="testingse">\n</span><span class="testings">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><span class="lineno"> </span> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingn">format</span><span class="testingo">=</span><span class="testings">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<p>Lovely.</p> +</div> +<div class="section" id="testing-even-more-sourcecode-directives"> +<h2>Testing even more sourcecode directives</h2> +<span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +<p>Lovely.</p> +</div> +<div class="section" id="testing-overriding-config-defaults"> +<h2>Testing overriding config defaults</h2> +<p>Even if the default is line numbers, we can override it here</p> +<div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +<p>Lovely.</p> +</div> +Alexis MétaireauFri, 15 Oct 2010 20:30:00 +0200tag:blog.notmyidea.org,2010-10-15:posts/2010/October/15/unbelievable/The baz taghttp://blog.notmyidea.org/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> +Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:tag/baz.html \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/all-en.atom.xml b/pelican/tests/output/custom_locale/feeds/all-en.atom.xml new file mode 100644 index 00000000..3bb10e38 --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/all-en.atom.xml @@ -0,0 +1,61 @@ + +Alexis' loghttp://blog.notmyidea.org/2013-11-17T23:29:00+01:00FILENAME_METADATA example2012-11-30T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-11-30:posts/2012/November/30/filename_metadata-example/<p>Some cool stuff!</p> +Second article2012-02-29T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-02-29:posts/2012/February/29/second-article/<p>This is some article, in english</p> +A markdown powered article2011-04-20T00:00:00+02:00Alexis Métaireautag:blog.notmyidea.org,2011-04-20:posts/2011/April/20/a-markdown-powered-article/<p>You're mutually oblivious.</p> +<p><a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a root-relative link to unbelievable</a> +<a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a file-relative link to unbelievable</a></p>Article 12011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-1/<p>Article 1</p> +Article 22011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-2/<p>Article 2</p> +Article 32011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-3/<p>Article 3</p> +This is a super article !2013-11-17T23:29:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-12-02:posts/2010/December/02/this-is-a-super-article/<p>Some content here !</p> +<div class="section" id="this-is-a-simple-title"> +<h2>This is a simple title</h2> +<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" /> +<pre class="literal-block"> +&gt;&gt;&gt; from ipdb import set_trace +&gt;&gt;&gt; set_trace() +</pre> +<p>→ And now try with some utf8 hell: ééé</p> +</div> +Oh yeah !2010-10-20T10:14:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-20:posts/2010/October/20/oh-yeah/<div class="section" id="why-not"> +<h2>Why not ?</h2> +<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +</div> +Unbelievable !2010-10-15T20:30:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-15:posts/2010/October/15/unbelievable/<p>Or completely awesome. Depends the needs.</p> +<p><a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a root-relative link to markdown-article</a> +<a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> +<div class="section" id="testing-sourcecode-directive"> +<h2>Testing sourcecode directive</h2> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table></div> +<div class="section" id="testing-another-case"> +<h2>Testing another case</h2> +<p>This will now have a line number in 'custom' since it's the default in +pelican.conf, it will have nothing in default.</p> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table><p>Lovely.</p> +</div> +<div class="section" id="testing-more-sourcecode-directives"> +<h2>Testing more sourcecode directives</h2> +<div class="highlight"><pre><span id="foo-8"><a name="foo-8"></a><span class="lineno special"> 8</span> <span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><span class="lineno special">10</span> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><span class="lineno"> </span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><span class="lineno special">12</span> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><span class="lineno"> </span> <span class="testingc"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><span class="lineno special">14</span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><span class="lineno"> </span> <br></span><span id="foo-16"><a name="foo-16"></a><span class="lineno special">16</span> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><span class="lineno special">18</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><span class="lineno"> </span> <br></span><span id="foo-20"><a name="foo-20"></a><span class="lineno special">20</span> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><span class="lineno"> </span> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><span class="lineno special">22</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingbp">True</span><br></span><span id="foo-23"><a name="foo-23"></a><span class="lineno"> </span> <br></span><span id="foo-24"><a name="foo-24"></a><span class="lineno special">24</span> <span class="testingc"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><span class="lineno"> </span> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingbp">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><span class="lineno special">26</span> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings">&#39;</span><span class="testingse">\n</span><span class="testings">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><span class="lineno"> </span> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingn">format</span><span class="testingo">=</span><span class="testings">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<p>Lovely.</p> +</div> +<div class="section" id="testing-even-more-sourcecode-directives"> +<h2>Testing even more sourcecode directives</h2> +<span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +<p>Lovely.</p> +</div> +<div class="section" id="testing-overriding-config-defaults"> +<h2>Testing overriding config defaults</h2> +<p>Even if the default is line numbers, we can override it here</p> +<div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +<p>Lovely.</p> +</div> +The baz tag2010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/all-fr.atom.xml b/pelican/tests/output/custom_locale/feeds/all-fr.atom.xml new file mode 100644 index 00000000..5d58742c --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/all-fr.atom.xml @@ -0,0 +1,4 @@ + +Alexis' loghttp://blog.notmyidea.org/2012-03-02T14:01:01+01:00Trop bien !2012-03-02T14:01:01+01:00Alexis Métaireautag:blog.notmyidea.org,2012-03-02:oh-yeah-fr.html<p>Et voila du contenu en français</p> +Deuxième article2012-02-29T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-02-29:second-article-fr.html<p>Ceci est un article, en français.</p> + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/all.atom.xml b/pelican/tests/output/custom_locale/feeds/all.atom.xml new file mode 100644 index 00000000..f709f2b1 --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/all.atom.xml @@ -0,0 +1,63 @@ + +Alexis' loghttp://blog.notmyidea.org/2013-11-17T23:29:00+01:00FILENAME_METADATA example2012-11-30T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-11-30:posts/2012/November/30/filename_metadata-example/<p>Some cool stuff!</p> +Trop bien !2012-03-02T14:01:01+01:00Alexis Métaireautag:blog.notmyidea.org,2012-03-02:oh-yeah-fr.html<p>Et voila du contenu en français</p> +Second article2012-02-29T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-02-29:posts/2012/February/29/second-article/<p>This is some article, in english</p> +Deuxième article2012-02-29T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-02-29:second-article-fr.html<p>Ceci est un article, en français.</p> +A markdown powered article2011-04-20T00:00:00+02:00Alexis Métaireautag:blog.notmyidea.org,2011-04-20:posts/2011/April/20/a-markdown-powered-article/<p>You're mutually oblivious.</p> +<p><a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a root-relative link to unbelievable</a> +<a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a file-relative link to unbelievable</a></p>Article 12011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-1/<p>Article 1</p> +Article 22011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-2/<p>Article 2</p> +Article 32011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-3/<p>Article 3</p> +This is a super article !2013-11-17T23:29:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-12-02:posts/2010/December/02/this-is-a-super-article/<p>Some content here !</p> +<div class="section" id="this-is-a-simple-title"> +<h2>This is a simple title</h2> +<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" /> +<pre class="literal-block"> +&gt;&gt;&gt; from ipdb import set_trace +&gt;&gt;&gt; set_trace() +</pre> +<p>→ And now try with some utf8 hell: ééé</p> +</div> +Oh yeah !2010-10-20T10:14:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-20:posts/2010/October/20/oh-yeah/<div class="section" id="why-not"> +<h2>Why not ?</h2> +<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +</div> +Unbelievable !2010-10-15T20:30:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-15:posts/2010/October/15/unbelievable/<p>Or completely awesome. Depends the needs.</p> +<p><a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a root-relative link to markdown-article</a> +<a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> +<div class="section" id="testing-sourcecode-directive"> +<h2>Testing sourcecode directive</h2> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table></div> +<div class="section" id="testing-another-case"> +<h2>Testing another case</h2> +<p>This will now have a line number in 'custom' since it's the default in +pelican.conf, it will have nothing in default.</p> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table><p>Lovely.</p> +</div> +<div class="section" id="testing-more-sourcecode-directives"> +<h2>Testing more sourcecode directives</h2> +<div class="highlight"><pre><span id="foo-8"><a name="foo-8"></a><span class="lineno special"> 8</span> <span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><span class="lineno special">10</span> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><span class="lineno"> </span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><span class="lineno special">12</span> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><span class="lineno"> </span> <span class="testingc"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><span class="lineno special">14</span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><span class="lineno"> </span> <br></span><span id="foo-16"><a name="foo-16"></a><span class="lineno special">16</span> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><span class="lineno special">18</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><span class="lineno"> </span> <br></span><span id="foo-20"><a name="foo-20"></a><span class="lineno special">20</span> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><span class="lineno"> </span> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><span class="lineno special">22</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingbp">True</span><br></span><span id="foo-23"><a name="foo-23"></a><span class="lineno"> </span> <br></span><span id="foo-24"><a name="foo-24"></a><span class="lineno special">24</span> <span class="testingc"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><span class="lineno"> </span> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingbp">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><span class="lineno special">26</span> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings">&#39;</span><span class="testingse">\n</span><span class="testings">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><span class="lineno"> </span> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingn">format</span><span class="testingo">=</span><span class="testings">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<p>Lovely.</p> +</div> +<div class="section" id="testing-even-more-sourcecode-directives"> +<h2>Testing even more sourcecode directives</h2> +<span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +<p>Lovely.</p> +</div> +<div class="section" id="testing-overriding-config-defaults"> +<h2>Testing overriding config defaults</h2> +<p>Even if the default is line numbers, we can override it here</p> +<div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +<p>Lovely.</p> +</div> +The baz tag2010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/all.rss.xml b/pelican/tests/output/custom_locale/feeds/all.rss.xml new file mode 100644 index 00000000..39fbc240 --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/all.rss.xml @@ -0,0 +1,63 @@ + +Alexis' loghttp://blog.notmyidea.org/Sun, 17 Nov 2013 23:29:00 +0100FILENAME_METADATA examplehttp://blog.notmyidea.org/posts/2012/November/30/filename_metadata-example/<p>Some cool stuff!</p> +Alexis MétaireauFri, 30 Nov 2012 00:00:00 +0100tag:blog.notmyidea.org,2012-11-30:posts/2012/November/30/filename_metadata-example/Trop bien !http://blog.notmyidea.org/oh-yeah-fr.html<p>Et voila du contenu en français</p> +Alexis MétaireauFri, 02 Mar 2012 14:01:01 +0100tag:blog.notmyidea.org,2012-03-02:oh-yeah-fr.htmlSecond articlehttp://blog.notmyidea.org/posts/2012/February/29/second-article/<p>This is some article, in english</p> +Alexis MétaireauWed, 29 Feb 2012 00:00:00 +0100tag:blog.notmyidea.org,2012-02-29:posts/2012/February/29/second-article/foobarbazDeuxième articlehttp://blog.notmyidea.org/second-article-fr.html<p>Ceci est un article, en français.</p> +Alexis MétaireauWed, 29 Feb 2012 00:00:00 +0100tag:blog.notmyidea.org,2012-02-29:second-article-fr.htmlfoobarbazA markdown powered articlehttp://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/<p>You're mutually oblivious.</p> +<p><a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a root-relative link to unbelievable</a> +<a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a file-relative link to unbelievable</a></p>Alexis MétaireauWed, 20 Apr 2011 00:00:00 +0200tag:blog.notmyidea.org,2011-04-20:posts/2011/April/20/a-markdown-powered-article/Article 1http://blog.notmyidea.org/posts/2011/February/17/article-1/<p>Article 1</p> +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-1/Article 2http://blog.notmyidea.org/posts/2011/February/17/article-2/<p>Article 2</p> +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-2/Article 3http://blog.notmyidea.org/posts/2011/February/17/article-3/<p>Article 3</p> +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-3/This is a super article !http://blog.notmyidea.org/posts/2010/December/02/this-is-a-super-article/<p>Some content here !</p> +<div class="section" id="this-is-a-simple-title"> +<h2>This is a simple title</h2> +<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" /> +<pre class="literal-block"> +&gt;&gt;&gt; from ipdb import set_trace +&gt;&gt;&gt; set_trace() +</pre> +<p>→ And now try with some utf8 hell: ééé</p> +</div> +Alexis MétaireauSun, 17 Nov 2013 23:29:00 +0100tag:blog.notmyidea.org,2010-12-02:posts/2010/December/02/this-is-a-super-article/foobarfoobarOh yeah !http://blog.notmyidea.org/posts/2010/October/20/oh-yeah/<div class="section" id="why-not"> +<h2>Why not ?</h2> +<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +</div> +Alexis MétaireauWed, 20 Oct 2010 10:14:00 +0200tag:blog.notmyidea.org,2010-10-20:posts/2010/October/20/oh-yeah/ohbaryeahUnbelievable !http://blog.notmyidea.org/posts/2010/October/15/unbelievable/<p>Or completely awesome. Depends the needs.</p> +<p><a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a root-relative link to markdown-article</a> +<a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> +<div class="section" id="testing-sourcecode-directive"> +<h2>Testing sourcecode directive</h2> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table></div> +<div class="section" id="testing-another-case"> +<h2>Testing another case</h2> +<p>This will now have a line number in 'custom' since it's the default in +pelican.conf, it will have nothing in default.</p> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table><p>Lovely.</p> +</div> +<div class="section" id="testing-more-sourcecode-directives"> +<h2>Testing more sourcecode directives</h2> +<div class="highlight"><pre><span id="foo-8"><a name="foo-8"></a><span class="lineno special"> 8</span> <span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><span class="lineno special">10</span> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><span class="lineno"> </span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><span class="lineno special">12</span> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><span class="lineno"> </span> <span class="testingc"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><span class="lineno special">14</span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><span class="lineno"> </span> <br></span><span id="foo-16"><a name="foo-16"></a><span class="lineno special">16</span> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><span class="lineno special">18</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><span class="lineno"> </span> <br></span><span id="foo-20"><a name="foo-20"></a><span class="lineno special">20</span> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><span class="lineno"> </span> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><span class="lineno special">22</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingbp">True</span><br></span><span id="foo-23"><a name="foo-23"></a><span class="lineno"> </span> <br></span><span id="foo-24"><a name="foo-24"></a><span class="lineno special">24</span> <span class="testingc"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><span class="lineno"> </span> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingbp">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><span class="lineno special">26</span> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings">&#39;</span><span class="testingse">\n</span><span class="testings">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><span class="lineno"> </span> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingn">format</span><span class="testingo">=</span><span class="testings">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<p>Lovely.</p> +</div> +<div class="section" id="testing-even-more-sourcecode-directives"> +<h2>Testing even more sourcecode directives</h2> +<span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +<p>Lovely.</p> +</div> +<div class="section" id="testing-overriding-config-defaults"> +<h2>Testing overriding config defaults</h2> +<p>Even if the default is line numbers, we can override it here</p> +<div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +<p>Lovely.</p> +</div> +Alexis MétaireauFri, 15 Oct 2010 20:30:00 +0200tag:blog.notmyidea.org,2010-10-15:posts/2010/October/15/unbelievable/The baz taghttp://blog.notmyidea.org/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> +Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:tag/baz.html \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/bar.atom.xml b/pelican/tests/output/custom_locale/feeds/bar.atom.xml new file mode 100644 index 00000000..13a5cde2 --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/bar.atom.xml @@ -0,0 +1,8 @@ + +Alexis' loghttp://blog.notmyidea.org/2010-10-20T10:14:00+02:00Oh yeah !2010-10-20T10:14:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-20:posts/2010/October/20/oh-yeah/<div class="section" id="why-not"> +<h2>Why not ?</h2> +<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +</div> + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/bar.rss.xml b/pelican/tests/output/custom_locale/feeds/bar.rss.xml new file mode 100644 index 00000000..4426eb6a --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/bar.rss.xml @@ -0,0 +1,8 @@ + +Alexis' loghttp://blog.notmyidea.org/Wed, 20 Oct 2010 10:14:00 +0200Oh yeah !http://blog.notmyidea.org/posts/2010/October/20/oh-yeah/<div class="section" id="why-not"> +<h2>Why not ?</h2> +<p>After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +</div> +Alexis MétaireauWed, 20 Oct 2010 10:14:00 +0200tag:blog.notmyidea.org,2010-10-20:posts/2010/October/20/oh-yeah/ohbaryeah \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/cat1.atom.xml b/pelican/tests/output/custom_locale/feeds/cat1.atom.xml new file mode 100644 index 00000000..54d382c4 --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/cat1.atom.xml @@ -0,0 +1,7 @@ + +Alexis' loghttp://blog.notmyidea.org/2011-04-20T00:00:00+02:00A markdown powered article2011-04-20T00:00:00+02:00Alexis Métaireautag:blog.notmyidea.org,2011-04-20:posts/2011/April/20/a-markdown-powered-article/<p>You're mutually oblivious.</p> +<p><a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a root-relative link to unbelievable</a> +<a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a file-relative link to unbelievable</a></p>Article 12011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-1/<p>Article 1</p> +Article 22011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-2/<p>Article 2</p> +Article 32011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-3/<p>Article 3</p> + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/cat1.rss.xml b/pelican/tests/output/custom_locale/feeds/cat1.rss.xml new file mode 100644 index 00000000..4f3b12f5 --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/cat1.rss.xml @@ -0,0 +1,7 @@ + +Alexis' loghttp://blog.notmyidea.org/Wed, 20 Apr 2011 00:00:00 +0200A markdown powered articlehttp://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/<p>You're mutually oblivious.</p> +<p><a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a root-relative link to unbelievable</a> +<a href="http://blog.notmyidea.org/posts/2010/October/15/unbelievable/">a file-relative link to unbelievable</a></p>Alexis MétaireauWed, 20 Apr 2011 00:00:00 +0200tag:blog.notmyidea.org,2011-04-20:posts/2011/April/20/a-markdown-powered-article/Article 1http://blog.notmyidea.org/posts/2011/February/17/article-1/<p>Article 1</p> +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-1/Article 2http://blog.notmyidea.org/posts/2011/February/17/article-2/<p>Article 2</p> +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-2/Article 3http://blog.notmyidea.org/posts/2011/February/17/article-3/<p>Article 3</p> +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:posts/2011/February/17/article-3/ \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/misc.atom.xml b/pelican/tests/output/custom_locale/feeds/misc.atom.xml new file mode 100644 index 00000000..18800485 --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/misc.atom.xml @@ -0,0 +1,38 @@ + +Alexis' loghttp://blog.notmyidea.org/2012-11-30T00:00:00+01:00FILENAME_METADATA example2012-11-30T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-11-30:posts/2012/November/30/filename_metadata-example/<p>Some cool stuff!</p> +Second article2012-02-29T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-02-29:posts/2012/February/29/second-article/<p>This is some article, in english</p> +Unbelievable !2010-10-15T20:30:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-15:posts/2010/October/15/unbelievable/<p>Or completely awesome. Depends the needs.</p> +<p><a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a root-relative link to markdown-article</a> +<a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> +<div class="section" id="testing-sourcecode-directive"> +<h2>Testing sourcecode directive</h2> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table></div> +<div class="section" id="testing-another-case"> +<h2>Testing another case</h2> +<p>This will now have a line number in 'custom' since it's the default in +pelican.conf, it will have nothing in default.</p> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table><p>Lovely.</p> +</div> +<div class="section" id="testing-more-sourcecode-directives"> +<h2>Testing more sourcecode directives</h2> +<div class="highlight"><pre><span id="foo-8"><a name="foo-8"></a><span class="lineno special"> 8</span> <span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><span class="lineno special">10</span> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><span class="lineno"> </span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><span class="lineno special">12</span> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><span class="lineno"> </span> <span class="testingc"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><span class="lineno special">14</span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><span class="lineno"> </span> <br></span><span id="foo-16"><a name="foo-16"></a><span class="lineno special">16</span> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><span class="lineno special">18</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><span class="lineno"> </span> <br></span><span id="foo-20"><a name="foo-20"></a><span class="lineno special">20</span> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><span class="lineno"> </span> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><span class="lineno special">22</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingbp">True</span><br></span><span id="foo-23"><a name="foo-23"></a><span class="lineno"> </span> <br></span><span id="foo-24"><a name="foo-24"></a><span class="lineno special">24</span> <span class="testingc"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><span class="lineno"> </span> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingbp">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><span class="lineno special">26</span> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings">&#39;</span><span class="testingse">\n</span><span class="testings">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><span class="lineno"> </span> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingn">format</span><span class="testingo">=</span><span class="testings">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<p>Lovely.</p> +</div> +<div class="section" id="testing-even-more-sourcecode-directives"> +<h2>Testing even more sourcecode directives</h2> +<span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +<p>Lovely.</p> +</div> +<div class="section" id="testing-overriding-config-defaults"> +<h2>Testing overriding config defaults</h2> +<p>Even if the default is line numbers, we can override it here</p> +<div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +<p>Lovely.</p> +</div> +The baz tag2010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/misc.rss.xml b/pelican/tests/output/custom_locale/feeds/misc.rss.xml new file mode 100644 index 00000000..0be49f79 --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/misc.rss.xml @@ -0,0 +1,38 @@ + +Alexis' loghttp://blog.notmyidea.org/Fri, 30 Nov 2012 00:00:00 +0100FILENAME_METADATA examplehttp://blog.notmyidea.org/posts/2012/November/30/filename_metadata-example/<p>Some cool stuff!</p> +Alexis MétaireauFri, 30 Nov 2012 00:00:00 +0100tag:blog.notmyidea.org,2012-11-30:posts/2012/November/30/filename_metadata-example/Second articlehttp://blog.notmyidea.org/posts/2012/February/29/second-article/<p>This is some article, in english</p> +Alexis MétaireauWed, 29 Feb 2012 00:00:00 +0100tag:blog.notmyidea.org,2012-02-29:posts/2012/February/29/second-article/foobarbazUnbelievable !http://blog.notmyidea.org/posts/2010/October/15/unbelievable/<p>Or completely awesome. Depends the needs.</p> +<p><a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a root-relative link to markdown-article</a> +<a class="reference external" href="http://blog.notmyidea.org/posts/2011/April/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> +<div class="section" id="testing-sourcecode-directive"> +<h2>Testing sourcecode directive</h2> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table></div> +<div class="section" id="testing-another-case"> +<h2>Testing another case</h2> +<p>This will now have a line number in 'custom' since it's the default in +pelican.conf, it will have nothing in default.</p> +<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +</td></tr></table><p>Lovely.</p> +</div> +<div class="section" id="testing-more-sourcecode-directives"> +<h2>Testing more sourcecode directives</h2> +<div class="highlight"><pre><span id="foo-8"><a name="foo-8"></a><span class="lineno special"> 8</span> <span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><span class="lineno special">10</span> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><span class="lineno"> </span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><span class="lineno special">12</span> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><span class="lineno"> </span> <span class="testingc"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><span class="lineno special">14</span> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><span class="lineno"> </span> <br></span><span id="foo-16"><a name="foo-16"></a><span class="lineno special">16</span> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><span class="lineno"> </span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><span class="lineno special">18</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><span class="lineno"> </span> <br></span><span id="foo-20"><a name="foo-20"></a><span class="lineno special">20</span> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><span class="lineno"> </span> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><span class="lineno special">22</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingbp">True</span><br></span><span id="foo-23"><a name="foo-23"></a><span class="lineno"> </span> <br></span><span id="foo-24"><a name="foo-24"></a><span class="lineno special">24</span> <span class="testingc"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><span class="lineno"> </span> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingbp">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><span class="lineno special">26</span> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings">&#39;</span><span class="testingse">\n</span><span class="testings">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><span class="lineno"> </span> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingn">format</span><span class="testingo">=</span><span class="testings">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<p>Lovely.</p> +</div> +<div class="section" id="testing-even-more-sourcecode-directives"> +<h2>Testing even more sourcecode directives</h2> +<span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +<p>Lovely.</p> +</div> +<div class="section" id="testing-overriding-config-defaults"> +<h2>Testing overriding config defaults</h2> +<p>Even if the default is line numbers, we can override it here</p> +<div class="highlight"><pre><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div> +<p>Lovely.</p> +</div> +Alexis MétaireauFri, 15 Oct 2010 20:30:00 +0200tag:blog.notmyidea.org,2010-10-15:posts/2010/October/15/unbelievable/The baz taghttp://blog.notmyidea.org/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> +Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:tag/baz.html \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/yeah.atom.xml b/pelican/tests/output/custom_locale/feeds/yeah.atom.xml new file mode 100644 index 00000000..5f7d8c4f --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/yeah.atom.xml @@ -0,0 +1,14 @@ + +Alexis' loghttp://blog.notmyidea.org/2013-11-17T23:29:00+01:00This is a super article !2013-11-17T23:29:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-12-02:posts/2010/December/02/this-is-a-super-article/<p>Some content here !</p> +<div class="section" id="this-is-a-simple-title"> +<h2>This is a simple title</h2> +<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" /> +<pre class="literal-block"> +&gt;&gt;&gt; from ipdb import set_trace +&gt;&gt;&gt; set_trace() +</pre> +<p>→ And now try with some utf8 hell: ééé</p> +</div> + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/feeds/yeah.rss.xml b/pelican/tests/output/custom_locale/feeds/yeah.rss.xml new file mode 100644 index 00000000..50c5803c --- /dev/null +++ b/pelican/tests/output/custom_locale/feeds/yeah.rss.xml @@ -0,0 +1,14 @@ + +Alexis' loghttp://blog.notmyidea.org/Sun, 17 Nov 2013 23:29:00 +0100This is a super article !http://blog.notmyidea.org/posts/2010/December/02/this-is-a-super-article/<p>Some content here !</p> +<div class="section" id="this-is-a-simple-title"> +<h2>This is a simple title</h2> +<p>And here comes the cool <a class="reference external" href="http://books.couchdb.org/relax/design-documents/views">stuff</a>.</p> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> +<img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi_Macro.jpg" style="width: 600px; height: 450px;" /> +<pre class="literal-block"> +&gt;&gt;&gt; from ipdb import set_trace +&gt;&gt;&gt; set_trace() +</pre> +<p>→ And now try with some utf8 hell: ééé</p> +</div> +Alexis MétaireauSun, 17 Nov 2013 23:29:00 +0100tag:blog.notmyidea.org,2010-12-02:posts/2010/December/02/this-is-a-super-article/foobarfoobar \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/index.html b/pelican/tests/output/custom_locale/index.html new file mode 100644 index 00000000..fd9a82b4 --- /dev/null +++ b/pelican/tests/output/custom_locale/index.html @@ -0,0 +1,173 @@ + + + + + Alexis' log + + + + + + + + + +Fork me on GitHub + + + + +
+

Other articles

+
+
    + +
  1. + +
  2. + +
  3. +
+

+ Page 1 / 3 + » +

+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/index2.html b/pelican/tests/output/custom_locale/index2.html new file mode 100644 index 00000000..f02ba27c --- /dev/null +++ b/pelican/tests/output/custom_locale/index2.html @@ -0,0 +1,187 @@ + + + + + Alexis' log + + + + + + + + + +Fork me on GitHub + + + +
+
    +
  1. + +
  2. + +
  3. + +
  4. +
    +

    Oh yeah !

    +
    + +
    +
    +

    Why not ?

    +

    After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !

    +alternate text +
    + + read more +

    There are comments.

    +
  5. +
+

+ « + Page 2 / 3 + » +

+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/index3.html b/pelican/tests/output/custom_locale/index3.html new file mode 100644 index 00000000..9c7416b6 --- /dev/null +++ b/pelican/tests/output/custom_locale/index3.html @@ -0,0 +1,138 @@ + + + + + Alexis' log + + + + + + + + + +Fork me on GitHub + + + +
+
    +
  1. + +
  2. +
+

+ « + Page 3 / 3 +

+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/jinja2_template.html b/pelican/tests/output/custom_locale/jinja2_template.html new file mode 100644 index 00000000..0eafa913 --- /dev/null +++ b/pelican/tests/output/custom_locale/jinja2_template.html @@ -0,0 +1,77 @@ + + + + + Alexis' log + + + + + + + + + +Fork me on GitHub + + + +Some text + +
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/oh-yeah-fr.html b/pelican/tests/output/custom_locale/oh-yeah-fr.html new file mode 100644 index 00000000..cdda855d --- /dev/null +++ b/pelican/tests/output/custom_locale/oh-yeah-fr.html @@ -0,0 +1,116 @@ + + + + + Trop bien ! + + + + + + + + + +Fork me on GitHub + + +
+
+
+

+ Trop bien !

+
+ +
+

Et voila du contenu en français

+ +
+
+

Comments !

+
+ + +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/override/index.html b/pelican/tests/output/custom_locale/override/index.html new file mode 100644 index 00000000..e84d79fe --- /dev/null +++ b/pelican/tests/output/custom_locale/override/index.html @@ -0,0 +1,81 @@ + + + + + Override url/save_as + + + + + + + + + +Fork me on GitHub + + +
+

Override url/save_as

+ +

Test page which overrides save_as and url so that this page will be generated +at a custom location.

+ +
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/pages/this-is-a-test-hidden-page.html b/pelican/tests/output/custom_locale/pages/this-is-a-test-hidden-page.html new file mode 100644 index 00000000..dced8107 --- /dev/null +++ b/pelican/tests/output/custom_locale/pages/this-is-a-test-hidden-page.html @@ -0,0 +1,81 @@ + + + + + This is a test hidden page + + + + + + + + + +Fork me on GitHub + + +
+

This is a test hidden page

+ +

This is great for things like error(404) pages +Anyone can see this page but it's not linked to anywhere!

+ +
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/pages/this-is-a-test-page.html b/pelican/tests/output/custom_locale/pages/this-is-a-test-page.html new file mode 100644 index 00000000..46ea4fef --- /dev/null +++ b/pelican/tests/output/custom_locale/pages/this-is-a-test-page.html @@ -0,0 +1,81 @@ + + + + + This is a test page + + + + + + + + + +Fork me on GitHub + + +
+

This is a test page

+ +

Just an image.

+alternate text + +
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/pictures/Fat_Cat.jpg b/pelican/tests/output/custom_locale/pictures/Fat_Cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d8a96d356be94e782aaf36ba4e47168435b56338 GIT binary patch literal 62675 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

)n(M0K@w($Mad^U|4b&>Gs6u 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 literal 0 HcmV?d00001 diff --git a/pelican/tests/output/custom_locale/pictures/Sushi.jpg b/pelican/tests/output/custom_locale/pictures/Sushi.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e49e5f0ab53461187c2cf61ebc1f3befdc937aed GIT binary patch literal 28992 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 V9RVclK)_SR8Zrq#de{_0|JgZd7T*8> literal 0 HcmV?d00001 diff --git a/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html b/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html new file mode 100644 index 00000000..ec1007f3 --- /dev/null +++ b/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html @@ -0,0 +1,129 @@ + + + + + This is a super article ! + + + + + + + + + +Fork me on GitHub + +

+
+
+
+

+ This is a super article !

+
+ +
+

Some content here !

+
+

This is a simple title

+

And here comes the cool stuff.

+alternate text +alternate text +
+>>> from ipdb import set_trace
+>>> set_trace()
+
+

→ And now try with some utf8 hell: ééé

+
+ +
+
+

Comments !

+
+ + +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html b/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html new file mode 100644 index 00000000..cb8ccef5 --- /dev/null +++ b/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html @@ -0,0 +1,146 @@ + + + + + Unbelievable ! + + + + + + + + + +Fork me on GitHub + + +
+
+
+

+ Unbelievable !

+
+ +
+

Or completely awesome. Depends the needs.

+

a root-relative link to markdown-article +a file-relative link to markdown-article

+
+

Testing sourcecode directive

+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+
+
+

Testing another case

+

This will now have a line number in 'custom' since it's the default in +pelican.conf, it will have nothing in default.

+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Lovely.

+
+
+

Testing more sourcecode directives

+
 8 def run(self):
self.assert_has_content()
10 try:
lexer = get_lexer_by_name(self.arguments[0])
12 except ValueError:
# no lexer found - use the text one instead of an exception
14 lexer = TextLexer()

16 if ('linenos' in self.options and
self.options['linenos'] not in ('table', 'inline')):
18 self.options['linenos'] = 'table'

20 for flag in ('nowrap', 'nobackground', 'anchorlinenos'):
if flag in self.options:
22 self.options[flag] = True

24 # noclasses should already default to False, but just in case...
formatter = HtmlFormatter(noclasses=False, **self.options)
26 parsed = highlight('\n'.join(self.content), lexer, formatter)
return [nodes.raw('', parsed, format='html')]
+

Lovely.

+
+
+

Testing even more sourcecode directives

+formatter = self.options and VARIANTS[self.options.keys()[0]] +

Lovely.

+
+
+

Testing overriding config defaults

+

Even if the default is line numbers, we can override it here

+
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Lovely.

+
+ +
+
+

Comments !

+
+ + +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html b/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html new file mode 100644 index 00000000..c42bce5d --- /dev/null +++ b/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html @@ -0,0 +1,121 @@ + + + + + Oh yeah ! + + + + + + + + + +Fork me on GitHub + + +
+
+
+

+ Oh yeah !

+
+ +
+
+

Why not ?

+

After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !

+alternate text +
+ +
+
+

Comments !

+
+ + +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html b/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html new file mode 100644 index 00000000..49cc6078 --- /dev/null +++ b/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html @@ -0,0 +1,115 @@ + + + + + A markdown powered article + + + + + + + + + +Fork me on GitHub + + +
+
+
+

+ A markdown powered article

+
+ +
+

You're mutually oblivious.

+

a root-relative link to unbelievable +a file-relative link to unbelievable

+
+
+

Comments !

+
+ + +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html b/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html new file mode 100644 index 00000000..8029e585 --- /dev/null +++ b/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html @@ -0,0 +1,114 @@ + + + + + Article 1 + + + + + + + + + +Fork me on GitHub + + +
+
+
+

+ Article 1

+
+ +
+

Article 1

+ +
+
+

Comments !

+
+ + +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html b/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html new file mode 100644 index 00000000..ca6aaaf3 --- /dev/null +++ b/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html @@ -0,0 +1,114 @@ + + + + + Article 2 + + + + + + + + + +Fork me on GitHub + + +
+
+
+

+ Article 2

+
+ +
+

Article 2

+ +
+
+

Comments !

+
+ + +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html b/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html new file mode 100644 index 00000000..4f255f4f --- /dev/null +++ b/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html @@ -0,0 +1,114 @@ + + + + + Article 3 + + + + + + + + + +Fork me on GitHub + + +
+
+
+

+ Article 3

+
+ +
+

Article 3

+ +
+
+

Comments !

+
+ + +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html b/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html new file mode 100644 index 00000000..46b07717 --- /dev/null +++ b/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html @@ -0,0 +1,116 @@ + + + + + Second article + + + + + + + + + +Fork me on GitHub + + +
+
+
+

+ Second article

+
+ +
+

This is some article, in english

+ +
+
+

Comments !

+
+ + +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html b/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html new file mode 100644 index 00000000..0d021cde --- /dev/null +++ b/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html @@ -0,0 +1,114 @@ + + + + + FILENAME_METADATA example + + + + + + + + + +Fork me on GitHub + + +
+
+
+

+ FILENAME_METADATA example

+
+ +
+

Some cool stuff!

+ +
+
+

Comments !

+
+ + +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/robots.txt b/pelican/tests/output/custom_locale/robots.txt new file mode 100644 index 00000000..19a6e299 --- /dev/null +++ b/pelican/tests/output/custom_locale/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: /pictures diff --git a/pelican/tests/output/custom_locale/second-article-fr.html b/pelican/tests/output/custom_locale/second-article-fr.html new file mode 100644 index 00000000..2798c94b --- /dev/null +++ b/pelican/tests/output/custom_locale/second-article-fr.html @@ -0,0 +1,116 @@ + + + + + Deuxième article + + + + + + + + + +Fork me on GitHub + + +
+
+
+

+ Deuxième article

+
+ +
+

Ceci est un article, en français.

+ +
+
+

Comments !

+
+ + +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/tag/bar.html b/pelican/tests/output/custom_locale/tag/bar.html new file mode 100644 index 00000000..8a65a834 --- /dev/null +++ b/pelican/tests/output/custom_locale/tag/bar.html @@ -0,0 +1,160 @@ + + + + + Alexis' log - bar + + + + + + + + + +Fork me on GitHub + + + + +
+

Other articles

+
+
    + +
  1. + +
  2. +
    +

    Oh yeah !

    +
    + +
    +
    +

    Why not ?

    +

    After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! +YEAH !

    +alternate text +
    + + read more +

    There are comments.

    +
  3. +
+

+ Page 1 / 1 +

+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/tag/baz.html b/pelican/tests/output/custom_locale/tag/baz.html new file mode 100644 index 00000000..52467abb --- /dev/null +++ b/pelican/tests/output/custom_locale/tag/baz.html @@ -0,0 +1,114 @@ + + + + + The baz tag + + + + + + + + + +Fork me on GitHub + + +
+
+
+

+ The baz tag

+
+ +
+

This article overrides the listening of the articles under the baz tag.

+ +
+
+

Comments !

+
+ + +
+ +
+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/tag/foo.html b/pelican/tests/output/custom_locale/tag/foo.html new file mode 100644 index 00000000..87cb4ec5 --- /dev/null +++ b/pelican/tests/output/custom_locale/tag/foo.html @@ -0,0 +1,130 @@ + + + + + Alexis' log - foo + + + + + + + + + +Fork me on GitHub + + + + +
+

Other articles

+
+
    + +
  1. +
+

+ Page 1 / 1 +

+
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/tag/foobar.html b/pelican/tests/output/custom_locale/tag/foobar.html new file mode 100644 index 00000000..0e5414b0 --- /dev/null +++ b/pelican/tests/output/custom_locale/tag/foobar.html @@ -0,0 +1,109 @@ + + + + + Alexis' log - foobar + + + + + + + + + +Fork me on GitHub + + + + +
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/tag/oh.html b/pelican/tests/output/custom_locale/tag/oh.html new file mode 100644 index 00000000..21c8e352 --- /dev/null +++ b/pelican/tests/output/custom_locale/tag/oh.html @@ -0,0 +1,80 @@ + + + + + Oh Oh Oh + + + + + + + + + +Fork me on GitHub + + +
+

Oh Oh Oh

+ +

This page overrides the listening of the articles under the oh tag.

+ +
+
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/tag/yeah.html b/pelican/tests/output/custom_locale/tag/yeah.html new file mode 100644 index 00000000..a8fe6f51 --- /dev/null +++ b/pelican/tests/output/custom_locale/tag/yeah.html @@ -0,0 +1,101 @@ + + + + + Alexis' log - yeah + + + + + + + + + +Fork me on GitHub + + + + +
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/tags.html b/pelican/tests/output/custom_locale/tags.html new file mode 100644 index 00000000..0da0d291 --- /dev/null +++ b/pelican/tests/output/custom_locale/tags.html @@ -0,0 +1,87 @@ + + + + + Alexis' log - Tags + + + + + + + + + +Fork me on GitHub + + + +
+

Tags for Alexis' log

+ +
+ +
+
+

blogroll

+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/theme/css/main.css b/pelican/tests/output/custom_locale/theme/css/main.css new file mode 100644 index 00000000..2efb518d --- /dev/null +++ b/pelican/tests/output/custom_locale/theme/css/main.css @@ -0,0 +1,451 @@ +/* + Name: Smashing HTML5 + Date: July 2009 + Description: Sample layout for HTML5 and CSS3 goodness. + Version: 1.0 + License: MIT + Licensed by: Smashing Media GmbH + Original author: Enrique Ramírez +*/ + +/* Imports */ +@import url("reset.css"); +@import url("pygment.css"); +@import url("typogrify.css"); +@import url(//fonts.googleapis.com/css?family=Yanone+Kaffeesatz&subset=latin); + +/***** Global *****/ +/* Body */ +body { + background: #F5F4EF; + color: #000305; + font-size: 87.5%; /* Base font size: 14px */ + font-family: 'Trebuchet MS', Trebuchet, 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; + line-height: 1.429; + margin: 0; + padding: 0; + text-align: left; +} + +/* Headings */ +h1 {font-size: 2em } +h2 {font-size: 1.571em} /* 22px */ +h3 {font-size: 1.429em} /* 20px */ +h4 {font-size: 1.286em} /* 18px */ +h5 {font-size: 1.143em} /* 16px */ +h6 {font-size: 1em} /* 14px */ + +h1, h2, h3, h4, h5, h6 { + font-weight: 400; + line-height: 1.1; + margin-bottom: .8em; + font-family: 'Yanone Kaffeesatz', arial, serif; +} + +h3, h4, h5, h6 { margin-top: .8em; } + +hr { border: 2px solid #EEEEEE; } + +/* Anchors */ +a {outline: 0;} +a img {border: 0px; text-decoration: none;} +a:link, a:visited { + color: #C74350; + padding: 0 1px; + text-decoration: underline; +} +a:hover, a:active { + background-color: #C74350; + color: #fff; + text-decoration: none; + text-shadow: 1px 1px 1px #333; +} + +h1 a:hover { + background-color: inherit +} + +/* Paragraphs */ +div.line-block, +p { margin-top: 1em; + margin-bottom: 1em;} + +strong, b {font-weight: bold;} +em, i {font-style: italic;} + +/* Lists */ +ul { + list-style: outside disc; + margin: 0em 0 0 1.5em; +} + +ol { + list-style: outside decimal; + margin: 0em 0 0 1.5em; +} + +li { margin-top: 0.5em;} + +.post-info { + float:right; + margin:10px; + padding:5px; +} + +.post-info p{ + margin-top: 1px; + margin-bottom: 1px; +} + +.readmore { float: right } + +dl {margin: 0 0 1.5em 0;} +dt {font-weight: bold;} +dd {margin-left: 1.5em;} + +pre{background-color: rgb(238, 238, 238); padding: 10px; margin: 10px; overflow: auto;} + +/* Quotes */ +blockquote { + margin: 20px; + font-style: italic; +} +cite {} + +q {} + +div.note { + float: right; + margin: 5px; + font-size: 85%; + max-width: 300px; +} + +/* Tables */ +table {margin: .5em auto 1.5em auto; width: 98%;} + + /* Thead */ + thead th {padding: .5em .4em; text-align: left;} + thead td {} + + /* Tbody */ + tbody td {padding: .5em .4em;} + tbody th {} + + tbody .alt td {} + tbody .alt th {} + + /* Tfoot */ + tfoot th {} + tfoot td {} + +/* HTML5 tags */ +header, section, footer, +aside, nav, article, figure { + display: block; +} + +/***** Layout *****/ +.body {clear: both; margin: 0 auto; width: 800px;} +img.right, figure.right {float: right; margin: 0 0 2em 2em;} +img.left, figure.left {float: left; margin: 0 2em 2em 0;} + +/* + Header +*****************/ +#banner { + margin: 0 auto; + padding: 2.5em 0 0 0; +} + + /* Banner */ + #banner h1 {font-size: 3.571em; line-height: 0;} + #banner h1 a:link, #banner h1 a:visited { + color: #000305; + display: block; + font-weight: bold; + margin: 0 0 .6em .2em; + text-decoration: none; + } + #banner h1 a:hover, #banner h1 a:active { + background: none; + color: #C74350; + text-shadow: none; + } + + #banner h1 strong {font-size: 0.36em; font-weight: normal;} + + /* Main Nav */ + #banner nav { + background: #000305; + font-size: 1.143em; + height: 40px; + line-height: 30px; + margin: 0 auto 2em auto; + padding: 0; + text-align: center; + width: 800px; + + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + } + + #banner nav ul {list-style: none; margin: 0 auto; width: 800px;} + #banner nav li {float: left; display: inline; margin: 0;} + + #banner nav a:link, #banner nav a:visited { + color: #fff; + display: inline-block; + height: 30px; + padding: 5px 1.5em; + text-decoration: none; + } + #banner nav a:hover, #banner nav a:active, + #banner nav .active a:link, #banner nav .active a:visited { + background: #C74451; + color: #fff; + text-shadow: none !important; + } + + #banner nav li:first-child a { + border-top-left-radius: 5px; + -moz-border-radius-topleft: 5px; + -webkit-border-top-left-radius: 5px; + + border-bottom-left-radius: 5px; + -moz-border-radius-bottomleft: 5px; + -webkit-border-bottom-left-radius: 5px; + } + +/* + Featured +*****************/ +#featured { + background: #fff; + margin-bottom: 2em; + overflow: hidden; + padding: 20px; + width: 760px; + + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; +} + +#featured figure { + border: 2px solid #eee; + float: right; + margin: 0.786em 2em 0 5em; + width: 248px; +} +#featured figure img {display: block; float: right;} + +#featured h2 {color: #C74451; font-size: 1.714em; margin-bottom: 0.333em;} +#featured h3 {font-size: 1.429em; margin-bottom: .5em;} + +#featured h3 a:link, #featured h3 a:visited {color: #000305; text-decoration: none;} +#featured h3 a:hover, #featured h3 a:active {color: #fff;} + +/* + Body +*****************/ +#content { + background: #fff; + margin-bottom: 2em; + overflow: hidden; + padding: 20px 20px; + width: 760px; + + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; +} + +/* + Extras +*****************/ +#extras {margin: 0 auto 3em auto; overflow: hidden;} + +#extras ul {list-style: none; margin: 0;} +#extras li {border-bottom: 1px solid #fff;} +#extras h2 { + color: #C74350; + font-size: 1.429em; + margin-bottom: .25em; + padding: 0 3px; +} + +#extras a:link, #extras a:visited { + color: #444; + display: block; + border-bottom: 1px solid #F4E3E3; + text-decoration: none; + padding: .3em .25em; +} + +#extras a:hover, #extras a:active {color: #fff;} + + /* Blogroll */ + #extras .blogroll { + float: left; + width: 615px; + } + + #extras .blogroll li {float: left; margin: 0 20px 0 0; width: 185px;} + + /* Social */ + #extras .social { + float: right; + width: 175px; + } + + #extras div[class='social'] a { + background-repeat: no-repeat; + background-position: 3px 6px; + padding-left: 25px; + } + + /* Icons */ + .social a[href*='about.me'] {background-image: url('../images/icons/aboutme.png');} + .social a[href*='bitbucket.org'] {background-image: url('../images/icons/bitbucket.png');} + .social a[href*='delicious.com'] {background-image: url('../images/icons/delicious.png');} + .social a[href*='digg.com'] {background-image: url('../images/icons/digg.png');} + .social a[href*='facebook.com'] {background-image: url('../images/icons/facebook.png');} + .social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.png');} + .social a[href*='github.com'], + .social a[href*='git.io'] { + background-image: url('../images/icons/github.png'); + background-size: 16px 16px; + } + .social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');} + .social a[href*='plus.google.com'] {background-image: url('../images/icons/google-plus.png');} + .social a[href*='groups.google.com'] {background-image: url('../images/icons/google-groups.png');} + .social a[href*='news.ycombinator.com'], + .social a[href*='hackernewsers.com'] {background-image: url('../images/icons/hackernews.png');} + .social a[href*='last.fm'], .social a[href*='lastfm.'] {background-image: url('../images/icons/lastfm.png');} + .social a[href*='linkedin.com'] {background-image: url('../images/icons/linkedin.png');} + .social a[href*='reddit.com'] {background-image: url('../images/icons/reddit.png');} + .social a[type$='atom+xml'], .social a[type$='rss+xml'] {background-image: url('../images/icons/rss.png');} + .social a[href*='slideshare.net'] {background-image: url('../images/icons/slideshare.png');} + .social a[href*='speakerdeck.com'] {background-image: url('../images/icons/speakerdeck.png');} + .social a[href*='stackoverflow.com'] {background-image: url('../images/icons/stackoverflow.png');} + .social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');} + .social a[href*='vimeo.com'] {background-image: url('../images/icons/vimeo.png');} + .social a[href*='youtube.com'] {background-image: url('../images/icons/youtube.png');} + +/* + About +*****************/ +#about { + background: #fff; + font-style: normal; + margin-bottom: 2em; + overflow: hidden; + padding: 20px; + text-align: left; + width: 760px; + + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; +} + +#about .primary {float: left; width: 165px;} +#about .primary strong {color: #C64350; display: block; font-size: 1.286em;} +#about .photo {float: left; margin: 5px 20px;} + +#about .url:link, #about .url:visited {text-decoration: none;} + +#about .bio {float: right; width: 500px;} + +/* + Footer +*****************/ +#contentinfo {padding-bottom: 2em; text-align: right;} + +/***** Sections *****/ +/* Blog */ +.hentry { + display: block; + clear: both; + border-bottom: 1px solid #eee; + padding: 1.5em 0; +} +li:last-child .hentry, #content > .hentry {border: 0; margin: 0;} +#content > .hentry {padding: 1em 0;} +.hentry img{display : none ;} +.entry-title {font-size: 3em; margin-bottom: 10px; margin-top: 0;} +.entry-title a:link, .entry-title a:visited {text-decoration: none; color: #333;} +.entry-title a:visited {background-color: #fff;} + +.hentry .post-info * {font-style: normal;} + + /* Content */ + .hentry footer {margin-bottom: 2em;} + .hentry footer address {display: inline;} + #posts-list footer address {display: block;} + + /* Blog Index */ + #posts-list {list-style: none; margin: 0;} + #posts-list .hentry {padding-left: 10px; position: relative;} + + #posts-list footer { + left: 10px; + position: relative; + float: left; + top: 0.5em; + width: 190px; + } + + /* About the Author */ + #about-author { + background: #f9f9f9; + clear: both; + font-style: normal; + margin: 2em 0; + padding: 10px 20px 15px 20px; + + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + } + + #about-author strong { + color: #C64350; + clear: both; + display: block; + font-size: 1.429em; + } + + #about-author .photo {border: 1px solid #ddd; float: left; margin: 5px 1em 0 0;} + + /* Comments */ + #comments-list {list-style: none; margin: 0 1em;} + #comments-list blockquote { + background: #f8f8f8; + clear: both; + font-style: normal; + margin: 0; + padding: 15px 20px; + + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + } + #comments-list footer {color: #888; padding: .5em 1em 0 0; text-align: right;} + + #comments-list li:nth-child(2n) blockquote {background: #F5f5f5;} + + /* Add a Comment */ + #add-comment label {clear: left; float: left; text-align: left; width: 150px;} + #add-comment input[type='text'], + #add-comment input[type='email'], + #add-comment input[type='url'] {float: left; width: 200px;} + + #add-comment textarea {float: left; height: 150px; width: 495px;} + + #add-comment p.req {clear: both; margin: 0 .5em 1em 0; text-align: right;} + + #add-comment input[type='submit'] {float: right; margin: 0 .5em;} + #add-comment * {margin-bottom: .5em;} diff --git a/pelican/tests/output/custom_locale/theme/css/pygment.css b/pelican/tests/output/custom_locale/theme/css/pygment.css new file mode 100644 index 00000000..fdd056f6 --- /dev/null +++ b/pelican/tests/output/custom_locale/theme/css/pygment.css @@ -0,0 +1,205 @@ +.hll { +background-color:#eee; +} +.c { +color:#408090; +font-style:italic; +} +.err { +border:1px solid #FF0000; +} +.k { +color:#007020; +font-weight:bold; +} +.o { +color:#666666; +} +.cm { +color:#408090; +font-style:italic; +} +.cp { +color:#007020; +} +.c1 { +color:#408090; +font-style:italic; +} +.cs { +background-color:#FFF0F0; +color:#408090; +} +.gd { +color:#A00000; +} +.ge { +font-style:italic; +} +.gr { +color:#FF0000; +} +.gh { +color:#000080; +font-weight:bold; +} +.gi { +color:#00A000; +} +.go { +color:#303030; +} +.gp { +color:#C65D09; +font-weight:bold; +} +.gs { +font-weight:bold; +} +.gu { +color:#800080; +font-weight:bold; +} +.gt { +color:#0040D0; +} +.kc { +color:#007020; +font-weight:bold; +} +.kd { +color:#007020; +font-weight:bold; +} +.kn { +color:#007020; +font-weight:bold; +} +.kp { +color:#007020; +} +.kr { +color:#007020; +font-weight:bold; +} +.kt { +color:#902000; +} +.m { +color:#208050; +} +.s { +color:#4070A0; +} +.na { +color:#4070A0; +} +.nb { +color:#007020; +} +.nc { +color:#0E84B5; +font-weight:bold; +} +.no { +color:#60ADD5; +} +.nd { +color:#555555; +font-weight:bold; +} +.ni { +color:#D55537; +font-weight:bold; +} +.ne { +color:#007020; +} +.nf { +color:#06287E; +} +.nl { +color:#002070; +font-weight:bold; +} +.nn { +color:#0E84B5; +font-weight:bold; +} +.nt { +color:#062873; +font-weight:bold; +} +.nv { +color:#BB60D5; +} +.ow { +color:#007020; +font-weight:bold; +} +.w { +color:#BBBBBB; +} +.mf { +color:#208050; +} +.mh { +color:#208050; +} +.mi { +color:#208050; +} +.mo { +color:#208050; +} +.sb { +color:#4070A0; +} +.sc { +color:#4070A0; +} +.sd { +color:#4070A0; +font-style:italic; +} +.s2 { +color:#4070A0; +} +.se { +color:#4070A0; +font-weight:bold; +} +.sh { +color:#4070A0; +} +.si { +color:#70A0D0; +font-style:italic; +} +.sx { +color:#C65D09; +} +.sr { +color:#235388; +} +.s1 { +color:#4070A0; +} +.ss { +color:#517918; +} +.bp { +color:#007020; +} +.vc { +color:#BB60D5; +} +.vg { +color:#BB60D5; +} +.vi { +color:#BB60D5; +} +.il { +color:#208050; +} diff --git a/pelican/tests/output/custom_locale/theme/css/reset.css b/pelican/tests/output/custom_locale/theme/css/reset.css new file mode 100644 index 00000000..1e217566 --- /dev/null +++ b/pelican/tests/output/custom_locale/theme/css/reset.css @@ -0,0 +1,52 @@ +/* + Name: Reset Stylesheet + Description: Resets browser's default CSS + Author: Eric Meyer + Author URI: http://meyerweb.com/eric/tools/css/reset/ +*/ + +/* v1.0 | 20080212 */ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, font, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td { + background: transparent; + border: 0; + font-size: 100%; + margin: 0; + outline: 0; + padding: 0; + vertical-align: baseline; +} + +body {line-height: 1;} + +ol, ul {list-style: none;} + +blockquote, q {quotes: none;} + +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +/* remember to define focus styles! */ +:focus { + outline: 0; +} + +/* remember to highlight inserts somehow! */ +ins {text-decoration: none;} +del {text-decoration: line-through;} + +/* tables still need 'cellspacing="0"' in the markup */ +table { + border-collapse: collapse; + border-spacing: 0; +} \ No newline at end of file diff --git a/pelican/tests/output/custom_locale/theme/css/typogrify.css b/pelican/tests/output/custom_locale/theme/css/typogrify.css new file mode 100644 index 00000000..c9b34dc8 --- /dev/null +++ b/pelican/tests/output/custom_locale/theme/css/typogrify.css @@ -0,0 +1,3 @@ +.caps {font-size:.92em;} +.amp {color:#666; font-size:1.05em;font-family:"Warnock Pro", "Goudy Old Style","Palatino","Book Antiqua",serif; font-style:italic;} +.dquo {margin-left:-.38em;} diff --git a/pelican/tests/output/custom_locale/theme/css/wide.css b/pelican/tests/output/custom_locale/theme/css/wide.css new file mode 100644 index 00000000..88fd59ce --- /dev/null +++ b/pelican/tests/output/custom_locale/theme/css/wide.css @@ -0,0 +1,48 @@ +@import url("main.css"); + +body { + font:1.3em/1.3 "Hoefler Text","Georgia",Georgia,serif,sans-serif; +} + +.post-info{ + display: none; +} + +#banner nav { + display: none; + -moz-border-radius: 0px; + margin-bottom: 20px; + overflow: hidden; + font-size: 1em; + background: #F5F4EF; +} + +#banner nav ul{ + padding-right: 50px; +} + +#banner nav li{ + float: right; + color: #000; +} + +#banner nav li a { + color: #000; +} + +#banner h1 { + margin-bottom: -18px; +} + +#featured, #extras { + padding: 50px; +} + +#featured { + padding-top: 20px; +} + +#extras { + padding-top: 0px; + padding-bottom: 0px; +} diff --git a/pelican/tests/output/custom_locale/theme/images/icons/aboutme.png b/pelican/tests/output/custom_locale/theme/images/icons/aboutme.png new file mode 100644 index 0000000000000000000000000000000000000000..9609df3bd9d766cd4b827fb0a8339b700c1abf24 GIT binary patch literal 751 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstUx|vage(c z!@6@aFM%9|WRD45bDP46hOx7_4S6Fo+k-*%fF5)Ta>O z6XFU~z-^brZ51PwI9(_Kh^7g}PZf%vA`~}SFm|F~be~{E7r$Q>mqnyt>;%CWAnF&0 z>J^CU7KrHN4{PNQZsheU=5fy8Gz;Yisssv#ckqX`@rAbXg*5X8HSz`4@%dHr`jqpy z=5RZtaF_)12GsKU*YNsP@%UEo0L8sad4MK(6mq-eal2-7IizygC9)g&bNiP6KWohG zS-|Cz#pRUFX_v@p6U%1c!{t%H>ygjnp2z8w&f}cUVI9k18Od!O&8Fwc>5|Q96~*D0 z#%UhPVH?k3;LWPz#^I34reVi!8_%X{#by@DrewmR<-%&@&nzOxqVB+|@6E{0!>ne@ zqU+A2Y{jhQ!lY=;OkH}&m?E%Ja zC$sHaw(BO9N_QK10Mpk${dC;$Iv-Sgs1 z>}GlM0tE(@$Z4WS9~SJov+DT=kEZa@(E0WcEcx0FsV`5Db6NC!V}woNM4#AY#y4|V zcGfJc2;H@B=ic4*7ff{9i&|5ruCcY7__a#1S1tN{P32SRuV!D_tlt0mk+S{xzkezF zn-7Y&EBC3-DcWUz;LL;X@g_ZzKBs5>H{b4a!=6dHduc-B)u;`Jo`r8<+{W~Vq2`0N zaE8$A3qX&kmbgZgq$HN4S|t~y0x1R~14Cn717lr7(-1=oD?>9YBLiInb1MS_`T5Fb hC>nC}Q!>*kF*O*PK{PbaT?tCT44$rjF6*2UngApI;B){0 literal 0 HcmV?d00001 diff --git a/pelican/tests/output/custom_locale/theme/images/icons/bitbucket.png b/pelican/tests/output/custom_locale/theme/images/icons/bitbucket.png new file mode 100644 index 0000000000000000000000000000000000000000..d05ba1610eab6ec3f9a4dcae689d4d88bda5433f GIT binary patch literal 3714 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 literal 0 HcmV?d00001 diff --git a/pelican/tests/output/custom_locale/theme/images/icons/gitorious.png b/pelican/tests/output/custom_locale/theme/images/icons/gitorious.png new file mode 100644 index 0000000000000000000000000000000000000000..3eeb3ecec36a73ff505e04ecdecbcc4792ef6786 GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!O@L2`D?^o)dRwqYcev@q#DMvQ z*(*AHH`kW0om8=NO8&uVRmbKn*uQ@5(H+Zf?%R0x*ww37FTJ_{>eZ{eAD{pK|9`68 zWgnn9NuDl_ArhBs&l(CfDDW^GWLD*36#n?Xe(OZZj60uZDW6tS-s!tZ;qA;Vf>oNe zoO+M!7w(Q%nqK|iN%H(B6U8~-_(gR#lieQ**`4zb(6ROF5mHl4iMd b=se=)bK`s~y3?}Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyq( z0|^AkZL;YA00C!7L_t(I%VS`Gf&_0HFrDaQqmtllJp)MJ0^(m#dOmnQe`Ts~~BcM&z2u(nCq_gq= z7&lY6AuyMK{funGm2)TmfBF3B|J(^}V8h})E&un_7XJV6?(P5kw{QH<2ysPrkqQGe zaQ=a8*|2Qhe-QZh?;n_+)zkF9r6dC`2Exbo?ff4HG!&|51_Ly-!Oq;YV!?kH04e_c z`xn?|n1LYr#DU#F11w-JxWxdCCa^$xYWRObptr6V*$cl=42W?z`+xTMA)r;58bP){ zesK4Hl#4Nj0k@C?G1=e#|F54v;Lb)4le}mj^k}^W4@6{%>dY9FfCGV^+9H&wMGiz2 zjKtz?1=auqi>CG<2OMfNq9-I62F3rB_Uiwu=1l{q2lUhkPe>?9*&EFzURJO;$Dfob d%1o4IX8>>N|HqM7x0V0^002ovPDHLkV1m@;(I5Z- literal 0 HcmV?d00001 diff --git a/pelican/tests/output/custom_locale/theme/images/icons/google-groups.png b/pelican/tests/output/custom_locale/theme/images/icons/google-groups.png new file mode 100644 index 0000000000000000000000000000000000000000..5de15e68f4d1e4176b46fe6346d42f53e3296b21 GIT binary patch literal 803 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstUx|vage(c z!@6@aFM%9|WRD45bDP46hOx7_4S6Fo+k-*%fF5)Mpyt z6XFU~@c;jRouXIDNvA)3c>3}ElMnA6zkmDa-J1t*U*CKC>h7DDcV4}?_43)x7td}y ze|qiNlPk|2Uv}&NGS4Ioi}&t+__Wd%$cHF^5*9C6Zh|& zS-fcGqD3=xieDc*uvxqCB`~XH4>TcVYa`q+>QZfU%sx2Z(n*wUnoNHS?EYjGdv*%IzuiFv_(s)h|m2&VRzopr01MEB^#A|> literal 0 HcmV?d00001 diff --git a/pelican/tests/output/custom_locale/theme/images/icons/google-plus.png b/pelican/tests/output/custom_locale/theme/images/icons/google-plus.png new file mode 100644 index 0000000000000000000000000000000000000000..3c6b74324031611f20c0a3810131aa74fd0a5a9f GIT binary patch literal 527 zcmV+q0`UEbP)ZK z>upIAVz}dFal_O6CT?`r$qOAn;FBaOK-k!D!Jidayc;Y>7GYGxnGBxue+IChM|)V`ar=`j`~3M zC07%M|NqZF^fS8TVssIRJYwIy1j>GXU1E8{UJs6dLU1-v!-3nb2Irih=+?6PKKJ@8lF5#&^v9fcghZg&b90USur#Ch_yBx0YzYJ9ftpZ|9@Mbb;3sH7#Llc zapC{3OLv2zTp&7Rr3KUgB!TRsHrfndzX7?Q-YzOS8|-?va>A!K_nuYT9<U;^giL<|Q^RJR%`Z!y$8yYa>Upa1{=xci{g2`qvgF`RFk`e^@wXB(G2 zShMW$$p`;HLHGV;SLhaf#f_+F1DIs^y0_<$oBBFk`Sl>Ab*Ovm_g^4AcawG2Lj|D- zqK4swiTo;US!A@iaP1FZz%33(6Ney%eY$dMwPn_zpvZ?`|G(*ST!StSLI9gPlb*yp RBR2p5002ovPDHLkV1g3B{|5j7 literal 0 HcmV?d00001 diff --git a/pelican/tests/output/custom_locale/theme/images/icons/hackernews.png b/pelican/tests/output/custom_locale/theme/images/icons/hackernews.png new file mode 100644 index 0000000000000000000000000000000000000000..fc7a82d4d68068d5fb032885b93e670c385ae1b4 GIT binary patch literal 3273 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 literal 0 HcmV?d00001 diff --git a/pelican/tests/output/custom_locale/theme/images/icons/lastfm.png b/pelican/tests/output/custom_locale/theme/images/icons/lastfm.png new file mode 100644 index 0000000000000000000000000000000000000000..3a6c6262b644dadbcf6cce5dfe4fed9740a9ec1f GIT binary patch literal 975 zcmV;=12FuFP)6?8>dbVG7wVRUJ4ZXi@?ZDjy5FfTVRFgbe517iRH11L#E zK~y*q1;I~j6jdC+@$Y-{cJ_af?v|Cd&=58%RRW19jgdqNQ4(W7BPZ0vgC6ith#oz2 zwmQaM~wsz^Z-QCXa%DC5DyB?4oI zHSqw7c|gHN7dAskEJO*20zV5*!N7txD5W7|ke~#<+t+u*fj>r ztH?8TXslr8?-TWV6(gOX%;6VdsD%CI2XwQA8l*5f0`DG!`c7!JVEYK^^e^` zcD=D}N`gz%A|jG3Hzk<+OHvW}@9ocI<-{ifWL)N6Op zl_6x*$4|GSoI{~dSr4lfE@RM%BU`HwdyYXEVvL7at4)4lw9&i$@PL;@=v)?SEfh1D zasgHF@wCQe1o1p1(b(1+x;KE@R@B7!CDScsKTn?9J!|Sao$80mhEYbMc$-A=FpjF~ xhgEG<4rh((w99I&de5(oe5r7A)lWa1`5&mym2=&ymqP#m002ovPDHLkV1nRSx}g97 literal 0 HcmV?d00001 diff --git a/pelican/tests/output/custom_locale/theme/images/icons/linkedin.png b/pelican/tests/output/custom_locale/theme/images/icons/linkedin.png new file mode 100644 index 0000000000000000000000000000000000000000..d29c1201bcb0c278d49f573f9ef95ebfe932fb5b GIT binary patch literal 896 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 KT-G@yGywnx3UT@X literal 0 HcmV?d00001 diff --git a/pelican/tests/output/custom_locale/theme/images/icons/stackoverflow.png b/pelican/tests/output/custom_locale/theme/images/icons/stackoverflow.png new file mode 100644 index 0000000000000000000000000000000000000000..f5b65e9990dbf423ff652b297f1d0172c8c1cf27 GIT binary patch literal 916 zcmeAS@N?(olHy`uVBq!ia0vp^0w65F1|7sn8f<3~fRJ+vJ~_8MEyP6_>_pwTJhc%)&)Mu9Ed(YG|jmTlZ9 z@KIPeZ^FK^-?9GB|W>{Q7m)EC+@8;a3mYxk}1?tCE&x6kxKjd-nU)D+_Cb1MZtP zf6ioHdF7Ur;v^T%WC`(qYnHY?KFysk()nSxK=;)vJ-w30K5t&s)3ykMV9;LODnQ81ohPJtCw>uw6%3rniLY9y-{G%>&K7poH)Ybf9hcZ zPx7%K&0{68BG<3+JpQ`j393DPNr7J)G`t`kgi;Fv5%&IGQ?s!oY8v6HF>egkKm6gM^6?fU09+hB7{`$2g z!Dr>|-0l5+On2^2_f4p`m-+THD#}N=VM2Fz(!8K!0$xisQhfv_dG-h{GMTvHCdbLN z^XFvvX21P9HE8+fMNXk>uPs~lGBfn^9g#*-%#3?x$4 ze30#20|W~;m8*zxxBvb3Dl3rX#Piv+IUiP)6?8>dbVG7wVRUJ4ZXi@?ZDjy5FfTVRFgbe517iRH0+&fd zK~y*q1;I;eT~!#z;orN~+WXwn#6*y$w(*h*ilq*eIv-e)>{rfz2>o|YjIakM#%UhMb+@bNp@nA+}SPdDo7O9oA zGH1DQ=3VCA#gT5=SO4a&tyI0gXBT8cdC;FG`L+ZpV~z_2(k zy27+~*2T$!9(dBjs?l*Gs@^i^ycrPqX$n7{!nGav4CBKp&|ow~joLoVsusArMnnq)x&OT0jOD0^FU!r)xMV z@T`Lg@WUazJu+?=Xo^@Bc7w?~m$5A3q=>T&2}l_pF5!6#!NjS=iGu;#28{!#P2-AU z0h%HbP#g*{cet?yAML`kY`i-ZA56r3H`W4*h|+12)Ju^?5tk}wyNb*^F%HHT7vW$4 z0AT#-;$9CI5)714(INy>lS^h8jI9{BG64bGL3}rZ+e`R_3xqi;MG7(3u z(Kz(IvnufS+IiH% z|H$#?(3yDKFI$%htO#ZKU{k0-ZZa!IYoMLFnl`;oUNO7#KQyI_l_SgjkN^Mx07*qo IM6N<$g7S@P4*&oF literal 0 HcmV?d00001 diff --git a/pelican/tests/output/custom_locale/theme/images/icons/vimeo.png b/pelican/tests/output/custom_locale/theme/images/icons/vimeo.png new file mode 100644 index 0000000000000000000000000000000000000000..dba472022f0fcf7ecdd8f4847a8a3bde90789bc7 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstUx|vage(c z!@6@aFM%9|WRD45bDP46hOx7_4S6Fo+k-*%fF5l;{oc z32_C|+_Uys@43gj_L}zg+illgm0x@zv+3s4+i(B>|Nr6VAFo3XmfU;0&*Mqkod+xp6y#3gPyN@QHxXzy0-n4wn!XxLor|jgco@-h?@!*{&Jj>5-y7s_z z@+$6G2Ohk5z2Vroh5JukeEmths6S=tPM4Z~$^H#Vee))+-^JCwnxk~8b9t9&bn*5L zvT{HNF_r}R1v5B2yO9RsBze2LaDKeG^bL^H>FMGaB5^si|EAa>1s)gXg^zYT($l&Y zn|<~Fe;e+k=x2)RJNb=;l_qw0c;tC<3C(%IzGPL~DJ!ccy{fL?3~Y=q61VnM+{k& zPwWILICuW)Iw1Sxx96+>e|Uf6_up&l^DbVzD4uS~541_8B*-rqWG?~05TV1%3sir^ z)5S5Q;#N{XT0%k!1BU`zox({D0S6wQ93LMAIc5h2!;S_HIkiHz2^JA8f(i{15>tM# zJZaz(V9+^u@ZiYinline' ' markup and stuff to "typogrify' '"...

\n', - 'date': datetime.datetime(2010, 12, 2, 10, 14), - 'modified': datetime.datetime(2010, 12, 2, 10, 20), + 'date': SafeDatetime(2010, 12, 2, 10, 14), + 'modified': SafeDatetime(2010, 12, 2, 10, 20), 'tags': ['foo', 'bar', 'foobar'], 'custom_field': 'http://notmyidea.org', } @@ -70,7 +70,7 @@ class RstReaderTest(ReaderTest): 'category': 'yeah', 'author': 'Alexis Métaireau', 'title': 'Rst with filename metadata', - 'date': datetime.datetime(2012, 11, 29), + 'date': SafeDatetime(2012, 11, 29), } for key, value in page.metadata.items(): self.assertEqual(value, expected[key], key) @@ -85,7 +85,7 @@ class RstReaderTest(ReaderTest): 'category': 'yeah', 'author': 'Alexis Métaireau', 'title': 'Rst with filename metadata', - 'date': datetime.datetime(2012, 11, 29), + 'date': SafeDatetime(2012, 11, 29), 'slug': 'article_with_filename_metadata', 'mymeta': 'foo', } @@ -171,8 +171,8 @@ class MdReaderTest(ReaderTest): 'category': 'test', 'title': 'Test md File', 'summary': '

I have a lot to test

', - 'date': datetime.datetime(2010, 12, 2, 10, 14), - 'modified': datetime.datetime(2010, 12, 2, 10, 20), + 'date': SafeDatetime(2010, 12, 2, 10, 14), + 'modified': SafeDatetime(2010, 12, 2, 10, 20), 'tags': ['foo', 'bar', 'foobar'], } for key, value in metadata.items(): @@ -184,8 +184,8 @@ class MdReaderTest(ReaderTest): 'title': 'マックOS X 10.8でパイソンとVirtualenvをインストールと設定', 'summary': '

パイソンとVirtualenvをまっくでインストールする方法について明確に説明します。

', 'category': '指導書', - 'date': datetime.datetime(2012, 12, 20), - 'modified': datetime.datetime(2012, 12, 22), + 'date': SafeDatetime(2012, 12, 20), + 'modified': SafeDatetime(2012, 12, 22), 'tags': ['パイソン', 'マック'], 'slug': 'python-virtualenv-on-mac-osx-mountain-lion-10.8', } @@ -220,8 +220,8 @@ class MdReaderTest(ReaderTest): 'summary': ( '

Summary with inline markup ' 'should be supported.

'), - 'date': datetime.datetime(2012, 10, 31), - 'modified': datetime.datetime(2012, 11, 1), + 'date': SafeDatetime(2012, 10, 31), + 'modified': SafeDatetime(2012, 11, 1), 'slug': 'article-with-markdown-containing-footnotes', 'multiline': [ 'Line Metadata should be handle properly.', @@ -311,7 +311,7 @@ class MdReaderTest(ReaderTest): expected = { 'category': 'yeah', 'author': 'Alexis Métaireau', - 'date': datetime.datetime(2012, 11, 30), + 'date': SafeDatetime(2012, 11, 30), } for key, value in expected.items(): self.assertEqual(value, page.metadata[key], key) @@ -325,7 +325,7 @@ class MdReaderTest(ReaderTest): expected = { 'category': 'yeah', 'author': 'Alexis Métaireau', - 'date': datetime.datetime(2012, 11, 30), + 'date': SafeDatetime(2012, 11, 30), 'slug': 'md_w_filename_meta', 'mymeta': 'foo', } @@ -358,7 +358,7 @@ class HTMLReaderTest(ReaderTest): 'author': 'Alexis Métaireau', 'title': 'This is a super article !', 'summary': 'Summary and stuff', - 'date': datetime.datetime(2010, 12, 2, 10, 14), + 'date': SafeDatetime(2010, 12, 2, 10, 14), 'tags': ['foo', 'bar', 'foobar'], 'custom_field': 'http://notmyidea.org', } @@ -382,7 +382,7 @@ class HTMLReaderTest(ReaderTest): 'author': 'Alexis Métaireau', 'title': 'This is a super article !', 'summary': 'Summary and stuff', - 'date': datetime.datetime(2010, 12, 2, 10, 14), + 'date': SafeDatetime(2010, 12, 2, 10, 14), 'tags': ['foo', 'bar', 'foobar'], 'custom_field': 'http://notmyidea.org', } diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 3c12a15b..df1918b5 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals, print_function, absolute_import import logging import shutil import os -import datetime import time import locale from sys import platform, version_info @@ -38,24 +37,24 @@ class TestUtils(LoggedTestCase): def test_get_date(self): # valid ones - date = datetime.datetime(year=2012, month=11, day=22) - date_hour = datetime.datetime( + date = utils.SafeDatetime(year=2012, month=11, day=22) + date_hour = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11) - date_hour_z = datetime.datetime( + date_hour_z = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11, tzinfo=pytz.timezone('UTC')) - date_hour_est = datetime.datetime( + date_hour_est = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11, tzinfo=pytz.timezone('EST')) - date_hour_sec = datetime.datetime( + date_hour_sec = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11, second=10) - date_hour_sec_z = datetime.datetime( + date_hour_sec_z = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11, second=10, tzinfo=pytz.timezone('UTC')) - date_hour_sec_est = datetime.datetime( + date_hour_sec_est = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11, second=10, tzinfo=pytz.timezone('EST')) - date_hour_sec_frac_z = datetime.datetime( + date_hour_sec_frac_z = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11, second=10, microsecond=123000, tzinfo=pytz.timezone('UTC')) dates = { @@ -76,14 +75,14 @@ class TestUtils(LoggedTestCase): } # examples from http://www.w3.org/TR/NOTE-datetime - iso_8601_date = datetime.datetime(year=1997, month=7, day=16) - iso_8601_date_hour_tz = datetime.datetime( + iso_8601_date = utils.SafeDatetime(year=1997, month=7, day=16) + iso_8601_date_hour_tz = utils.SafeDatetime( year=1997, month=7, day=16, hour=19, minute=20, tzinfo=pytz.timezone('CET')) - iso_8601_date_hour_sec_tz = datetime.datetime( + iso_8601_date_hour_sec_tz = utils.SafeDatetime( year=1997, month=7, day=16, hour=19, minute=20, second=30, tzinfo=pytz.timezone('CET')) - iso_8601_date_hour_sec_ms_tz = datetime.datetime( + iso_8601_date_hour_sec_ms_tz = utils.SafeDatetime( year=1997, month=7, day=16, hour=19, minute=20, second=30, microsecond=450000, tzinfo=pytz.timezone('CET')) iso_8601 = { @@ -258,7 +257,7 @@ class TestUtils(LoggedTestCase): self.assertFalse(os.path.exists(test_directory)) def test_strftime(self): - d = datetime.date(2012, 8, 29) + d = utils.SafeDatetime(2012, 8, 29) # simple formatting self.assertEqual(utils.strftime(d, '%d/%m/%y'), '29/08/12') @@ -296,7 +295,7 @@ class TestUtils(LoggedTestCase): else: locale.setlocale(locale.LC_TIME, str('tr_TR.UTF-8')) - d = datetime.date(2012, 8, 29) + d = utils.SafeDatetime(2012, 8, 29) # simple self.assertEqual(utils.strftime(d, '%d %B %Y'), '29 Ağustos 2012') @@ -329,7 +328,7 @@ class TestUtils(LoggedTestCase): else: locale.setlocale(locale.LC_TIME, str('fr_FR.UTF-8')) - d = datetime.date(2012, 8, 29) + d = utils.SafeDatetime(2012, 8, 29) # simple self.assertEqual(utils.strftime(d, '%d %B %Y'), '29 août 2012') @@ -448,7 +447,7 @@ class TestDateFormatter(unittest.TestCase): os.makedirs(template_dir) with open(template_path, 'w') as template_file: template_file.write('date = {{ date|strftime("%A, %d %B %Y") }}') - self.date = datetime.date(2012, 8, 29) + self.date = utils.SafeDatetime(2012, 8, 29) def tearDown(self): @@ -464,7 +463,7 @@ class TestDateFormatter(unittest.TestCase): def test_french_strftime(self): # This test tries to reproduce an issue that occured with python3.3 under macos10 only locale.setlocale(locale.LC_ALL, str('fr_FR.UTF-8')) - date = datetime.datetime(2014,8,14) + date = utils.SafeDatetime(2014,8,14) # we compare the lower() dates since macos10 returns "Jeudi" for %A 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() diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 7c8662c9..b6078201 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -21,7 +21,7 @@ from six.moves.urllib.error import URLError from six.moves.urllib.parse import urlparse from six.moves.urllib.request import urlretrieve -from pelican.utils import slugify +from pelican.utils import slugify, SafeDatetime from pelican.log import init logger = logging.getLogger(__name__) @@ -303,7 +303,7 @@ def dc2fields(file): def posterous2fields(api_token, email, password): """Imports posterous posts""" import base64 - from datetime import datetime, timedelta + from datetime import timedelta try: # py3k import import json @@ -340,7 +340,7 @@ def posterous2fields(api_token, email, password): slug = slugify(post.get('title')) tags = [tag.get('name') for tag in post.get('tags')] raw_date = post.get('display_date') - date_object = datetime.strptime(raw_date[:-6], "%Y/%m/%d %H:%M:%S") + date_object = SafeDatetime.strptime(raw_date[:-6], "%Y/%m/%d %H:%M:%S") offset = int(raw_date[-5:]) delta = timedelta(hours = offset / 100) date_object -= delta diff --git a/pelican/utils.py b/pelican/utils.py index 84b3a41e..586d85ff 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -14,6 +14,7 @@ import shutil import traceback import pickle import hashlib +import datetime from collections import Hashable from contextlib import contextmanager @@ -56,7 +57,10 @@ def strftime(date, date_format): for candidate in candidates: # test for valid C89 directives only if candidate[1] in 'aAbBcdfHIjmMpSUwWxXyYzZ%': - formatted = date.strftime(candidate) + if isinstance(date, SafeDatetime): + formatted = date.strftime(candidate, safe=False) + else: + formatted = date.strftime(candidate) # convert Py2 result to unicode if not six.PY3 and enc is not None: formatted = formatted.decode(enc) @@ -68,6 +72,17 @@ def strftime(date, date_format): return template % tuple(formatted_candidates) +class SafeDatetime(datetime.datetime): + '''Subclass of datetime that works with utf-8 format strings on PY2''' + + def strftime(self, fmt, safe=True): + '''Uses our custom strftime if supposed to be *safe*''' + if safe: + return strftime(self, fmt) + else: + return super(SafeDatetime, self).strftime(fmt) + + class DateFormatter(object): '''A date formatter object used as a jinja filter @@ -183,8 +198,10 @@ def get_date(string): If no format matches the given date, raise a ValueError. """ string = re.sub(' +', ' ', string) + default = SafeDatetime.now().replace(hour=0, minute=0, + second=0, microsecond=0) try: - return dateutil.parser.parse(string) + return dateutil.parser.parse(string, default=default) except (TypeError, ValueError): raise ValueError('{0!r} is not a valid date'.format(string)) diff --git a/pelican/writers.py b/pelican/writers.py index 3e01ee6c..61acdadd 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -151,12 +151,7 @@ class Writer(object): def _write_file(template, localcontext, output_path, name, override): """Render the template write the file.""" - old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, str('C')) - try: - output = template.render(localcontext) - finally: - locale.setlocale(locale.LC_ALL, old_locale) + output = template.render(localcontext) path = os.path.join(output_path, name) try: os.makedirs(os.path.dirname(path)) diff --git a/samples/pelican_FR.conf.py b/samples/pelican_FR.conf.py new file mode 100644 index 00000000..1f6aaaa1 --- /dev/null +++ b/samples/pelican_FR.conf.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +AUTHOR = 'Alexis Métaireau' +SITENAME = "Alexis' log" +SITEURL = 'http://blog.notmyidea.org' +TIMEZONE = "Europe/Paris" + +# can be useful in development, but set to False when you're ready to publish +RELATIVE_URLS = True + +GITHUB_URL = 'http://github.com/ametaireau/' +DISQUS_SITENAME = "blog-notmyidea" +PDF_GENERATOR = False +REVERSE_CATEGORY_ORDER = True +LOCALE = "fr_FR.UTF-8" +DEFAULT_PAGINATION = 4 +DEFAULT_DATE = (2012, 3, 2, 14, 1, 1) + +ARTICLE_URL = 'posts/{date:%Y}/{date:%B}/{date:%d}/{slug}/' +ARTICLE_SAVE_AS = ARTICLE_URL + 'index.html' + +FEED_ALL_RSS = 'feeds/all.rss.xml' +CATEGORY_FEED_RSS = 'feeds/%s.rss.xml' + +LINKS = (('Biologeek', 'http://biologeek.org'), + ('Filyb', "http://filyb.info/"), + ('Libert-fr', "http://www.libert-fr.com"), + ('N1k0', "http://prendreuncafe.com/blog/"), + ('Tarek Ziadé', "http://ziade.org/blog"), + ('Zubin Mithra', "http://zubin71.wordpress.com/"),) + +SOCIAL = (('twitter', 'http://twitter.com/ametaireau'), + ('lastfm', 'http://lastfm.com/user/akounet'), + ('github', 'http://github.com/ametaireau'),) + +# global metadata to all the contents +DEFAULT_METADATA = (('yeah', 'it is'),) + +# path-specific metadata +EXTRA_PATH_METADATA = { + 'extra/robots.txt': {'path': 'robots.txt'}, + } + +# static paths will be copied without parsing their contents +STATIC_PATHS = [ + 'pictures', + 'extra/robots.txt', + ] + +# custom page generated with a jinja2 template +TEMPLATE_PAGES = {'pages/jinja2_template.html': 'jinja2_template.html'} + +# code blocks with line numbers +PYGMENTS_RST_OPTIONS = {'linenos': 'table'} + +# foobar will not be used, because it's not in caps. All configuration keys +# have to be in caps +foobar = "barbaz" From 009543b7e476d3bcaca3dd8576a47631c06de1e7 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Thu, 26 Jun 2014 00:51:45 -0400 Subject: [PATCH 0022/1173] Fix test errors on OSX On OSX, if LC_TIME and LC_CTYPE differs the output of strftime is not properly decoded in Python 3. This makes sure that the 'utils.DateFormatter' and the related Jinja filter 'strftime' set the same value for LC_TIME and LC_CTYPE while formatting. Also, '%a' is removed from DEFAULT_DATE_FORMAT in 'custom_locale' tests. OSX and *nix have different conversions for '%a' ('Jeu' vs 'jeu.') and there is not a feasible way to handle the difference for tests. --- docs/contribute.rst | 2 +- .../tests/output/custom_locale/archives.html | 20 +++++++++---------- .../author/alexis-metaireau.html | 8 ++++---- .../author/alexis-metaireau2.html | 10 +++++----- .../author/alexis-metaireau3.html | 4 ++-- .../output/custom_locale/category/bar.html | 2 +- .../output/custom_locale/category/cat1.html | 8 ++++---- .../output/custom_locale/category/misc.html | 8 ++++---- .../output/custom_locale/category/yeah.html | 4 ++-- .../custom_locale/drafts/a-draft-article.html | 2 +- pelican/tests/output/custom_locale/index.html | 8 ++++---- .../tests/output/custom_locale/index2.html | 10 +++++----- .../tests/output/custom_locale/index3.html | 4 ++-- .../output/custom_locale/oh-yeah-fr.html | 2 +- .../02/this-is-a-super-article/index.html | 4 ++-- .../2010/octobre/15/unbelievable/index.html | 2 +- .../posts/2010/octobre/20/oh-yeah/index.html | 2 +- .../20/a-markdown-powered-article/index.html | 2 +- .../2011/février/17/article-1/index.html | 2 +- .../2011/février/17/article-2/index.html | 2 +- .../2011/février/17/article-3/index.html | 2 +- .../2012/février/29/second-article/index.html | 2 +- .../30/filename_metadata-example/index.html | 2 +- .../custom_locale/second-article-fr.html | 2 +- .../tests/output/custom_locale/tag/bar.html | 8 ++++---- .../tests/output/custom_locale/tag/baz.html | 2 +- .../tests/output/custom_locale/tag/foo.html | 6 +++--- .../output/custom_locale/tag/foobar.html | 4 ++-- .../tests/output/custom_locale/tag/yeah.html | 2 +- pelican/tests/test_pelican.py | 2 +- pelican/utils.py | 10 ++++++++-- ...{pelican_FR.conf.py => pelican.conf_FR.py} | 1 + 32 files changed, 78 insertions(+), 71 deletions(-) rename samples/{pelican_FR.conf.py => pelican.conf_FR.py} (98%) diff --git a/docs/contribute.rst b/docs/contribute.rst index 044ef924..19604da0 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -91,7 +91,7 @@ functional tests. To do so, you can use the following two commands:: $ LC_ALL=en_US.utf8 pelican -o pelican/tests/output/custom/ \ -s samples/pelican.conf.py samples/content/ $ LC_ALL=fr_FR.utf8 pelican -o pelican/tests/output/custom_locale/ \ - -s samples/pelican_FR.conf.py samples/content/ + -s samples/pelican.conf_FR.py samples/content/ $ LC_ALL=en_US.utf8 pelican -o pelican/tests/output/basic/ \ samples/content/ diff --git a/pelican/tests/output/custom_locale/archives.html b/pelican/tests/output/custom_locale/archives.html index a7b96336..1336b487 100644 --- a/pelican/tests/output/custom_locale/archives.html +++ b/pelican/tests/output/custom_locale/archives.html @@ -32,25 +32,25 @@

Archives for Alexis' log

-
ven. 30 novembre 2012
+
30 novembre 2012
FILENAME_METADATA example
-
mer. 29 février 2012
+
29 février 2012
Second article
-
mer. 20 avril 2011
+
20 avril 2011
A markdown powered article
-
jeu. 17 février 2011
+
17 février 2011
Article 1
-
jeu. 17 février 2011
+
17 février 2011
Article 2
-
jeu. 17 février 2011
+
17 février 2011
Article 3
-
jeu. 02 décembre 2010
+
02 décembre 2010
This is a super article !
-
mer. 20 octobre 2010
+
20 octobre 2010
Oh yeah !
-
ven. 15 octobre 2010
+
15 octobre 2010
Unbelievable !
-
dim. 14 mars 2010
+
14 mars 2010
The baz tag
diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau.html b/pelican/tests/output/custom_locale/author/alexis-metaireau.html index b54446c8..7c2fa448 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau.html @@ -34,7 +34,7 @@

FILENAME_METADATA example

- Published: ven. 30 novembre 2012 + Published: 30 novembre 2012
@@ -59,7 +59,7 @@
- Published: mer. 29 février 2012 + Published: 29 février 2012
@@ -84,7 +84,7 @@
- Published: mer. 20 avril 2011 + Published: 20 avril 2011
@@ -108,7 +108,7 @@
- Published: jeu. 17 février 2011 + Published: 17 février 2011
diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau2.html b/pelican/tests/output/custom_locale/author/alexis-metaireau2.html index 07020512..9ba313da 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau2.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau2.html @@ -40,7 +40,7 @@
- Published: jeu. 17 février 2011 + Published: 17 février 2011
@@ -63,7 +63,7 @@
- Published: jeu. 17 février 2011 + Published: 17 février 2011
@@ -86,11 +86,11 @@
- Published: jeu. 02 décembre 2010 + Published: 02 décembre 2010
- Updated: dim. 17 novembre 2013 + Updated: 17 novembre 2013
@@ -114,7 +114,7 @@ as well as inline markup.

- Published: mer. 20 octobre 2010 + Published: 20 octobre 2010
diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html index 9578e3d6..5104b7b0 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html @@ -40,7 +40,7 @@
- Published: ven. 15 octobre 2010 + Published: 15 octobre 2010
@@ -73,7 +73,7 @@ pelican.conf, it ...

- Published: dim. 14 mars 2010 + Published: 14 mars 2010
diff --git a/pelican/tests/output/custom_locale/category/bar.html b/pelican/tests/output/custom_locale/category/bar.html index d9fc6acb..c693d3f5 100644 --- a/pelican/tests/output/custom_locale/category/bar.html +++ b/pelican/tests/output/custom_locale/category/bar.html @@ -34,7 +34,7 @@

Oh yeah !

- Published: mer. 20 octobre 2010 + Published: 20 octobre 2010
diff --git a/pelican/tests/output/custom_locale/category/cat1.html b/pelican/tests/output/custom_locale/category/cat1.html index 1b09acfe..c332f817 100644 --- a/pelican/tests/output/custom_locale/category/cat1.html +++ b/pelican/tests/output/custom_locale/category/cat1.html @@ -34,7 +34,7 @@

A markdown powered article

- Published: mer. 20 avril 2011 + Published: 20 avril 2011
@@ -60,7 +60,7 @@
- Published: jeu. 17 février 2011 + Published: 17 février 2011
@@ -83,7 +83,7 @@
- Published: jeu. 17 février 2011 + Published: 17 février 2011
@@ -106,7 +106,7 @@
- Published: jeu. 17 février 2011 + Published: 17 février 2011
diff --git a/pelican/tests/output/custom_locale/category/misc.html b/pelican/tests/output/custom_locale/category/misc.html index bcaec248..02562525 100644 --- a/pelican/tests/output/custom_locale/category/misc.html +++ b/pelican/tests/output/custom_locale/category/misc.html @@ -34,7 +34,7 @@

FILENAME_METADATA example

- Published: ven. 30 novembre 2012 + Published: 30 novembre 2012
@@ -59,7 +59,7 @@
- Published: mer. 29 février 2012 + Published: 29 février 2012
@@ -84,7 +84,7 @@
- Published: ven. 15 octobre 2010 + Published: 15 octobre 2010
@@ -117,7 +117,7 @@ pelican.conf, it ...

- Published: dim. 14 mars 2010 + Published: 14 mars 2010
diff --git a/pelican/tests/output/custom_locale/category/yeah.html b/pelican/tests/output/custom_locale/category/yeah.html index 7f881612..6427095b 100644 --- a/pelican/tests/output/custom_locale/category/yeah.html +++ b/pelican/tests/output/custom_locale/category/yeah.html @@ -34,11 +34,11 @@

This is a super article !

- Published: jeu. 02 décembre 2010 + Published: 02 décembre 2010
- Updated: dim. 17 novembre 2013 + Updated: 17 novembre 2013
diff --git a/pelican/tests/output/custom_locale/drafts/a-draft-article.html b/pelican/tests/output/custom_locale/drafts/a-draft-article.html index 82fb057b..16ff22cd 100644 --- a/pelican/tests/output/custom_locale/drafts/a-draft-article.html +++ b/pelican/tests/output/custom_locale/drafts/a-draft-article.html @@ -39,7 +39,7 @@
- Published: ven. 02 mars 2012 + Published: 02 mars 2012
diff --git a/pelican/tests/output/custom_locale/index.html b/pelican/tests/output/custom_locale/index.html index fd9a82b4..5898c5b5 100644 --- a/pelican/tests/output/custom_locale/index.html +++ b/pelican/tests/output/custom_locale/index.html @@ -34,7 +34,7 @@

FILENAME_METADATA example

- Published: ven. 30 novembre 2012 + Published: 30 novembre 2012
@@ -59,7 +59,7 @@
- Published: mer. 29 février 2012 + Published: 29 février 2012
@@ -84,7 +84,7 @@
- Published: mer. 20 avril 2011 + Published: 20 avril 2011
@@ -108,7 +108,7 @@
- Published: jeu. 17 février 2011 + Published: 17 février 2011
diff --git a/pelican/tests/output/custom_locale/index2.html b/pelican/tests/output/custom_locale/index2.html index f02ba27c..f1ed6b46 100644 --- a/pelican/tests/output/custom_locale/index2.html +++ b/pelican/tests/output/custom_locale/index2.html @@ -40,7 +40,7 @@
- Published: jeu. 17 février 2011 + Published: 17 février 2011
@@ -63,7 +63,7 @@
- Published: jeu. 17 février 2011 + Published: 17 février 2011
@@ -86,11 +86,11 @@
- Published: jeu. 02 décembre 2010 + Published: 02 décembre 2010
- Updated: dim. 17 novembre 2013 + Updated: 17 novembre 2013
@@ -114,7 +114,7 @@ as well as inline markup.

- Published: mer. 20 octobre 2010 + Published: 20 octobre 2010
diff --git a/pelican/tests/output/custom_locale/index3.html b/pelican/tests/output/custom_locale/index3.html index 9c7416b6..7f7a905f 100644 --- a/pelican/tests/output/custom_locale/index3.html +++ b/pelican/tests/output/custom_locale/index3.html @@ -40,7 +40,7 @@
- Published: ven. 15 octobre 2010 + Published: 15 octobre 2010
@@ -73,7 +73,7 @@ pelican.conf, it ...

- Published: dim. 14 mars 2010 + Published: 14 mars 2010
diff --git a/pelican/tests/output/custom_locale/oh-yeah-fr.html b/pelican/tests/output/custom_locale/oh-yeah-fr.html index cdda855d..72c8b62d 100644 --- a/pelican/tests/output/custom_locale/oh-yeah-fr.html +++ b/pelican/tests/output/custom_locale/oh-yeah-fr.html @@ -39,7 +39,7 @@
- Published: ven. 02 mars 2012 + Published: 02 mars 2012
diff --git a/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html b/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html index ec1007f3..0dd0e3da 100644 --- a/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html +++ b/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html @@ -39,11 +39,11 @@
- Published: jeu. 02 décembre 2010 + Published: 02 décembre 2010
- Updated: dim. 17 novembre 2013 + Updated: 17 novembre 2013
diff --git a/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html b/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html index cb8ccef5..6cdb4bb1 100644 --- a/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html +++ b/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html @@ -39,7 +39,7 @@
- Published: ven. 15 octobre 2010 + Published: 15 octobre 2010
diff --git a/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html b/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html index c42bce5d..233df239 100644 --- a/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html +++ b/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html @@ -39,7 +39,7 @@
- Published: mer. 20 octobre 2010 + Published: 20 octobre 2010
diff --git a/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html b/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html index 49cc6078..aa4d254f 100644 --- a/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html +++ b/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html @@ -39,7 +39,7 @@
- Published: mer. 20 avril 2011 + Published: 20 avril 2011
diff --git a/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html b/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html index 8029e585..e9c86a97 100644 --- a/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html +++ b/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html @@ -39,7 +39,7 @@
- Published: jeu. 17 février 2011 + Published: 17 février 2011
diff --git a/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html b/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html index ca6aaaf3..8724d7c2 100644 --- a/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html +++ b/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html @@ -39,7 +39,7 @@
- Published: jeu. 17 février 2011 + Published: 17 février 2011
diff --git a/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html b/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html index 4f255f4f..c14b537d 100644 --- a/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html +++ b/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html @@ -39,7 +39,7 @@
- Published: jeu. 17 février 2011 + Published: 17 février 2011
diff --git a/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html b/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html index 46b07717..598bf797 100644 --- a/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html +++ b/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html @@ -39,7 +39,7 @@
- Published: mer. 29 février 2012 + Published: 29 février 2012
diff --git a/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html b/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html index 0d021cde..66339175 100644 --- a/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html +++ b/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html @@ -39,7 +39,7 @@
- Published: ven. 30 novembre 2012 + Published: 30 novembre 2012
diff --git a/pelican/tests/output/custom_locale/second-article-fr.html b/pelican/tests/output/custom_locale/second-article-fr.html index 2798c94b..eb2225d9 100644 --- a/pelican/tests/output/custom_locale/second-article-fr.html +++ b/pelican/tests/output/custom_locale/second-article-fr.html @@ -39,7 +39,7 @@
- Published: mer. 29 février 2012 + Published: 29 février 2012
diff --git a/pelican/tests/output/custom_locale/tag/bar.html b/pelican/tests/output/custom_locale/tag/bar.html index 8a65a834..a8e3bd7a 100644 --- a/pelican/tests/output/custom_locale/tag/bar.html +++ b/pelican/tests/output/custom_locale/tag/bar.html @@ -34,7 +34,7 @@

Second article

- Published: mer. 29 février 2012 + Published: 29 février 2012
@@ -61,11 +61,11 @@
- Published: jeu. 02 décembre 2010 + Published: 02 décembre 2010
- Updated: dim. 17 novembre 2013 + Updated: 17 novembre 2013
@@ -89,7 +89,7 @@ as well as inline markup.

- Published: mer. 20 octobre 2010 + Published: 20 octobre 2010
diff --git a/pelican/tests/output/custom_locale/tag/baz.html b/pelican/tests/output/custom_locale/tag/baz.html index 52467abb..77e09faf 100644 --- a/pelican/tests/output/custom_locale/tag/baz.html +++ b/pelican/tests/output/custom_locale/tag/baz.html @@ -39,7 +39,7 @@
- Published: dim. 14 mars 2010 + Published: 14 mars 2010
diff --git a/pelican/tests/output/custom_locale/tag/foo.html b/pelican/tests/output/custom_locale/tag/foo.html index 87cb4ec5..3c238811 100644 --- a/pelican/tests/output/custom_locale/tag/foo.html +++ b/pelican/tests/output/custom_locale/tag/foo.html @@ -34,7 +34,7 @@

Second article

- Published: mer. 29 février 2012 + Published: 29 février 2012
@@ -61,11 +61,11 @@
- Published: jeu. 02 décembre 2010 + Published: 02 décembre 2010
- Updated: dim. 17 novembre 2013 + Updated: 17 novembre 2013
diff --git a/pelican/tests/output/custom_locale/tag/foobar.html b/pelican/tests/output/custom_locale/tag/foobar.html index 0e5414b0..e30c4906 100644 --- a/pelican/tests/output/custom_locale/tag/foobar.html +++ b/pelican/tests/output/custom_locale/tag/foobar.html @@ -34,11 +34,11 @@

This is a super article !

- Published: jeu. 02 décembre 2010 + Published: 02 décembre 2010
- Updated: dim. 17 novembre 2013 + Updated: 17 novembre 2013
diff --git a/pelican/tests/output/custom_locale/tag/yeah.html b/pelican/tests/output/custom_locale/tag/yeah.html index a8fe6f51..6754ecf6 100644 --- a/pelican/tests/output/custom_locale/tag/yeah.html +++ b/pelican/tests/output/custom_locale/tag/yeah.html @@ -34,7 +34,7 @@

Oh yeah !

- Published: mer. 20 octobre 2010 + Published: 20 octobre 2010
diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index cfae594a..bad73569 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -20,7 +20,7 @@ OUTPUT_PATH = os.path.abspath(os.path.join(CURRENT_DIR, 'output')) INPUT_PATH = os.path.join(SAMPLES_PATH, "content") SAMPLE_CONFIG = os.path.join(SAMPLES_PATH, "pelican.conf.py") -SAMPLE_FR_CONFIG = os.path.join(SAMPLES_PATH, "pelican_FR.conf.py") +SAMPLE_FR_CONFIG = os.path.join(SAMPLES_PATH, "pelican.conf_FR.py") def recursiveDiff(dcmp): diff --git a/pelican/utils.py b/pelican/utils.py index 586d85ff..5e4822ba 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -94,12 +94,18 @@ class DateFormatter(object): self.locale = locale.setlocale(locale.LC_TIME) def __call__(self, date, date_format): - old_locale = locale.setlocale(locale.LC_TIME) + old_lc_time = locale.setlocale(locale.LC_TIME) + old_lc_ctype = locale.setlocale(locale.LC_CTYPE) + locale.setlocale(locale.LC_TIME, self.locale) + # on OSX, encoding from LC_CTYPE determines the unicode output in PY3 + # make sure it's same as LC_TIME + locale.setlocale(locale.LC_CTYPE, self.locale) formatted = strftime(date, date_format) - locale.setlocale(locale.LC_TIME, old_locale) + locale.setlocale(locale.LC_TIME, old_lc_time) + locale.setlocale(locale.LC_CTYPE, old_lc_ctype) return formatted diff --git a/samples/pelican_FR.conf.py b/samples/pelican.conf_FR.py similarity index 98% rename from samples/pelican_FR.conf.py rename to samples/pelican.conf_FR.py index 1f6aaaa1..0c4ec60d 100644 --- a/samples/pelican_FR.conf.py +++ b/samples/pelican.conf_FR.py @@ -16,6 +16,7 @@ REVERSE_CATEGORY_ORDER = True LOCALE = "fr_FR.UTF-8" DEFAULT_PAGINATION = 4 DEFAULT_DATE = (2012, 3, 2, 14, 1, 1) +DEFAULT_DATE_FORMAT = '%d %B %Y' ARTICLE_URL = 'posts/{date:%Y}/{date:%B}/{date:%d}/{slug}/' ARTICLE_SAVE_AS = ARTICLE_URL + 'index.html' From ce8574aff4e527b533557feedbb6256937359ede Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Wed, 11 Jun 2014 15:58:06 -0400 Subject: [PATCH 0023/1173] Fix HTMLParser related deprecation warnings in Py3.4 --- pelican/readers.py | 12 ++++++------ pelican/tools/pelican_import.py | 9 ++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/pelican/readers.py b/pelican/readers.py index 431e6937..954ea412 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -21,10 +21,7 @@ try: from html import escape except ImportError: from cgi import escape -try: - from html.parser import HTMLParser -except ImportError: - from HTMLParser import HTMLParser +from six.moves.html_parser import HTMLParser from pelican import signals from pelican.contents import Page, Category, Tag, Author @@ -43,7 +40,6 @@ METADATA_PROCESSORS = { logger = logging.getLogger(__name__) - class BaseReader(object): """Base class to read files. @@ -231,7 +227,11 @@ class HTMLReader(BaseReader): class _HTMLParser(HTMLParser): def __init__(self, settings, filename): - HTMLParser.__init__(self) + try: + # Python 3.4+ + HTMLParser.__init__(self, convert_charrefs=False) + except TypeError: + HTMLParser.__init__(self) self.body = '' self.metadata = {} self.settings = settings diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 7c8662c9..a50aa216 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -4,11 +4,10 @@ from __future__ import unicode_literals, print_function import argparse try: - # py3k import - from html.parser import HTMLParser + from html import unescape # py3.4+ except ImportError: - # py2 import - from HTMLParser import HTMLParser # NOQA + from six.moves.html_parser import HTMLParser + unescape = HTMLParser().unescape import os import re import subprocess @@ -129,7 +128,7 @@ def wp2fields(xml, wp_custpost=False): try: # Use HTMLParser due to issues with BeautifulSoup 3 - title = HTMLParser().unescape(item.title.contents[0]) + title = unescape(item.title.contents[0]) except IndexError: title = 'No title [%s]' % item.find('post_name').string logger.warning('Post "%s" is lacking a proper title' % title) From fc505091c280f2b8d5645c18352dd14c93e13f11 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Wed, 11 Jun 2014 16:11:48 -0400 Subject: [PATCH 0024/1173] Patch docutils.io.FileInput to not use "U" mode in py3 "U" mode is redundant in py3 since "newline" argument replaces it and by default universal newlines is enabled. As of py3.4, "U" mode triggers a deprecation warning. --- pelican/readers.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pelican/readers.py b/pelican/readers.py index 954ea412..101ed8b5 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -10,6 +10,7 @@ import docutils import docutils.core import docutils.io from docutils.writers.html4css1 import HTMLTranslator +import six # import the directives to have pygments support from pelican import rstdirectives # NOQA @@ -118,6 +119,19 @@ class RstReader(BaseReader): enabled = bool(docutils) file_extensions = ['rst'] + class FileInput(docutils.io.FileInput): + """Patch docutils.io.FileInput to remove "U" mode in py3. + + Universal newlines is enabled by default and "U" mode is deprecated + in py3. + + """ + + def __init__(self, *args, **kwargs): + if six.PY3: + kwargs['mode'] = kwargs.get('mode', 'r').replace('U', '') + docutils.io.FileInput.__init__(self, *args, **kwargs) + def __init__(self, *args, **kwargs): super(RstReader, self).__init__(*args, **kwargs) @@ -149,12 +163,14 @@ class RstReader(BaseReader): extra_params = {'initial_header_level': '2', 'syntax_highlight': 'short', 'input_encoding': 'utf-8', - 'exit_status_level': 2} + 'exit_status_level': 2, + 'embed_stylesheet': False} user_params = self.settings.get('DOCUTILS_SETTINGS') if user_params: extra_params.update(user_params) pub = docutils.core.Publisher( + source_class=self.FileInput, destination_class=docutils.io.StringOutput) pub.set_components('standalone', 'restructuredtext', 'html') pub.writer.translator_class = PelicanHTMLTranslator From 40c9406ca4302b5c60fd8d205aae418b450a0844 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Sun, 25 May 2014 10:06:31 +0200 Subject: [PATCH 0025/1173] Fix test_direct_templates_save_as_false test case A non-existent asserts_called_count attr was used which would always succeed, silently hiding the error. --- pelican/tests/test_generators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index dfa9a36b..9305fa6e 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -198,14 +198,14 @@ class TestArticlesGenerator(unittest.TestCase): settings = get_settings() settings['DIRECT_TEMPLATES'] = ['archives'] - settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' + settings['ARCHIVES_SAVE_AS'] = False settings['CACHE_PATH'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, path=None, theme=settings['THEME'], output_path=None) write = MagicMock() generator.generate_direct_templates(write) - write.assert_called_count == 0 + self.assertEqual(write.call_count, 0) def test_per_article_template(self): """ From 7fabd712a1b91eeb9a71f3f4b10d4c979acfe799 Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Fri, 27 Jun 2014 20:18:17 +0200 Subject: [PATCH 0026/1173] Generator.get_files backwards compatibility for 1 path This should prevent outdated plugins with generators from failing. Also fixes #1382. --- pelican/generators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pelican/generators.py b/pelican/generators.py index 865cc6f8..7cbce8de 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -119,6 +119,8 @@ class Generator(object): :param extensions: the list of allowed extensions (if False, all extensions are allowed) """ + if isinstance(paths, six.string_types): + paths = [paths] # backward compatibility for older generators files = [] for path in paths: root = os.path.join(self.path, path) From 95e170a15fcb47cdefb3e8e3d72239ce6dc538a7 Mon Sep 17 00:00:00 2001 From: Giulia Vergottini Date: Wed, 11 Jun 2014 22:56:32 +0200 Subject: [PATCH 0027/1173] prettify github make commit message --- pelican/tools/templates/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in index 6bfa7b0c..6c1e321e 100644 --- a/pelican/tools/templates/Makefile.in +++ b/pelican/tools/templates/Makefile.in @@ -103,7 +103,7 @@ cf_upload: publish cd $(OUTPUTDIR) && swift -v -A https://auth.api.rackspacecloud.com/v1.0 -U $(CLOUDFILES_USERNAME) -K $(CLOUDFILES_API_KEY) upload -c $(CLOUDFILES_CONTAINER) . github: publish - ghp-import -b $(GITHUB_PAGES_BRANCH) $$(OUTPUTDIR) + ghp-import -m "generate blog content" -b $(GITHUB_PAGES_BRANCH) $$(OUTPUTDIR) git push origin $(GITHUB_PAGES_BRANCH) .PHONY: html help clean regenerate serve devserver publish ssh_upload rsync_upload dropbox_upload ftp_upload s3_upload cf_upload github From 5eaf2aa2bfe2fcc92e7e56b48b5e1cfa868d4be4 Mon Sep 17 00:00:00 2001 From: Giulia Vergottini Date: Sun, 29 Jun 2014 17:00:33 +0200 Subject: [PATCH 0028/1173] update make github commit message --- pelican/tools/templates/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in index 6c1e321e..a8ffd6ca 100644 --- a/pelican/tools/templates/Makefile.in +++ b/pelican/tools/templates/Makefile.in @@ -103,7 +103,7 @@ cf_upload: publish cd $(OUTPUTDIR) && swift -v -A https://auth.api.rackspacecloud.com/v1.0 -U $(CLOUDFILES_USERNAME) -K $(CLOUDFILES_API_KEY) upload -c $(CLOUDFILES_CONTAINER) . github: publish - ghp-import -m "generate blog content" -b $(GITHUB_PAGES_BRANCH) $$(OUTPUTDIR) + ghp-import -m "Generate Pelican site" -b $(GITHUB_PAGES_BRANCH) $$(OUTPUTDIR) git push origin $(GITHUB_PAGES_BRANCH) .PHONY: html help clean regenerate serve devserver publish ssh_upload rsync_upload dropbox_upload ftp_upload s3_upload cf_upload github From 43523dac4df78604b51339ecbaccfa82313d3281 Mon Sep 17 00:00:00 2001 From: Eli Bendersky Date: Mon, 30 Jun 2014 06:40:19 -0700 Subject: [PATCH 0029/1173] Fix "server didn't start" error message in develop_server.sh It has port 8000 hardcoded into it, which is confusing when the server runs on another port. --- pelican/tools/templates/develop_server.sh.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/templates/develop_server.sh.in b/pelican/tools/templates/develop_server.sh.in index 4259af5b..732069c2 100755 --- a/pelican/tools/templates/develop_server.sh.in +++ b/pelican/tools/templates/develop_server.sh.in @@ -75,7 +75,7 @@ function start_up(){ echo "Pelican didn't start. Is the Pelican package installed?" return 1 elif ! alive $$srv_pid ; then - echo "The HTTP server didn't start. Is there another service using port 8000?" + echo "The HTTP server didn't start. Is there another service using port" $$port "?" return 1 fi echo 'Pelican and HTTP server processes now running in background.' From 2e1b16826b1b933c9f819bca9cbfd5d754bb3c1e Mon Sep 17 00:00:00 2001 From: Ondrej Grover Date: Mon, 26 May 2014 12:32:05 +0200 Subject: [PATCH 0030/1173] Enhance feedback and contributing guidelines Many folks ask for help (via IRC or filing GitHub issues) without sufficient detail and/or without first determining if the answers to their questions might be better sourced elsewhere. These documentation changes encourage users to follow certain guidelines when reaching out for help. --- CONTRIBUTING.rst | 111 +++++++++++++++++++++++++++++++++++++++----- README.rst | 17 ++----- docs/contribute.rst | 18 ++----- docs/faq.rst | 11 +---- docs/index.rst | 18 ++----- 5 files changed, 111 insertions(+), 64 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 6063613b..6b1f3b05 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,20 +1,91 @@ -Contribution submission guidelines -================================== +Filing issues +------------- + +* Before you file an issue, try `asking for help`_ first. +* If determined to file an issue, first check for `existing issues`_, including closed issues. + +.. _`asking for help`: `How to get help`_ +.. _`existing issues`: https://github.com/getpelican/pelican/issues + +How to get help +--------------- + +Before you ask for help, please make sure you do the following: + +1. Read the documentation_ thoroughly. If in a hurry, at least use the search + field that is provided at top-right on the documentation_ pages. Make sure + you read the docs for the Pelican version you are using. +2. Use a search engine (e.g., DuckDuckGo, Google) to search for a solution to + your problem. Someone may have already found a solution, perhaps in the + form of a plugin_ or a specific combination of settings. + +3. Try reproducing the issue in a clean environment, ensuring you are using: + +* latest Pelican release (or an up-to-date git clone of Pelican master) +* latest releases of libraries used by Pelican +* no plugins or only those related to the issue + +If despite the above efforts you still cannot resolve your problem, be sure to +include in your inquiry the following, preferably in the form of links to +content uploaded to a `paste service`_, GitHub repository, or other +publicly-accessible location: + +* Describe what version of Pelican you are running (output of ``pelican --version`` + or the HEAD commit hash if you cloned the repo) and how exactly you installed + it (the full command you used, e.g. ``pip install pelican``). +* If you are looking for a way to get some end result, prepare a detailed + description of what the end result should look like (preferably in the form of + an image or a mock-up page) and explain in detail what you have done so far to achieve it. +* If you are trying to solve some issue, prepare a detailed description of how + to reproduce the problem. If the issue cannot be easily reproduced, it cannot + be debugged by developers or volunteers. Describe only the **minimum steps** + necessary to reproduce it (no extra plugins, etc.). +* Upload your settings file or any other custom code that would enable people to + reproduce the problem or to see what you have already tried to achieve the + desired end result. +* Upload detailed and **complete** output logs and backtraces (remember to add + the ``--debug`` flag: ``pelican --debug content [...]``) + +.. _documentation: http://docs.getpelican.com/ +.. _`paste service`: https://dpaste.de/ + +Once the above preparation is ready, you can contact people willing to help via +(preferably) the ``#pelican`` IRC channel or send a message to ``authors at getpelican dot com``. +Remember to include all the information you prepared. + +The #pelican IRC channel +........................ + +* Because of differing time zones, you may not get an immediate response to your + question, but please be patient and stay logged into IRC — someone will almost + always respond if you wait long enough (it may take a few hours). +* If you don't have an IRC client handy, use the webchat_ for quick feedback. +* You can direct your IRC client to the channel using this `IRC link`_ or you + can manually join the ``#pelican`` IRC channel on the `freenode IRC network`_. + +.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4 +.. _`IRC link`: irc://irc.freenode.org/pelican +.. _`freenode IRC network`: http://www.freenode.org/ + + +Contributing code +----------------- + +Before you submit a contribution, please ask whether it is desired so that you +don't spend a lot of time working on something that would be rejected for a +known reason. Consider also whether your new feature might be better suited as +a plugin_ — you can `ask for help`_ to make that determination. + +Using Git and GitHub +.................... -* Consider whether your new feature might be better suited as a plugin_. Folks - are usually available in the `#pelican IRC channel`_ if help is needed to - make that determination. * `Create a new git branch`_ specific to your change (as opposed to making your commits in the master branch). -* **Don't put multiple fixes/features in the same branch / pull request.** +* **Don't put multiple unrelated fixes/features in the same branch / pull request.** For example, if you're hacking on a new feature and find a bugfix that doesn't *require* your new feature, **make a new distinct branch and pull request** for the bugfix. -* Adhere to `PEP8 coding standards`_ whenever possible. * Check for unnecessary whitespace via ``git diff --check`` before committing. -* **Add docs and tests for your changes**. -* `Run all the tests`_ **on both Python 2.7 and 3.3** to ensure nothing was - accidentally broken. * First line of your commit message should start with present-tense verb, be 50 characters or less, and include the relevant issue number(s) if applicable. *Example:* ``Ensure proper PLUGIN_PATH behavior. Refs #428.`` If the commit @@ -32,7 +103,22 @@ Contribution submission guidelines `hub pull-request `_ to turn your GitHub issue into a pull request containing your code. -Check out our `Git Tips`_ page or ask on the `#pelican IRC channel`_ if you +Contribution quality standards +.............................. + +* Adhere to `PEP8 coding standards`_ whenever possible. This can be eased via + the `pep8 `_ or `flake8 + `_ tools, the latter of which in + particular will give you some useful hints about ways in which the + code/formatting can be improved. +* Make sure your code is compatible with Python 2.7, 3.3, and 3.4 — see our + `compatibility cheatsheet`_ for more details. +* Add docs and tests for your changes. Undocumented and untested features will + not be accepted. +* `Run all the tests`_ **on all versions of Python supported by Pelican** to + ensure nothing was accidentally broken. + +Check out our `Git Tips`_ page or `ask for help`_ if you need assistance or have any questions about these guidelines. .. _`plugin`: http://docs.getpelican.com/en/latest/plugins.html @@ -42,4 +128,5 @@ need assistance or have any questions about these guidelines. .. _`Run all the tests`: http://docs.getpelican.com/en/latest/contribute.html#running-the-test-suite .. _`Git Tips`: https://github.com/getpelican/pelican/wiki/Git-Tips .. _`PEP8 coding standards`: http://www.python.org/dev/peps/pep-0008/ - +.. _`ask for help`: `How to get help`_ +.. _`compatibility cheatsheet`: http://docs.getpelican.com/en/latest/contribute.html#python-3-development-tips diff --git a/README.rst b/README.rst index bf506c5f..f6b5e4e2 100644 --- a/README.rst +++ b/README.rst @@ -45,16 +45,10 @@ You can access the source code at: https://github.com/getpelican/pelican If you feel hackish, have a look at the explanation of `Pelican's internals`_. -Feedback / Contact us ---------------------- +How to get help, contribute, or provide feedback +------------------------------------------------ -If you want to see new features in Pelican, don't hesitate to offer -suggestions, clone the repository, etc. There are many ways to contribute_. -That's open source, dude! - -Send a message to "authors at getpelican dot com" with any requests/feedback! You -can also join the team at `#pelican on Freenode`_ (or if you don't have an IRC -client handy, use the webchat_ for quick feedback. +See our `contribution submission and feedback guidelines `_. .. Links @@ -62,8 +56,5 @@ client handy, use the webchat_ for quick feedback. .. _reStructuredText: http://docutils.sourceforge.net/rst.html .. _Markdown: http://daringfireball.net/projects/markdown/ .. _Jinja2: http://jinja.pocoo.org/ -.. _`Pelican documentation`: http://docs.getpelican.com/latest/ +.. _`Pelican documentation`: http://docs.getpelican.com/ .. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html -.. _`#pelican on Freenode`: irc://irc.freenode.net/pelican -.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4 -.. _contribute: http://docs.getpelican.com/en/latest/contribute.html diff --git a/docs/contribute.rst b/docs/contribute.rst index 19604da0..72837593 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -1,13 +1,13 @@ -How to contribute -################# +Contributing and Feedback Guidelines +#################################### There are many ways to contribute to Pelican. You can improve the documentation, add missing features, and fix bugs (or just report them). You can also help out by reviewing and commenting on `existing issues `_. -Don't hesitate to fork Pelican and submit a pull request on GitHub. When doing -so, please adhere to the following guidelines. +Don't hesitate to fork Pelican and submit an issue or pull request on GitHub. +When doing so, please adhere to the following guidelines. .. include:: ../CONTRIBUTING.rst @@ -47,16 +47,6 @@ Or using ``pip``:: $ pip install -e . -Coding standards -================ - -Try to respect what is described in the `PEP8 specification -`_ when making contributions. This -can be eased via the `pep8 `_ or `flake8 -`_ tools, the latter of which in -particular will give you some useful hints about ways in which the -code/formatting can be improved. - Building the docs ================= diff --git a/docs/faq.rst b/docs/faq.rst index 3a5cfec5..5a48a107 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -6,16 +6,7 @@ Here are some frequently asked questions about Pelican. What's the best way to communicate a problem, question, or suggestion? ====================================================================== -If you have a problem, question, or suggestion, please start by striking up a -conversation on `#pelican on Freenode `_. -Those who don't have an IRC client handy can jump in immediately via -`IRC webchat `_. Because -of differing time zones, you may not get an immediate response to your -question, but please be patient and stay logged into IRC — someone will almost -always respond if you wait long enough (it may take a few hours). - -If you're unable to resolve your issue or if you have a feature request, please -refer to the `issue tracker `_. +Please read our :doc:`feedback guidelines `. How can I help? ================ diff --git a/docs/index.rst b/docs/index.rst index 2beb8b20..ccfb9982 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -47,20 +47,10 @@ Source code You can access the source code at: https://github.com/getpelican/pelican -Feedback / Contact us ---------------------- +How to get help, contribute, or provide feedback +------------------------------------------------ -If you want to see new features in Pelican, don't hesitate to offer suggestions, -clone the repository, etc. There are many ways to :doc:`contribute`. -That's open source, dude! - -Send a message to "authors at getpelican dot com" with any requests/feedback. -For a more immediate response, you can also join the team via IRC at -`#pelican on Freenode`_ — if you don't have an IRC client handy, use the -webchat_ for quick feedback. If you ask a question via IRC and don't get an -immediate response, don't leave the channel! It may take a few hours because -of time zone differences, but if you are patient and remain in the channel, -someone will almost always respond to your inquiry. +See our :doc:`feedback and contribution submission guidelines `. Documentation ------------- @@ -93,5 +83,3 @@ Documentation .. _`Pelican documentation`: http://docs.getpelican.com/latest/ .. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html .. _`Pelican plugins`: https://github.com/getpelican/pelican-plugins -.. _`#pelican on Freenode`: irc://irc.freenode.net/pelican -.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4 From b2ccc62582152eda8efeccee9f0e1d2bb78dba5a Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 30 Jun 2014 18:42:27 -0700 Subject: [PATCH 0031/1173] Add troubleshooting info to CONTRIBUTING docs --- CONTRIBUTING.rst | 20 ++++++++++++++++---- docs/contribute.rst | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 6b1f3b05..67357e48 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -2,7 +2,8 @@ Filing issues ------------- * Before you file an issue, try `asking for help`_ first. -* If determined to file an issue, first check for `existing issues`_, including closed issues. +* If determined to file an issue, first check for `existing issues`_, including + closed issues. .. _`asking for help`: `How to get help`_ .. _`existing issues`: https://github.com/getpelican/pelican/issues @@ -25,9 +26,19 @@ Before you ask for help, please make sure you do the following: * latest releases of libraries used by Pelican * no plugins or only those related to the issue +**NOTE:** The most common sources of problems are anomalies in (1) themes, +(2) settings files, and (3) ``make``/``fab`` automation wrappers. If you can't +reproduce your problem when using the following steps to generate your site, +then the problem is almost certainly with your chosen theme and/or settings +file (and not Pelican itself):: + + cd ~/projects/your-site + git clone https://github.com/getpelican/pelican ~/projects/pelican + pelican content -s ~/projects/pelican/samples/pelican.conf.py -t ~/projects/pelican/pelican/themes/notmyidea + If despite the above efforts you still cannot resolve your problem, be sure to -include in your inquiry the following, preferably in the form of links to -content uploaded to a `paste service`_, GitHub repository, or other +include in your inquiry the following information, preferably in the form of +links to content uploaded to a `paste service`_, GitHub repository, or other publicly-accessible location: * Describe what version of Pelican you are running (output of ``pelican --version`` @@ -35,7 +46,8 @@ publicly-accessible location: it (the full command you used, e.g. ``pip install pelican``). * If you are looking for a way to get some end result, prepare a detailed description of what the end result should look like (preferably in the form of - an image or a mock-up page) and explain in detail what you have done so far to achieve it. + an image or a mock-up page) and explain in detail what you have done so far to + achieve it. * If you are trying to solve some issue, prepare a detailed description of how to reproduce the problem. If the issue cannot be easily reproduced, it cannot be debugged by developers or volunteers. Describe only the **minimum steps** diff --git a/docs/contribute.rst b/docs/contribute.rst index 72837593..a3f56e9f 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -1,4 +1,4 @@ -Contributing and Feedback Guidelines +Contributing and feedback guidelines #################################### There are many ways to contribute to Pelican. You can improve the From 669bdc92e1a041fa176d59e766ab4c67619dc8c5 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 30 Jun 2014 18:48:46 -0700 Subject: [PATCH 0032/1173] Change IRC webchat provider to Kiwi IRC --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 67357e48..85d30c97 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -75,7 +75,7 @@ The #pelican IRC channel * You can direct your IRC client to the channel using this `IRC link`_ or you can manually join the ``#pelican`` IRC channel on the `freenode IRC network`_. -.. _webchat: http://webchat.freenode.net/?channels=pelican&uio=d4 +.. _webchat: https://kiwiirc.com/client/irc.freenode.net/?#pelican .. _`IRC link`: irc://irc.freenode.org/pelican .. _`freenode IRC network`: http://www.freenode.org/ From 975140d352b5dd80d2f0a25048afc097b33af96c Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 30 Jun 2014 18:58:14 -0700 Subject: [PATCH 0033/1173] Promote URL settings docs to top-level heading --- docs/settings.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.rst b/docs/settings.rst index 86424ec5..5db687dd 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -197,7 +197,7 @@ Setting name (followed by default value, if any) URL settings ------------- +============ The first thing to understand is that there are currently two supported methods for URL formation: *relative* and *absolute*. Relative URLs are useful From 9af21690270cd1959ab38f33e96f7b2a14ee46d1 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 1 Jul 2014 11:55:18 -0700 Subject: [PATCH 0034/1173] Update changelog in preparation for release --- docs/changelog.rst | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 68348da5..4e3b6834 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,10 +4,24 @@ Release history Next release ============ -* Added the `:modified:` metadata field to complement `:date:`. - Used to specify the last date and time an article was updated independently from the date and time it was published. -* Produce inline links instead of reference-style links when importing content. -* Multiple authors support added via new `:authors:` metadata field. +* Speed up content generation via new caching mechanism +* Switch Pelican docs to prettier RtD theme +* Add support for multiple content and plugin paths +* Add ``:modified:`` metadata field to complement ``:date:``. + Used to specify the last date and time an article was updated independently + from the date and time it was published. +* Add support for multiple authors via new ``:authors:`` metadata field +* Watch for changes in static directories when in auto-regeneration mode +* Add filters to limit log output when desired +* Add language support to drafts +* Add ``SLUGIFY_SOURCE`` setting to control how post slugs are generated +* Fix many issues relating to locale and encoding +* Apply Typogrify filter to post summary +* Preserve file metadata (e.g. time stamps) when copying static files to output +* Move AsciiDoc support from Pelican core into separate plugin +* Produce inline links instead of reference-style links when importing content +* Improve handling of ``IGNORE_FILES`` setting behavior +* Properly escape symbol characters in tag names (e.g., ``C++``) 3.3.0 (2013-09-24) ================== From c7052a6404cd485b2240c1f0f97b26a32df03200 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 1 Jul 2014 12:16:45 -0700 Subject: [PATCH 0035/1173] Build binary wheels when publishing to PyPI --- bumpr.rc | 2 +- dev_requirements.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bumpr.rc b/bumpr.rc index 969fdb24..97dbad22 100644 --- a/bumpr.rc +++ b/bumpr.rc @@ -5,7 +5,7 @@ clean = python setup.py clean rm -rf *egg-info build dist tests = python -m unittest discover -publish = python setup.py sdist register upload +publish = python setup.py sdist bdist_wheel register upload files = README.rst [bump] diff --git a/dev_requirements.txt b/dev_requirements.txt index 01fe2507..a7c10719 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -9,6 +9,7 @@ typogrify # To perform release bumpr==0.2.0 +wheel # For docs theme sphinx_rtd_theme From bea40aab8c1e7630d83000f983122c01093c5cd1 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 1 Jul 2014 12:24:37 -0700 Subject: [PATCH 0036/1173] Additions to changelog --- docs/changelog.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4e3b6834..0b08c428 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,7 +5,8 @@ Next release ============ * Speed up content generation via new caching mechanism -* Switch Pelican docs to prettier RtD theme +* Add selective post generation (instead of always building entire site) +* Many documentation improvements, including switching to prettier RtD theme * Add support for multiple content and plugin paths * Add ``:modified:`` metadata field to complement ``:date:``. Used to specify the last date and time an article was updated independently @@ -22,6 +23,8 @@ Next release * Produce inline links instead of reference-style links when importing content * Improve handling of ``IGNORE_FILES`` setting behavior * Properly escape symbol characters in tag names (e.g., ``C++``) +* Minor tweaks for Python 3.4 compatibility +* Add several new signals 3.3.0 (2013-09-24) ================== From 09c07e2950fe3b52e3520a441578622eb135adbc Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 1 Jul 2014 13:08:55 -0700 Subject: [PATCH 0037/1173] Fix SLUGIFY_SOURCE docs --- docs/settings.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 5db687dd..d887dcc6 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -173,9 +173,9 @@ Setting name (followed by default value, if any) ``PYGMENTS_RST_OPTIONS = []`` A list of default Pygments settings for your reStructuredText code blocks. See :ref:`internal_pygments_options` for a list of supported options. -``SLUGIFY_SOURCE = 'input'`` Specifies where you want the slug to be automatically generated +``SLUGIFY_SOURCE = 'title'`` Specifies where you want the slug to be automatically generated from. Can be set to ``title`` to use the 'Title:' metadata tag or - ``basename`` to use the article's basename when creating the slug. + ``basename`` to use the article's file name when creating the slug. ``CACHE_CONTENT = True`` If ``True``, save content in a cache file. See :ref:`reading_only_modified_content` for details about caching. ``CONTENT_CACHING_LAYER = 'reader'`` If set to ``'reader'``, save only the raw content and metadata From 7e516cb7b0b9840bcf21915da1652f400c963803 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 1 Jul 2014 13:33:24 -0700 Subject: [PATCH 0038/1173] setup.py version should inherit from __init.py__ --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a2bcaeaa..7f95829f 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ #!/usr/bin/env python from setuptools import setup +from pelican import __version__ requires = ['feedgenerator >= 1.6', 'jinja2 >= 2.7', 'pygments', 'docutils', 'pytz >= 0a', 'blinker', 'unidecode', 'six >= 1.4', @@ -21,7 +22,7 @@ CHANGELOG = open('docs/changelog.rst').read() setup( name="pelican", - version="3.3", + version=__version__, url='http://getpelican.com/', author='Alexis Metaireau', author_email='authors@getpelican.com', @@ -41,6 +42,7 @@ setup( 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Software Development :: Libraries :: Python Modules', ], From a47c0e26c0260ea0a1322cf79dc838d35ddec400 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 1 Jul 2014 13:34:47 -0700 Subject: [PATCH 0039/1173] Bump version 3.4.0 --- docs/changelog.rst | 4 ++-- pelican/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0b08c428..d8c33cb5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,8 +1,8 @@ Release history ############### -Next release -============ +3.4.0 (2014-07-01) +================== * Speed up content generation via new caching mechanism * Add selective post generation (instead of always building entire site) diff --git a/pelican/__init__.py b/pelican/__init__.py index 082e5a58..f3b18039 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -25,7 +25,7 @@ from pelican.settings import read_settings from pelican.utils import clean_output_dir, folder_watcher, file_watcher from pelican.writers import Writer -__version__ = "3.3.1.dev" +__version__ = "3.4.0" DEFAULT_CONFIG_NAME = 'pelicanconf.py' From d41331bd69e3f663f4da341599e102ceaf197d77 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 1 Jul 2014 13:48:16 -0700 Subject: [PATCH 0040/1173] Work around setup.py encoding issue in last commit --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7f95829f..f665a020 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ CHANGELOG = open('docs/changelog.rst').read() setup( name="pelican", - version=__version__, + version=str(__version__), url='http://getpelican.com/', author='Alexis Metaireau', author_email='authors@getpelican.com', From 9b5261512d10a955ca431fd54321cbc9915877bd Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 1 Jul 2014 14:33:04 -0700 Subject: [PATCH 0041/1173] Revert setup.py changes. Ensure universal wheels. Attempts at fancy version number handling in setup.py caused more problems than they were worth, including Travis CI build failures. The setup.cfg key for universal binary wheel generation apparently changed at some point, so that was updated as well. --- setup.cfg | 2 +- setup.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 5e409001..2a9acf13 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ -[wheel] +[bdist_wheel] universal = 1 diff --git a/setup.py b/setup.py index f665a020..c1af72bb 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,5 @@ #!/usr/bin/env python from setuptools import setup -from pelican import __version__ requires = ['feedgenerator >= 1.6', 'jinja2 >= 2.7', 'pygments', 'docutils', 'pytz >= 0a', 'blinker', 'unidecode', 'six >= 1.4', @@ -22,7 +21,7 @@ CHANGELOG = open('docs/changelog.rst').read() setup( name="pelican", - version=str(__version__), + version="3.4.0", url='http://getpelican.com/', author='Alexis Metaireau', author_email='authors@getpelican.com', From e7eb3b8ec39fea6c62350215fb55fadf167041a5 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 1 Jul 2014 16:03:02 -0700 Subject: [PATCH 0042/1173] Increment development version to 3.5.dev --- pelican/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index f3b18039..43521ff1 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -25,7 +25,7 @@ from pelican.settings import read_settings from pelican.utils import clean_output_dir, folder_watcher, file_watcher from pelican.writers import Writer -__version__ = "3.4.0" +__version__ = "3.5.dev" DEFAULT_CONFIG_NAME = 'pelicanconf.py' From 2c50ccb764bb67fcaaed641289e9e4c3649f0f91 Mon Sep 17 00:00:00 2001 From: OGINO Masanori Date: Tue, 10 Jun 2014 09:47:14 +0900 Subject: [PATCH 0043/1173] Add timezone to datetime objects. Refs #962. Based on https://github.com/getpelican/pelican/pull/977, but it adds timezone information before formatting. Signed-off-by: OGINO Masanori --- pelican/contents.py | 9 ++++++- .../basic/a-markdown-powered-article.html | 4 ++-- pelican/tests/output/basic/archives.html | 2 +- pelican/tests/output/basic/article-1.html | 4 ++-- pelican/tests/output/basic/article-2.html | 4 ++-- pelican/tests/output/basic/article-3.html | 4 ++-- .../output/basic/author/alexis-metaireau.html | 8 +++---- pelican/tests/output/basic/categories.html | 2 +- pelican/tests/output/basic/category/bar.html | 2 +- pelican/tests/output/basic/category/cat1.html | 10 ++++---- pelican/tests/output/basic/category/misc.html | 10 ++++---- pelican/tests/output/basic/category/yeah.html | 4 ++-- .../basic/feeds/alexis-metaireau.atom.xml | 6 ++--- .../basic/feeds/alexis-metaireau.rss.xml | 6 ++--- .../tests/output/basic/feeds/all-en.atom.xml | 22 ++++++++--------- .../tests/output/basic/feeds/all-fr.atom.xml | 4 ++-- pelican/tests/output/basic/feeds/all.atom.xml | 24 +++++++++---------- pelican/tests/output/basic/feeds/bar.atom.xml | 4 ++-- .../tests/output/basic/feeds/cat1.atom.xml | 10 ++++---- .../tests/output/basic/feeds/misc.atom.xml | 10 ++++---- .../tests/output/basic/feeds/yeah.atom.xml | 4 ++-- .../basic/filename_metadata-example.html | 4 ++-- pelican/tests/output/basic/index.html | 24 +++++++++---------- pelican/tests/output/basic/oh-yeah.html | 4 ++-- .../tests/output/basic/override/index.html | 2 +- .../pages/this-is-a-test-hidden-page.html | 2 +- .../basic/pages/this-is-a-test-page.html | 2 +- .../tests/output/basic/second-article-fr.html | 4 ++-- .../tests/output/basic/second-article.html | 4 ++-- pelican/tests/output/basic/tag/bar.html | 10 ++++---- pelican/tests/output/basic/tag/baz.html | 4 ++-- pelican/tests/output/basic/tag/foo.html | 8 +++---- pelican/tests/output/basic/tag/foobar.html | 4 ++-- pelican/tests/output/basic/tag/yeah.html | 2 +- .../output/basic/this-is-a-super-article.html | 6 ++--- pelican/tests/output/basic/unbelievable.html | 4 ++-- .../custom/a-markdown-powered-article.html | 4 ++-- pelican/tests/output/custom/article-1.html | 4 ++-- pelican/tests/output/custom/article-2.html | 4 ++-- pelican/tests/output/custom/article-3.html | 4 ++-- .../custom/author/alexis-metaireau.html | 10 ++++---- .../custom/author/alexis-metaireau2.html | 12 +++++----- .../custom/author/alexis-metaireau3.html | 4 ++-- pelican/tests/output/custom/category/bar.html | 2 +- .../tests/output/custom/category/cat1.html | 8 +++---- .../tests/output/custom/category/misc.html | 8 +++---- .../tests/output/custom/category/yeah.html | 4 ++-- .../output/custom/drafts/a-draft-article.html | 2 +- .../custom/filename_metadata-example.html | 2 +- pelican/tests/output/custom/index.html | 8 +++---- pelican/tests/output/custom/index2.html | 12 +++++----- pelican/tests/output/custom/index3.html | 4 ++-- pelican/tests/output/custom/oh-yeah-fr.html | 2 +- pelican/tests/output/custom/oh-yeah.html | 2 +- .../output/custom/second-article-fr.html | 2 +- .../tests/output/custom/second-article.html | 2 +- pelican/tests/output/custom/tag/bar.html | 10 ++++---- pelican/tests/output/custom/tag/baz.html | 2 +- pelican/tests/output/custom/tag/foo.html | 8 +++---- pelican/tests/output/custom/tag/foobar.html | 4 ++-- pelican/tests/output/custom/tag/yeah.html | 2 +- .../custom/this-is-a-super-article.html | 6 ++--- pelican/tests/output/custom/unbelievable.html | 2 +- .../author/alexis-metaireau.html | 8 +++---- .../author/alexis-metaireau2.html | 12 +++++----- .../author/alexis-metaireau3.html | 4 ++-- .../output/custom_locale/category/bar.html | 2 +- .../output/custom_locale/category/cat1.html | 8 +++---- .../output/custom_locale/category/misc.html | 8 +++---- .../output/custom_locale/category/yeah.html | 6 ++--- .../custom_locale/drafts/a-draft-article.html | 2 +- pelican/tests/output/custom_locale/index.html | 8 +++---- .../tests/output/custom_locale/index2.html | 12 +++++----- .../tests/output/custom_locale/index3.html | 4 ++-- .../output/custom_locale/oh-yeah-fr.html | 2 +- .../02/this-is-a-super-article/index.html | 6 ++--- .../2010/octobre/15/unbelievable/index.html | 2 +- .../posts/2010/octobre/20/oh-yeah/index.html | 2 +- .../20/a-markdown-powered-article/index.html | 2 +- .../2011/février/17/article-1/index.html | 2 +- .../2011/février/17/article-2/index.html | 2 +- .../2011/février/17/article-3/index.html | 2 +- .../2012/février/29/second-article/index.html | 2 +- .../30/filename_metadata-example/index.html | 2 +- .../custom_locale/second-article-fr.html | 2 +- .../tests/output/custom_locale/tag/bar.html | 10 ++++---- .../tests/output/custom_locale/tag/baz.html | 2 +- .../tests/output/custom_locale/tag/foo.html | 8 +++---- .../output/custom_locale/tag/foobar.html | 6 ++--- .../tests/output/custom_locale/tag/yeah.html | 2 +- pelican/tests/test_utils.py | 3 +++ pelican/utils.py | 4 +++- 92 files changed, 259 insertions(+), 247 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 297a537b..7a9a8bc0 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -16,7 +16,7 @@ from pelican import signals from pelican.settings import DEFAULT_CONFIG from pelican.utils import (slugify, truncate_html_words, memoized, strftime, python_2_unicode_compatible, deprecated_attribute, - path_to_url, SafeDatetime) + path_to_url, set_date_tzinfo, SafeDatetime) # Import these so that they're avalaible when you import from pelican.contents. from pelican.urlwrappers import (URLWrapper, Author, Category, Tag) # NOQA @@ -117,9 +117,16 @@ class Content(object): locale.setlocale(locale.LC_ALL, locale_string) self.date_format = self.date_format[1] + # manage timezone + default_timezone = settings.get('TIMEZONE', 'UTC') + timezone = getattr(self, 'timezone', default_timezone) + if hasattr(self, 'date'): + self.date = set_date_tzinfo(self.date, timezone) self.locale_date = strftime(self.date, self.date_format) + if hasattr(self, 'modified'): + self.modified = set_date_tzinfo(self.modified, timezone) self.locale_modified = strftime(self.modified, self.date_format) # manage status diff --git a/pelican/tests/output/basic/a-markdown-powered-article.html b/pelican/tests/output/basic/a-markdown-powered-article.html index 40c96766..5fcc42a9 100644 --- a/pelican/tests/output/basic/a-markdown-powered-article.html +++ b/pelican/tests/output/basic/a-markdown-powered-article.html @@ -34,7 +34,7 @@
- + Published: Wed 20 April 2011 @@ -66,4 +66,4 @@
- \ No newline at end of file + diff --git a/pelican/tests/output/basic/archives.html b/pelican/tests/output/basic/archives.html index e32c92ba..f8f1a67f 100644 --- a/pelican/tests/output/basic/archives.html +++ b/pelican/tests/output/basic/archives.html @@ -69,4 +69,4 @@
- \ No newline at end of file + diff --git a/pelican/tests/output/basic/article-1.html b/pelican/tests/output/basic/article-1.html index 15a1496d..4ea7f4e3 100644 --- a/pelican/tests/output/basic/article-1.html +++ b/pelican/tests/output/basic/article-1.html @@ -34,7 +34,7 @@
- + Published: Thu 17 February 2011 @@ -65,4 +65,4 @@
- \ No newline at end of file + diff --git a/pelican/tests/output/basic/article-2.html b/pelican/tests/output/basic/article-2.html index c4b09e35..45130e55 100644 --- a/pelican/tests/output/basic/article-2.html +++ b/pelican/tests/output/basic/article-2.html @@ -34,7 +34,7 @@
- + Published: Thu 17 February 2011 @@ -65,4 +65,4 @@
- \ No newline at end of file + diff --git a/pelican/tests/output/basic/article-3.html b/pelican/tests/output/basic/article-3.html index 1c07e5e2..8603430f 100644 --- a/pelican/tests/output/basic/article-3.html +++ b/pelican/tests/output/basic/article-3.html @@ -34,7 +34,7 @@
- + Published: Thu 17 February 2011 @@ -65,4 +65,4 @@
- \ No newline at end of file + diff --git a/pelican/tests/output/basic/author/alexis-metaireau.html b/pelican/tests/output/basic/author/alexis-metaireau.html index d1dc6da6..11d54185 100644 --- a/pelican/tests/output/basic/author/alexis-metaireau.html +++ b/pelican/tests/output/basic/author/alexis-metaireau.html @@ -29,11 +29,11 @@

This is a super article !

- + Published: Thu 02 December 2010
- + Updated: Sun 17 November 2013 @@ -69,7 +69,7 @@
- + Published: Wed 20 October 2010 @@ -109,4 +109,4 @@ YEAH !

- \ No newline at end of file + diff --git a/pelican/tests/output/basic/categories.html b/pelican/tests/output/basic/categories.html index fa2fc012..55e955c8 100644 --- a/pelican/tests/output/basic/categories.html +++ b/pelican/tests/output/basic/categories.html @@ -49,4 +49,4 @@
- \ No newline at end of file + diff --git a/pelican/tests/output/basic/category/bar.html b/pelican/tests/output/basic/category/bar.html index 763cfe77..18e434cb 100644 --- a/pelican/tests/output/basic/category/bar.html +++ b/pelican/tests/output/basic/category/bar.html @@ -29,7 +29,7 @@

Oh yeah !

{% endif %} -

In {{ article.category }}. {% if PDF_PROCESSOR %}get the pdf{% endif %}

+

In {{ article.category }}.

{% include 'taglist.html' %} {% import 'translations.html' as translations with context %} {{ translations.translations_for(article) }} diff --git a/pelican/themes/notmyidea/templates/page.html b/pelican/themes/notmyidea/templates/page.html index 5ac50b66..0d8283fe 100644 --- a/pelican/themes/notmyidea/templates/page.html +++ b/pelican/themes/notmyidea/templates/page.html @@ -5,8 +5,6 @@

{{ page.title }}

{% import 'translations.html' as translations with context %} {{ translations.translations_for(page) }} - {% if PDF_PROCESSOR %}get - the pdf{% endif %} {{ page.content }} {% endblock %} diff --git a/pelican/themes/notmyidea/templates/taglist.html b/pelican/themes/notmyidea/templates/taglist.html index 1e0b95a7..58f35576 100644 --- a/pelican/themes/notmyidea/templates/taglist.html +++ b/pelican/themes/notmyidea/templates/taglist.html @@ -1,2 +1 @@ {% if article.tags %}

tags: {% for tag in article.tags %}{{ tag | escape }} {% endfor %}

{% endif %} -{% if PDF_PROCESSOR %}

get the pdf

{% endif %} From 87d86d724c6ae01adf1488b2f65dd0ff1e48ca46 Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Sat, 28 Feb 2015 15:33:54 -0800 Subject: [PATCH 0136/1173] Change phrasing and formatting of README Made a few changes to the README to emphasize Pelican's position as a general-purpose static site generator, and not just a blogging tool. See #1645 for more details. --- README.rst | 59 +++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/README.rst b/README.rst index 564cc77c..a5643514 100644 --- a/README.rst +++ b/README.rst @@ -3,57 +3,58 @@ Pelican |build-status| |coverage-status| Pelican is a static site generator, written in Python_. -* Write your weblog entries directly with your editor of choice (vim!) - in reStructuredText_ or Markdown_ -* Includes a simple CLI tool to (re)generate the weblog -* Easy to interface with DVCSes and web hooks -* Completely static output is easy to host anywhere +* Write content in reStructuredText_ or Markdown_ using your editor of choice. +* Includes a simple command line tool to (re)generate site files. +* Easy to interface with version control systems and web hooks. +* Completely static output is simple to host anywhere. + Features -------- Pelican currently supports: -* Blog articles and pages -* Comments, via an external service (Disqus). (Please note that while - useful, Disqus is an external service, and thus the comment data will be - somewhat outside of your control and potentially subject to data loss.) -* Theming support (themes are created using Jinja2_ templates) -* PDF generation of the articles/pages (optional) +* Blog articles and static pages +* Integration with external services (ex. Google Analytics and Disqus) +* Site themes (created using Jinja2_ templates) * Publication of articles in multiple languages -* Atom/RSS feeds -* Code syntax highlighting -* Import from WordPress, Dotclear, or RSS feeds -* Integration with external tools: Twitter, Google Analytics, etc. (optional) -* Fast rebuild times thanks to content caching and selective output writing. +* Generation of Atom and RSS feeds +* Syntax highlighting via Pygments_ +* Importing existing content from WordPress, Dotclear, and more services +* Fast rebuild times due to content caching and selective output writing -Have a look at the `Pelican documentation`_ for more information. +Check out `Pelican's documentation`_ for further information. -Why the name "Pelican"? ------------------------ - -"Pelican" is an anagram for *calepin*, which means "notebook" in French. ;) - -Source code ------------ - -You can access the source code at: https://github.com/getpelican/pelican - -If you feel hackish, have a look at the explanation of `Pelican's internals`_. How to get help, contribute, or provide feedback ------------------------------------------------ See our `contribution submission and feedback guidelines `_. + +Source code +----------- + +Pelican's source code is `hosted on GitHub`_. If you're feeling hackish, +take a look at `Pelican's internals`_. + + +Why the name "Pelican"? +----------------------- + +"Pelican" is an anagram of *calepin*, which means "notebook" in French. + + .. Links .. _Python: http://www.python.org/ .. _reStructuredText: http://docutils.sourceforge.net/rst.html .. _Markdown: http://daringfireball.net/projects/markdown/ .. _Jinja2: http://jinja.pocoo.org/ -.. _`Pelican documentation`: http://docs.getpelican.com/ +.. _Pygments: http://pygments.org/ +.. _`Pelican's documentation`: http://docs.getpelican.com/ .. _`Pelican's internals`: http://docs.getpelican.com/en/latest/internals.html +.. _`hosted on GitHub`: https://github.com/getpelican/pelican .. |build-status| image:: https://img.shields.io/travis/getpelican/pelican/master.svg :target: https://travis-ci.org/getpelican/pelican From e35ca1d6ff4fd9a31b6dd60b2bb345c2fee0828e Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 5 Mar 2015 12:04:39 -0800 Subject: [PATCH 0137/1173] Minor improvements to README --- README.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index a5643514..0bb3bcc8 100644 --- a/README.rst +++ b/README.rst @@ -3,10 +3,10 @@ Pelican |build-status| |coverage-status| Pelican is a static site generator, written in Python_. -* Write content in reStructuredText_ or Markdown_ using your editor of choice. -* Includes a simple command line tool to (re)generate site files. -* Easy to interface with version control systems and web hooks. -* Completely static output is simple to host anywhere. +* Write content in reStructuredText_ or Markdown_ using your editor of choice +* Includes a simple command line tool to (re)generate site files +* Easy to interface with version control systems and web hooks +* Completely static output is simple to host anywhere Features @@ -14,13 +14,13 @@ Features Pelican currently supports: -* Blog articles and static pages -* Integration with external services (ex. Google Analytics and Disqus) +* Chronological content (e.g., articles, blog posts) as well as static pages +* Integration with external services (e.g., Google Analytics and Disqus) * Site themes (created using Jinja2_ templates) * Publication of articles in multiple languages * Generation of Atom and RSS feeds * Syntax highlighting via Pygments_ -* Importing existing content from WordPress, Dotclear, and more services +* Importing existing content from WordPress, Dotclear, and other services * Fast rebuild times due to content caching and selective output writing Check out `Pelican's documentation`_ for further information. @@ -35,7 +35,7 @@ See our `contribution submission and feedback guidelines `_. Source code ----------- -Pelican's source code is `hosted on GitHub`_. If you're feeling hackish, +Pelican's source code is `hosted on GitHub`_. If you feel like hacking, take a look at `Pelican's internals`_. From 3ea45420152a8465db33fd4a67ac88f1c1426df5 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Tue, 17 Feb 2015 20:05:00 -0500 Subject: [PATCH 0138/1173] Make sure Content uses URLWrappers --- pelican/contents.py | 14 +++----------- pelican/readers.py | 4 ++++ pelican/tests/test_contents.py | 11 ++++++----- pelican/tests/test_generators.py | 32 ++++++++++++++++++++++++++++++++ pelican/tests/test_paginator.py | 4 ++-- 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 074c28be..90121316 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -53,7 +53,7 @@ class Content(object): self._context = context self.translations = [] - local_metadata = dict(settings['DEFAULT_METADATA']) + local_metadata = dict() local_metadata.update(metadata) # set metadata as attributes @@ -166,21 +166,13 @@ class Content(object): """Returns the URL, formatted with the proper values""" metadata = copy.copy(self.metadata) path = self.metadata.get('path', self.get_relative_source_path()) - default_category = self.settings['DEFAULT_CATEGORY'] - slug_substitutions = self.settings.get('SLUG_SUBSTITUTIONS', ()) metadata.update({ 'path': path_to_url(path), 'slug': getattr(self, 'slug', ''), 'lang': getattr(self, 'lang', 'en'), 'date': getattr(self, 'date', SafeDatetime.now()), - 'author': slugify( - getattr(self, 'author', ''), - slug_substitutions - ), - 'category': slugify( - getattr(self, 'category', default_category), - slug_substitutions - ) + 'author': self.author.slug if hasattr(self, 'author') else '', + 'category': self.category.slug if hasattr(self, 'category') else '' }) return metadata diff --git a/pelican/readers.py b/pelican/readers.py index 731fb5da..a9b71bed 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -537,6 +537,10 @@ def find_empty_alt(content, path): def default_metadata(settings=None, process=None): metadata = {} if settings: + for name, value in dict(settings.get('DEFAULT_METADATA', {})).items(): + if process: + value = process(name, value) + metadata[name] = value if 'DEFAULT_CATEGORY' in settings: value = settings['DEFAULT_CATEGORY'] if process: diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 4b692e29..004d512e 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -8,7 +8,7 @@ import os.path from pelican.tests.support import unittest, get_settings -from pelican.contents import Page, Article, Static, URLWrapper +from pelican.contents import Page, Article, Static, URLWrapper, Author, Category from pelican.settings import DEFAULT_CONFIG from pelican.utils import path_to_url, truncate_html_words, SafeDatetime, posix_join from pelican.signals import content_object_init @@ -33,7 +33,7 @@ class TestPage(unittest.TestCase): 'metadata': { 'summary': TEST_SUMMARY, 'title': 'foo bar', - 'author': 'Blogger', + 'author': Author('Blogger', DEFAULT_CONFIG), }, 'source_path': '/path/to/file/foo.ext' } @@ -374,7 +374,8 @@ class TestPage(unittest.TestCase): content = Page(**args) assert content.authors == [content.author] args['metadata'].pop('author') - args['metadata']['authors'] = ['First Author', 'Second Author'] + args['metadata']['authors'] = [Author('First Author', DEFAULT_CONFIG), + Author('Second Author', DEFAULT_CONFIG)] content = Page(**args) assert content.authors assert content.author == content.authors[0] @@ -396,8 +397,8 @@ class TestArticle(TestPage): settings['ARTICLE_URL'] = '{author}/{category}/{slug}/' settings['ARTICLE_SAVE_AS'] = '{author}/{category}/{slug}/index.html' article_kwargs = self._copy_page_kwargs() - article_kwargs['metadata']['author'] = "O'Brien" - article_kwargs['metadata']['category'] = 'C# & stuff' + article_kwargs['metadata']['author'] = Author("O'Brien", settings) + article_kwargs['metadata']['category'] = Category('C# & stuff', settings) article_kwargs['metadata']['title'] = 'fnord' article_kwargs['settings'] = settings article = Article(**article_kwargs) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 9f38c002..acf767f2 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -413,6 +413,38 @@ class TestArticlesGenerator(unittest.TestCase): generator.generate_context() generator.readers.read_file.assert_called_count == orig_call_count + def test_standard_metadata_in_default_metadata(self): + settings = get_settings(filenames={}) + settings['CACHE_CONTENT'] = False + settings['DEFAULT_CATEGORY'] = 'Default' + settings['DEFAULT_DATE'] = (1970, 1, 1) + settings['DEFAULT_METADATA'] = (('author', 'Blogger'), + # category will be ignored in favor of + # DEFAULT_CATEGORY + ('category', 'Random'), + ('tags', 'general, untagged')) + generator = ArticlesGenerator( + context=settings.copy(), settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.generate_context() + + authors = sorted([author.name for author, _ in generator.authors]) + authors_expected = sorted(['Alexis Métaireau', 'Blogger', + 'First Author', 'Second Author']) + self.assertEqual(authors, authors_expected) + + categories = sorted([category.name + for category, _ in generator.categories]) + categories_expected = [ + sorted(['Default', 'TestCategory', 'yeah', 'test', '指導書']), + sorted(['Default', 'TestCategory', 'Yeah', 'test', '指導書'])] + self.assertIn(categories, categories_expected) + + tags = sorted([tag.name for tag in generator.tags]) + tags_expected = sorted(['bar', 'foo', 'foobar', 'general', 'untagged', + 'パイソン', 'マック']) + self.assertEqual(tags, tags_expected) + class TestPageGenerator(unittest.TestCase): # Note: Every time you want to test for a new field; Make sure the test diff --git a/pelican/tests/test_paginator.py b/pelican/tests/test_paginator.py index 5494fda8..002d9e07 100644 --- a/pelican/tests/test_paginator.py +++ b/pelican/tests/test_paginator.py @@ -5,7 +5,7 @@ import locale from pelican.tests.support import unittest, get_settings from pelican.paginator import Paginator -from pelican.contents import Article +from pelican.contents import Article, Author from pelican.settings import DEFAULT_CONFIG from jinja2.utils import generate_lorem_ipsum @@ -26,7 +26,6 @@ class TestPage(unittest.TestCase): 'metadata': { 'summary': TEST_SUMMARY, 'title': 'foo bar', - 'author': 'Blogger', }, 'source_path': '/path/to/file/foo.ext' } @@ -49,6 +48,7 @@ class TestPage(unittest.TestCase): key=lambda r: r[0], ) + self.page_kwargs['metadata']['author'] = Author('Blogger', settings) object_list = [Article(**self.page_kwargs), Article(**self.page_kwargs)] paginator = Paginator('foobar.foo', object_list, settings) page = paginator.page(1) From 4e896c427ddef6b9a19088dfc653f7b8c15f5c08 Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Fri, 6 Mar 2015 23:51:26 -0800 Subject: [PATCH 0139/1173] Standardize formatting of .travis.yml Use 2 spaces for indentation. --- .travis.yml | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index a052252b..f5a7f04f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,25 +1,24 @@ language: python python: - - "2.7" - - "3.3" - - "3.4" + - "2.7" + - "3.3" + - "3.4" addons: apt_packages: - pandoc before_install: - - sudo apt-get update -qq - - sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8 + - sudo apt-get update -qq + - sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8 install: - - pip install . - - pip install -r dev_requirements.txt - - pip install nose-cov + - pip install . + - pip install -r dev_requirements.txt + - pip install nose-cov script: nosetests -sv --with-coverage --cover-package=pelican pelican after_success: - # Report coverage results to coveralls.io - pip install coveralls - coveralls notifications: - irc: - channels: - - "irc.freenode.org#pelican" - on_success: change + irc: + channels: + - "irc.freenode.org#pelican" + on_success: change From ffe71d324d4812925b2eeddbc52b66f5ebbf3801 Mon Sep 17 00:00:00 2001 From: robertlagrant Date: Fri, 13 Mar 2015 13:42:56 +0200 Subject: [PATCH 0140/1173] Change docs wording on cache regen for #1630 --- docs/settings.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 11444d2e..9fb97883 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -847,13 +847,11 @@ can be invoked by passing the ``--archive`` flag). The cache files are Python pickles, so they may not be readable by different versions of Python as the pickle format often changes. If -such an error is encountered, the cache files have to be rebuilt by -removing them and re-running Pelican, or by using the Pelican -command-line option ``--ignore-cache``. The cache files also have to -be rebuilt when changing the ``GZIP_CACHE`` setting for cache file -reading to work properly. +such an error is encountered, it is caught and the cache file is +rebuilt automatically in the new format. The cache files will also be +rebuilt after the ``GZIP_CACHE`` setting has been changed. -The ``--ignore-cache`` command-line option is also useful when the +The ``--ignore-cache`` command-line option is useful when the whole cache needs to be regenerated, such as when making modifications to the settings file that will affect the cached content, or just for debugging purposes. When Pelican runs in autoreload mode, modification From 0f7f328206b4b3eb085335aa86c620150143ee6e Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Fri, 13 Mar 2015 23:01:31 -0700 Subject: [PATCH 0141/1173] Remove a couple of unused imports As reported by Pyflakes. --- pelican/contents.py | 2 +- pelican/writers.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 074c28be..a680c411 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, print_function import six -from six.moves.urllib.parse import (unquote, urlparse, urlunparse) +from six.moves.urllib.parse import urlparse, urlunparse import copy import locale diff --git a/pelican/writers.py b/pelican/writers.py index bf32e272..e90a0004 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -3,7 +3,6 @@ from __future__ import with_statement, unicode_literals, print_function import six import os -import locale import logging if not six.PY3: From ef737c22393174571fe17a6175eb98465c6ec246 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sat, 14 Mar 2015 13:36:51 -0400 Subject: [PATCH 0142/1173] Use `--relative-urls` only if it is specified Otherwise, `RELATIVE_URLS` in the config file is ignored and `RELATIVE_URLS` is set to `False` if `--relative-urls` is not specified. Fixes an issue introduced in #1592 --- pelican/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 3013744d..056c45ef 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -321,7 +321,8 @@ def get_config(args): config['CACHE_PATH'] = args.cache_path if args.selected_paths: config['WRITE_SELECTED'] = args.selected_paths.split(',') - config['RELATIVE_URLS'] = args.relative_paths + if args.relative_paths: + config['RELATIVE_URLS'] = args.relative_paths config['DEBUG'] = args.verbosity == logging.DEBUG # argparse returns bytes in Py2. There is no definite answer as to which From 875c4a5e05d818c776be3019506921b863b13dc0 Mon Sep 17 00:00:00 2001 From: Anton Antonov Date: Tue, 17 Mar 2015 01:23:29 +0200 Subject: [PATCH 0143/1173] Nitpick Content decorators A bit more readable this way. --- pelican/contents.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 96466a94..005d045c 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -90,7 +90,7 @@ class Content(object): self.in_default_lang = (self.lang == default_lang) - # create the slug if not existing, generate slug according to + # create the slug if not existing, generate slug according to # setting of SLUG_ATTRIBUTE if not hasattr(self, 'slug'): if settings['SLUGIFY_SOURCE'] == 'title' and hasattr(self, 'title'): @@ -308,8 +308,13 @@ class Content(object): """Dummy function""" pass - url = property(functools.partial(get_url_setting, key='url')) - save_as = property(functools.partial(get_url_setting, key='save_as')) + @property + def url(self): + return self.get_url_setting('url') + + @property + def save_as(self): + return self.get_url_setting('save_as') def _get_template(self): if hasattr(self, 'template') and self.template is not None: From db2e5174502787e447d3df32298cf950c6c894ae Mon Sep 17 00:00:00 2001 From: Forest Date: Mon, 29 Sep 2014 22:51:13 -0700 Subject: [PATCH 0144/1173] Ignore empty metadata. Fixes #1469. Fixes #1398. Some metadata values cause problems when empty. For example, a markdown file containing a Slug: line with no additional text causing Pelican to produce a file named ".html" instead of generating a proper file name. Others, like those created by a PATH_METADATA regex, must be preserved even if empty, so things like PAGE_URL="filename{customvalue}.html" will always work. Essentially, we want to discard empty metadata that we know will be useless or problematic. This is better than raising an exception because (a) it allows users to deliberately keep empty metadata in their source files for filling in later, and (b) users shouldn't be forced to fix empty metadata created by blog migration tools (see #1398). The metadata processors are the ideal place to do this, because they know the type of data they are handling and whether an empty value is wanted. Unfortunately, they can't discard items, and neither can process_metadata(), because their return values are always saved by calling code. We can't safely change the calling code, because some of it lives in custom reader classes out in the field, and we don't want to break those working systems. Discarding empty values at the time of use isn't good enough, because that still allows useless empty values in a source file to override configured defaults. My solution: - When processing a list of values, a metadata processor will omit any unwanted empty ones from the list it returns. - When processing an entirely unwanted value, it will return something easily identifiable that will pass through the reader code. - When collecting the processed metadata, read_file() will filter out items identified as unwanted. These metadata are affected by this change: author, authors, category, slug, status, tags. I also removed a bit of now-superfluous code from generators.py that was discarding empty authors at the time of use. --- pelican/generators.py | 4 +--- pelican/readers.py | 48 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index f0a6d264..75bd6b2a 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -544,10 +544,8 @@ class ArticlesGenerator(CachingGenerator): if hasattr(article, 'tags'): for tag in article.tags: self.tags[tag].append(article) - # ignore blank authors as well as undefined for author in getattr(article, 'authors', []): - if author.name != '': - self.authors[author].append(article) + self.authors[author].append(article) # sort the articles by date self.articles.sort(key=attrgetter('date'), reverse=True) self.dates = list(self.articles) diff --git a/pelican/readers.py b/pelican/readers.py index a9b71bed..3656cd96 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -28,16 +28,44 @@ from pelican.contents import Page, Category, Tag, Author from pelican.utils import get_date, pelican_open, FileStampDataCacher, SafeDatetime, posixize_path +def strip_split(text, sep=','): + """Return a list of stripped, non-empty substrings, delimited by sep.""" + items = [x.strip() for x in text.split(sep)] + return [x for x in items if x] + + +# Metadata processors have no way to discard an unwanted value, so we have +# them return this value instead to signal that it should be discarded later. +# This means that _filter_discardable_metadata() must be called on processed +# metadata dicts before use, to remove the items with the special value. +_DISCARD = object() + + +def _process_if_nonempty(processor, name, settings): + """Removes extra whitespace from name and applies a metadata processor. + If name is empty or all whitespace, returns _DISCARD instead. + """ + name = name.strip() + return processor(name, settings) if name else _DISCARD + + METADATA_PROCESSORS = { - 'tags': lambda x, y: [Tag(tag, y) for tag in x.split(',')], + 'tags': lambda x, y: [Tag(tag, y) for tag in strip_split(x)] or _DISCARD, 'date': lambda x, y: get_date(x.replace('_', ' ')), 'modified': lambda x, y: get_date(x), - 'status': lambda x, y: x.strip(), - 'category': Category, - 'author': Author, - 'authors': lambda x, y: [Author(author.strip(), y) for author in x.split(',')], + 'status': lambda x, y: x.strip() or _DISCARD, + 'category': lambda x, y: _process_if_nonempty(Category, x, y), + 'author': lambda x, y: _process_if_nonempty(Author, x, y), + 'authors': lambda x, y: [Author(a, y) for a in strip_split(x)] or _DISCARD, + 'slug': lambda x, y: x.strip() or _DISCARD, } + +def _filter_discardable_metadata(metadata): + """Return a copy of a dict, minus any items marked as discardable.""" + return {name: val for name, val in metadata.items() if val is not _DISCARD} + + logger = logging.getLogger(__name__) class BaseReader(object): @@ -447,14 +475,14 @@ class Readers(FileStampDataCacher): reader = self.readers[fmt] - metadata = default_metadata( - settings=self.settings, process=reader.process_metadata) + metadata = _filter_discardable_metadata(default_metadata( + settings=self.settings, process=reader.process_metadata)) metadata.update(path_metadata( full_path=path, source_path=source_path, settings=self.settings)) - metadata.update(parse_path_metadata( + metadata.update(_filter_discardable_metadata(parse_path_metadata( source_path=source_path, settings=self.settings, - process=reader.process_metadata)) + process=reader.process_metadata))) reader_name = reader.__class__.__name__ metadata['reader'] = reader_name.replace('Reader', '').lower() @@ -462,7 +490,7 @@ class Readers(FileStampDataCacher): if content is None: content, reader_metadata = reader.read(path) self.cache_data(path, (content, reader_metadata)) - metadata.update(reader_metadata) + metadata.update(_filter_discardable_metadata(reader_metadata)) if content: # find images with empty alt From 7ad649e3b79c20d9cbb783148e659824c46fbf8c Mon Sep 17 00:00:00 2001 From: Forest Date: Fri, 31 Oct 2014 23:05:19 -0700 Subject: [PATCH 0145/1173] Guess mime type with python-magic if available. Fixes #1514. --- pelican/server.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pelican/server.py b/pelican/server.py index 0a8dc1b6..60252e1f 100644 --- a/pelican/server.py +++ b/pelican/server.py @@ -12,6 +12,11 @@ try: except ImportError: import socketserver # NOQA +try: + from magic import from_file as magic_from_file +except ImportError: + magic_from_file = None + PORT = len(sys.argv) in (2, 3) and int(sys.argv[1]) or 8000 SERVER = len(sys.argv) == 3 and sys.argv[2] or "" SUFFIXES = ['', '.html', '/index.html'] @@ -39,6 +44,18 @@ class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler): logging.warning("Unable to find `%s` or variations.", self.original_path) + def guess_type(self, path): + """Guess at the mime type for the specified file. + """ + mimetype = srvmod.SimpleHTTPRequestHandler.guess_type(self, path) + + # If the default guess is too generic, try the python-magic library + if mimetype == 'application/octet-stream' and magic_from_file: + mimetype = magic_from_file(path, mime=True) + + return mimetype + + Handler = ComplexHTTPRequestHandler socketserver.TCPServer.allow_reuse_address = True From 7b4ceb29744b3a00f39925047a0dafef0176214f Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Fri, 3 Apr 2015 18:58:52 -0400 Subject: [PATCH 0146/1173] Make `pelican.server` importable and use it in `fab serve` `fab serve` and `make devserver` use different HTTP Handlers and as a result they behave differently. This makes sure `fab serve` also uses the Handler defined in `pelican.server` in order to get rid of the inconsistency. --- pelican/server.py | 46 ++++++++++++--------------- pelican/tools/templates/fabfile.py.in | 5 +-- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/pelican/server.py b/pelican/server.py index 60252e1f..f58ac085 100644 --- a/pelican/server.py +++ b/pelican/server.py @@ -2,30 +2,22 @@ from __future__ import print_function import os import sys import logging -try: - import SimpleHTTPServer as srvmod -except ImportError: - import http.server as srvmod # NOQA -try: - import SocketServer as socketserver -except ImportError: - import socketserver # NOQA +from six.moves import SimpleHTTPServer as srvmod +from six.moves import socketserver try: from magic import from_file as magic_from_file except ImportError: magic_from_file = None -PORT = len(sys.argv) in (2, 3) and int(sys.argv[1]) or 8000 -SERVER = len(sys.argv) == 3 and sys.argv[2] or "" -SUFFIXES = ['', '.html', '/index.html'] - class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler): + SUFFIXES = ['', '.html', '/index.html'] + def do_GET(self): # Try to detect file by applying various suffixes - for suffix in SUFFIXES: + for suffix in self.SUFFIXES: if not hasattr(self, 'original_path'): self.original_path = self.path @@ -56,19 +48,21 @@ class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler): return mimetype -Handler = ComplexHTTPRequestHandler +if __name__ == '__main__': + PORT = len(sys.argv) in (2, 3) and int(sys.argv[1]) or 8000 + SERVER = len(sys.argv) == 3 and sys.argv[2] or "" -socketserver.TCPServer.allow_reuse_address = True -try: - httpd = socketserver.TCPServer((SERVER, PORT), Handler) -except OSError as e: - logging.error("Could not listen on port %s, server %s.", PORT, SERVER) - sys.exit(getattr(e, 'exitcode', 1)) + socketserver.TCPServer.allow_reuse_address = True + try: + httpd = socketserver.TCPServer((SERVER, PORT), ComplexHTTPRequestHandler) + except OSError as e: + logging.error("Could not listen on port %s, server %s.", PORT, SERVER) + sys.exit(getattr(e, 'exitcode', 1)) -logging.info("Serving at port %s, server %s.", PORT, SERVER) -try: - httpd.serve_forever() -except KeyboardInterrupt as e: - logging.info("Shutting down server.") - httpd.socket.close() + logging.info("Serving at port %s, server %s.", PORT, SERVER) + try: + httpd.serve_forever() + except KeyboardInterrupt as e: + logging.info("Shutting down server.") + httpd.socket.close() diff --git a/pelican/tools/templates/fabfile.py.in b/pelican/tools/templates/fabfile.py.in index a8ab586b..bcc66f6a 100644 --- a/pelican/tools/templates/fabfile.py.in +++ b/pelican/tools/templates/fabfile.py.in @@ -3,9 +3,10 @@ import fabric.contrib.project as project import os import shutil import sys -import SimpleHTTPServer import SocketServer +from pelican.server import ComplexHTTPRequestHandler + # Local path configuration (can be absolute or relative to fabfile) env.deploy_path = 'output' DEPLOY_PATH = env.deploy_path @@ -51,7 +52,7 @@ def serve(): class AddressReuseTCPServer(SocketServer.TCPServer): allow_reuse_address = True - server = AddressReuseTCPServer(('', PORT), SimpleHTTPServer.SimpleHTTPRequestHandler) + server = AddressReuseTCPServer(('', PORT), ComplexHTTPRequestHandler) sys.stderr.write('Serving on port {0} ...\n'.format(PORT)) server.serve_forever() From 946e95172fa85e7d5967226f3dbc723f099f94c9 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sat, 4 Apr 2015 15:17:59 -0400 Subject: [PATCH 0147/1173] Use pelican.server in Quickstart docs --- docs/quickstart.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 122d65b5..c4f5a897 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -61,10 +61,10 @@ Preview your site ----------------- Open a new terminal session and run the following commands to switch to your -``output`` directory and launch Python's built-in web server:: +``output`` directory and launch Pelican's web server:: cd ~/projects/yoursite/output - python -m SimpleHTTPServer # -m http.server if you use python3 + python -m pelican.server Preview your site by navigating to http://localhost:8000/ in your browser. From 5ca808ed598679dfef4574892c79e1d262632051 Mon Sep 17 00:00:00 2001 From: winlu Date: Mon, 6 Apr 2015 09:49:01 +0200 Subject: [PATCH 0148/1173] fix broken internal metadata link --- docs/settings.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 9fb97883..73837181 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -470,14 +470,14 @@ your resume, and a contact page — you could have:: Path metadata ============= -Not all metadata needs to be `embedded in source file itself`__. For -example, blog posts are often named following a ``YYYY-MM-DD-SLUG.rst`` -pattern, or nested into ``YYYY/MM/DD-SLUG`` directories. To extract -metadata from the filename or path, set ``FILENAME_METADATA`` or -``PATH_METADATA`` to regular expressions that use Python's `group name -notation`_ ``(?P…)``. If you want to attach additional metadata -but don't want to encode it in the path, you can set -``EXTRA_PATH_METADATA``: +Not all metadata needs to be :ref:`embedded in source file itself +`. For example, blog posts are often named +following a ``YYYY-MM-DD-SLUG.rst`` pattern, or nested into +``YYYY/MM/DD-SLUG`` directories. To extract metadata from the +filename or path, set ``FILENAME_METADATA`` or ``PATH_METADATA`` to +regular expressions that use Python's `group name notation`_ ``(?P…)``. +If you want to attach additional metadata but don't want to encode +it in the path, you can set ``EXTRA_PATH_METADATA``: .. parsed-literal:: @@ -506,7 +506,6 @@ particular file: 'static/robots.txt': {'path': 'robots.txt'}, } -__ internal_metadata__ .. _group name notation: http://docs.python.org/3/library/re.html#regular-expression-syntax From 2e590d0e866a8679b99978819a75edded3c1bcef Mon Sep 17 00:00:00 2001 From: winlu Date: Mon, 6 Apr 2015 14:59:06 +0200 Subject: [PATCH 0149/1173] maintain test_readers * move all metadata tests to use a single function call (assertDictHasSubset) * add tests for assertDictHasSubset * correct some tests that iterated over metadata instead of expected metadata, resulting in metadata that was expected to be there but was not * correct resulting broken tests * add additional tests for EXTRA_PATH_METADATA * make MdReaderTest fail if Markdown is not available instead of skipping each method individually --- pelican/tests/test_readers.py | 181 ++++++++++++++++++++++++++-------- 1 file changed, 140 insertions(+), 41 deletions(-) diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index cb657673..d390fb48 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -22,6 +22,52 @@ class ReaderTest(unittest.TestCase): r = readers.Readers(settings=get_settings(**kwargs)) return r.read_file(base_path=CONTENT_PATH, path=path) + def assertDictHasSubset(self, dictionary, subset): + for key, value in subset.items(): + if key in dictionary: + real_value = dictionary.get(key) + self.assertEqual( + value, + real_value, + str('Expected %r to have value %r, but was %r') + % (key, value, real_value)) + else: + self.fail( + str('Expected %r to have value %r, but was not in Dict') + % (key, value)) + +class TestAssertDictHasSubset(ReaderTest): + def setUp(self): + self.dictionary = { + 'key-a' : 'val-a', + 'key-b' : 'val-b'} + + def tearDown(self): + self.dictionary = None + + def test_subset(self): + self.assertDictHasSubset(self.dictionary, {'key-a':'val-a'}) + + def test_equal(self): + self.assertDictHasSubset(self.dictionary, self.dictionary) + + def test_fail_not_set(self): + self.assertRaisesRegexp( + AssertionError, + 'Expected.*key-c.*to have value.*val-c.*but was not in Dict', + self.assertDictHasSubset, + self.dictionary, + {'key-c':'val-c'} + ) + + def test_fail_wrong_val(self): + self.assertRaisesRegexp( + AssertionError, + 'Expected .*key-a.* to have value .*val-b.* but was .*val-a.*', + self.assertDictHasSubset, + self.dictionary, + {'key-a':'val-b'} + ) class DefaultReaderTest(ReaderTest): @@ -48,8 +94,7 @@ class RstReaderTest(ReaderTest): 'custom_field': 'http://notmyidea.org', } - for key, value in expected.items(): - self.assertEqual(value, page.metadata[key], key) + self.assertDictHasSubset(page.metadata, expected) def test_article_with_filename_metadata(self): page = self.read_file( @@ -61,8 +106,7 @@ class RstReaderTest(ReaderTest): 'title': 'Rst with filename metadata', 'reader': 'rst', } - for key, value in page.metadata.items(): - self.assertEqual(value, expected[key], key) + self.assertDictHasSubset(page.metadata, expected) page = self.read_file( path='2012-11-29_rst_w_filename_meta#foo-bar.rst', @@ -74,13 +118,12 @@ class RstReaderTest(ReaderTest): 'date': SafeDatetime(2012, 11, 29), 'reader': 'rst', } - for key, value in page.metadata.items(): - self.assertEqual(value, expected[key], key) + self.assertDictHasSubset(page.metadata, expected) page = self.read_file( path='2012-11-29_rst_w_filename_meta#foo-bar.rst', FILENAME_METADATA=( - '(?P\d{4}-\d{2}-\d{2})_' + '(?P\d{4}-\d{2}-\d{2})' '_(?P.*)' '#(?P.*)-(?P.*)')) expected = { @@ -88,12 +131,11 @@ class RstReaderTest(ReaderTest): 'author': 'Alexis Métaireau', 'title': 'Rst with filename metadata', 'date': SafeDatetime(2012, 11, 29), - 'slug': 'article_with_filename_metadata', + 'slug': 'rst_w_filename_meta', 'mymeta': 'foo', 'reader': 'rst', } - for key, value in page.metadata.items(): - self.assertEqual(value, expected[key], key) + self.assertDictHasSubset(page.metadata, expected) def test_article_metadata_key_lowercase(self): # Keys of metadata should be lowercase. @@ -105,6 +147,81 @@ class RstReaderTest(ReaderTest): self.assertEqual('Yeah', metadata.get('category'), 'Value keeps case.') + def test_article_extra_path_metadata(self): + input_with_metadata = '2012-11-29_rst_w_filename_meta#foo-bar.rst' + page_metadata = self.read_file( + path=input_with_metadata, + FILENAME_METADATA=( + '(?P\d{4}-\d{2}-\d{2})' + '_(?P.*)' + '#(?P.*)-(?P.*)' + ), + EXTRA_PATH_METADATA={ + input_with_metadata: { + 'key-1a': 'value-1a', + 'key-1b': 'value-1b' + } + } + ) + expected_metadata = { + 'category': 'yeah', + 'author' : 'Alexis Métaireau', + 'title': 'Rst with filename metadata', + 'date': SafeDatetime(2012, 11, 29), + 'slug': 'rst_w_filename_meta', + 'mymeta': 'foo', + 'reader': 'rst', + 'key-1a': 'value-1a', + 'key-1b': 'value-1b' + } + self.assertDictHasSubset(page_metadata.metadata, expected_metadata) + + input_file_path_without_metadata = 'article.rst' + page_without_metadata = self.read_file( + path=input_file_path_without_metadata, + EXTRA_PATH_METADATA={ + input_file_path_without_metadata: { + 'author': 'Charlès Overwrite'} + } + ) + expected_without_metadata = { + 'category' : 'misc', + 'author' : 'Charlès Overwrite', + 'title' : 'Article title', + 'reader' : 'rst', + } + self.assertDictHasSubset( + page_without_metadata.metadata, + expected_without_metadata) + + def test_article_extra_path_metadata_dont_overwrite(self): + #EXTRA_PATH_METADATA['author'] should get ignored + #since we don't overwrite already set values + input_file_path = '2012-11-29_rst_w_filename_meta#foo-bar.rst' + page = self.read_file( + path=input_file_path, + FILENAME_METADATA=( + '(?P\d{4}-\d{2}-\d{2})' + '_(?P.*)' + '#(?P.*)-(?P.*)'), + EXTRA_PATH_METADATA={ + input_file_path: { + 'author': 'Charlès Overwrite', + 'key-1b': 'value-1b'} + } + ) + expected = { + 'category': 'yeah', + 'author' : 'Alexis Métaireau', + 'title': 'Rst with filename metadata', + 'date': SafeDatetime(2012, 11, 29), + 'slug': 'rst_w_filename_meta', + 'mymeta': 'foo', + 'reader': 'rst', + 'key-1b': 'value-1b' + } + self.assertDictHasSubset(page.metadata, expected) + def test_typogrify(self): # if nothing is specified in the settings, the content should be # unmodified @@ -205,13 +322,11 @@ class RstReaderTest(ReaderTest): 'authors': ['First Author', 'Second Author'] } - for key, value in expected.items(): - self.assertEqual(value, page.metadata[key], key) - + self.assertDictHasSubset(page.metadata, expected) +@unittest.skipUnless(readers.Markdown, "markdown isn't installed") class MdReaderTest(ReaderTest): - @unittest.skipUnless(readers.Markdown, "markdown isn't installed") def test_article_with_metadata(self): reader = readers.MarkdownReader(settings=get_settings()) content, metadata = reader.read( @@ -224,8 +339,7 @@ class MdReaderTest(ReaderTest): 'modified': SafeDatetime(2010, 12, 2, 10, 20), 'tags': ['foo', 'bar', 'foobar'], } - for key, value in metadata.items(): - self.assertEqual(value, expected[key], key) + self.assertDictHasSubset(metadata, expected) content, metadata = reader.read( _path('article_with_markdown_and_nonascii_summary.md')) @@ -238,10 +352,8 @@ class MdReaderTest(ReaderTest): 'tags': ['パイソン', 'マック'], 'slug': 'python-virtualenv-on-mac-osx-mountain-lion-10.8', } - for key, value in metadata.items(): - self.assertEqual(value, expected[key], key) + self.assertDictHasSubset(metadata, expected) - @unittest.skipUnless(readers.Markdown, "markdown isn't installed") def test_article_with_footnote(self): reader = readers.MarkdownReader(settings=get_settings()) content, metadata = reader.read( @@ -271,7 +383,6 @@ class MdReaderTest(ReaderTest): 'should be supported.

'), 'date': SafeDatetime(2012, 10, 31), 'modified': SafeDatetime(2012, 11, 1), - 'slug': 'article-with-markdown-containing-footnotes', 'multiline': [ 'Line Metadata should be handle properly.', 'See syntax of Meta-Data extension of Python Markdown package:', @@ -282,10 +393,8 @@ class MdReaderTest(ReaderTest): ] } self.assertEqual(content, expected_content) - for key, value in metadata.items(): - self.assertEqual(value, expected_metadata[key], key) + self.assertDictHasSubset(metadata, expected_metadata) - @unittest.skipUnless(readers.Markdown, "markdown isn't installed") def test_article_with_file_extensions(self): reader = readers.MarkdownReader(settings=get_settings()) # test to ensure the md file extension is being processed by the @@ -322,7 +431,6 @@ class MdReaderTest(ReaderTest): " the mdown extension.

") self.assertEqual(content, expected) - @unittest.skipUnless(readers.Markdown, "markdown isn't installed") def test_article_with_markdown_markup_extension(self): # test to ensure the markdown markup extension is being processed as # expected @@ -342,7 +450,6 @@ class MdReaderTest(ReaderTest): self.assertEqual(page.content, expected) - @unittest.skipUnless(readers.Markdown, "markdown isn't installed") def test_article_with_filename_metadata(self): page = self.read_file( path='2012-11-30_md_w_filename_meta#foo-bar.md', @@ -351,8 +458,7 @@ class MdReaderTest(ReaderTest): 'category': 'yeah', 'author': 'Alexis Métaireau', } - for key, value in expected.items(): - self.assertEqual(value, page.metadata[key], key) + self.assertDictHasSubset(page.metadata, expected) page = self.read_file( path='2012-11-30_md_w_filename_meta#foo-bar.md', @@ -362,8 +468,7 @@ class MdReaderTest(ReaderTest): 'author': 'Alexis Métaireau', 'date': SafeDatetime(2012, 11, 30), } - for key, value in expected.items(): - self.assertEqual(value, page.metadata[key], key) + self.assertDictHasSubset(page.metadata, expected) page = self.read_file( path='2012-11-30_md_w_filename_meta#foo-bar.md', @@ -378,8 +483,7 @@ class MdReaderTest(ReaderTest): 'slug': 'md_w_filename_meta', 'mymeta': 'foo', } - for key, value in expected.items(): - self.assertEqual(value, page.metadata[key], key) + self.assertDictHasSubset(page.metadata, expected) class HTMLReaderTest(ReaderTest): @@ -397,8 +501,7 @@ class HTMLReaderTest(ReaderTest): 'tags': ['foo', 'bar', 'foobar'], } - for key, value in expected.items(): - self.assertEqual(value, page.metadata[key], key) + self.assertDictHasSubset(page.metadata, expected) def test_article_with_metadata(self): page = self.read_file(path='article_with_metadata.html') @@ -412,8 +515,7 @@ class HTMLReaderTest(ReaderTest): 'custom_field': 'http://notmyidea.org', } - for key, value in expected.items(): - self.assertEqual(value, page.metadata[key], key) + self.assertDictHasSubset(page.metadata, expected) def test_article_with_multiple_authors(self): page = self.read_file(path='article_with_multiple_authors.html') @@ -421,8 +523,7 @@ class HTMLReaderTest(ReaderTest): 'authors': ['First Author', 'Second Author'] } - for key, value in expected.items(): - self.assertEqual(value, page.metadata[key], key) + self.assertDictHasSubset(page.metadata, expected) def test_article_with_metadata_and_contents_attrib(self): page = self.read_file(path='article_with_metadata_and_contents.html') @@ -435,8 +536,7 @@ class HTMLReaderTest(ReaderTest): 'tags': ['foo', 'bar', 'foobar'], 'custom_field': 'http://notmyidea.org', } - for key, value in expected.items(): - self.assertEqual(value, page.metadata[key], key) + self.assertDictHasSubset(page.metadata, expected) def test_article_with_null_attributes(self): page = self.read_file(path='article_with_null_attributes.html') @@ -460,5 +560,4 @@ class HTMLReaderTest(ReaderTest): 'title': 'Article with Nonconformant HTML meta tags', } - for key, value in expected.items(): - self.assertEqual(value, page.metadata[key], key) + self.assertDictHasSubset(page.metadata, expected) From 0f7938ccb71e34cd020026095fcb086b99c79856 Mon Sep 17 00:00:00 2001 From: Alberto Scotto Date: Wed, 8 Apr 2015 03:32:48 +0200 Subject: [PATCH 0150/1173] fix the meta tags added in #1028 The attribute 'contents' should be 'content'. See http://www.w3schools.com/tags/att_meta_content.asp --- pelican/themes/simple/templates/article.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pelican/themes/simple/templates/article.html b/pelican/themes/simple/templates/article.html index e81261c5..d558183d 100644 --- a/pelican/themes/simple/templates/article.html +++ b/pelican/themes/simple/templates/article.html @@ -2,15 +2,15 @@ {% block head %} {{ super() }} {% for keyword in article.keywords %} - + {% endfor %} {% for description in article.description %} - + {% endfor %} {% for tag in article.tags %} - + {% endfor %} {% endblock %} From adc5c4b572195d12cea1df4d0f8daa49ec2e168f Mon Sep 17 00:00:00 2001 From: winlu Date: Fri, 10 Apr 2015 09:02:12 +0200 Subject: [PATCH 0151/1173] fix broken refs --- docs/themes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/themes.rst b/docs/themes.rst index fd4ec8f9..7f2693ff 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -367,7 +367,7 @@ source_path Full system path of the article source file. status The article status, can be any of 'published' or 'draft'. summary Rendered summary content. -tags List of :ref:`Tag ` +tags List of :ref:`Tag ` objects. template Template name to use for rendering. title Title of the article. @@ -422,7 +422,7 @@ source_path Full system path of the page source file. status The page status, can be any of 'published' or 'draft'. summary Rendered summary content. -tags List of :ref:`Tag ` +tags List of :ref:`Tag ` objects. template Template name to use for rendering. title Title of the page. From bf7d113caaa9d9fcc860e6687a63589ea00aea10 Mon Sep 17 00:00:00 2001 From: winlu Date: Sat, 11 Apr 2015 22:45:31 +0200 Subject: [PATCH 0152/1173] add skips for tests relying on dev_requirements modules --- pelican/tests/test_generators.py | 16 +++++++++++++++- pelican/tests/test_importer.py | 7 +++++++ pelican/tests/test_rstdirectives.py | 10 ++++++++-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index acf767f2..4fb70826 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -6,7 +6,10 @@ from codecs import open try: from unittest.mock import MagicMock except ImportError: - from mock import MagicMock + try: + from mock import MagicMock + except ImportError: + MagicMock = False from shutil import rmtree from tempfile import mkdtemp @@ -112,6 +115,7 @@ class TestArticlesGenerator(unittest.TestCase): return [[article.title, article.status, article.category.name, article.template] for article in articles] + @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_generate_feeds(self): settings = get_settings() settings['CACHE_PATH'] = self.temp_cache @@ -215,6 +219,7 @@ class TestArticlesGenerator(unittest.TestCase): categories_expected = ['default', 'yeah', 'test', 'zhi-dao-shu'] self.assertEqual(sorted(categories), sorted(categories_expected)) + @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_direct_templates_save_as_default(self): settings = get_settings(filenames={}) @@ -228,6 +233,7 @@ class TestArticlesGenerator(unittest.TestCase): generator.get_template("archives"), settings, blog=True, paginated={}, page_name='archives') + @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_direct_templates_save_as_modified(self): settings = get_settings() @@ -244,6 +250,7 @@ class TestArticlesGenerator(unittest.TestCase): blog=True, paginated={}, page_name='archives/index') + @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_direct_templates_save_as_false(self): settings = get_settings() @@ -268,6 +275,7 @@ class TestArticlesGenerator(unittest.TestCase): self.assertIn(custom_template, self.articles) self.assertIn(standard_template, self.articles) + @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_period_in_timeperiod_archive(self): """ Test that the context of a generated period_archive is passed @@ -347,6 +355,7 @@ class TestArticlesGenerator(unittest.TestCase): authors_expected = ['alexis-metaireau', 'first-author', 'second-author'] self.assertEqual(sorted(authors), sorted(authors_expected)) + @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_article_object_caching(self): """Test Article objects caching at the generator level""" settings = get_settings(filenames={}) @@ -367,6 +376,7 @@ class TestArticlesGenerator(unittest.TestCase): generator.generate_context() generator.readers.read_file.assert_called_count == 0 + @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_reader_content_caching(self): """Test raw content caching at the reader level""" settings = get_settings(filenames={}) @@ -389,6 +399,7 @@ class TestArticlesGenerator(unittest.TestCase): for reader in readers.values(): reader.read.assert_called_count == 0 + @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_ignore_cache(self): """Test that all the articles are read again when not loading cache @@ -492,6 +503,7 @@ class TestPageGenerator(unittest.TestCase): self.assertEqual(sorted(pages_expected), sorted(pages)) self.assertEqual(sorted(hidden_pages_expected), sorted(hidden_pages)) + @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_page_object_caching(self): """Test Page objects caching at the generator level""" settings = get_settings(filenames={}) @@ -512,6 +524,7 @@ class TestPageGenerator(unittest.TestCase): generator.generate_context() generator.readers.read_file.assert_called_count == 0 + @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_reader_content_caching(self): """Test raw content caching at the reader level""" settings = get_settings(filenames={}) @@ -534,6 +547,7 @@ class TestPageGenerator(unittest.TestCase): for reader in readers.values(): reader.read.assert_called_count == 0 + @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_ignore_cache(self): """Test that all the pages are read again when not loading cache diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index c108bc52..4ace5ccc 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -26,6 +26,12 @@ try: except ImportError: BeautifulSoup = False # NOQA +try: + import bs4.builder._lxml as LXML +except ImportError: + LXML = False + + @skipIfNoExecutable(['pandoc', '--version']) @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') @@ -302,6 +308,7 @@ class TestBuildHeader(unittest.TestCase): @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') +@unittest.skipUnless(LXML, 'Needs lxml module') class TestWordpressXMLAttachements(unittest.TestCase): def setUp(self): self.old_locale = locale.setlocale(locale.LC_ALL) diff --git a/pelican/tests/test_rstdirectives.py b/pelican/tests/test_rstdirectives.py index ae863b30..7c5f8adf 100644 --- a/pelican/tests/test_rstdirectives.py +++ b/pelican/tests/test_rstdirectives.py @@ -1,9 +1,15 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, print_function - -from mock import Mock +try: + from unittest.mock import Mock +except ImportError: + try: + from mock import Mock + except ImportError: + Mock = False from pelican.tests.support import unittest +@unittest.skipUnless(Mock, 'Needs Mock module') class Test_abbr_role(unittest.TestCase): def call_it(self, text): from pelican.rstdirectives import abbr_role From f864418b9e1af0a980052dc4c7d51519c57e3649 Mon Sep 17 00:00:00 2001 From: "Daniel M. Drucker" Date: Mon, 13 Apr 2015 11:12:52 -0400 Subject: [PATCH 0153/1173] make the quickstart work The quickstart was worded confusingly - it said "from your project directory", which implied doing a `cd ..` down to the `projects` dir, which would cause `pelican content` to fail. In fact, you need to be in the `yoursite` directory, which is the directory that has the `content` directory in it. --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index c4f5a897..ef5256c2 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -49,7 +49,7 @@ Given that this example article is in Markdown format, save it as Generate your site ------------------ -From your project directory, run the ``pelican`` command to generate your site:: +From your site directory, run the ``pelican`` command to generate your site:: pelican content From a45a917766089c9e733cf417f942a4b9c651a154 Mon Sep 17 00:00:00 2001 From: winlu Date: Sat, 11 Apr 2015 15:02:53 +0200 Subject: [PATCH 0154/1173] test docs via travis * move build environment into tox * add new environment installing sphinx and testing for doc errors * reorganize dependency installs for easier management --- .travis.yml | 16 +++++++--------- dev_requirements.txt | 3 --- tox.ini | 29 +++++++++++++++++++++++------ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index f5a7f04f..cc124ea4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,11 @@ language: python python: - "2.7" - - "3.3" - - "3.4" +env: + - TOX_ENV=docs + - TOX_ENV=py27 + - TOX_ENV=py33 + - TOX_ENV=py34 addons: apt_packages: - pandoc @@ -10,13 +13,8 @@ before_install: - sudo apt-get update -qq - sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8 install: - - pip install . - - pip install -r dev_requirements.txt - - pip install nose-cov -script: nosetests -sv --with-coverage --cover-package=pelican pelican -after_success: - - pip install coveralls - - coveralls + - pip install tox +script: tox -e $TOX_ENV notifications: irc: channels: diff --git a/dev_requirements.txt b/dev_requirements.txt index a7c10719..028cbebd 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -10,6 +10,3 @@ typogrify # To perform release bumpr==0.2.0 wheel - -# For docs theme -sphinx_rtd_theme diff --git a/tox.ini b/tox.ini index 5dd36c36..11b3cae8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,28 @@ -# This tests the unified codebase (py27, py33) of Pelican. -# depends on some external libraries that aren't released yet. - [tox] -envlist = py27,py33,py34 +envlist = py27,py33,py34,docs [testenv] -commands = - python -m unittest discover +basepython = + py27: python2.7 + py33: python3.3 + py34: python3.4 +usedevelop=True deps = -rdev_requirements.txt + nose + nose-cov + coveralls + +commands = + {envpython} --version + nosetests -sv --with-coverage --cover-package=pelican pelican + coveralls + +[testenv:docs] +basepython = python2.7 +deps = + sphinx + sphinx_rtd_theme +changedir = docs +commands = + sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html From 2e84e30ad38175819ef97103f9720693c0218fc7 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Tue, 14 Apr 2015 15:19:16 -0400 Subject: [PATCH 0155/1173] Fixes #1695: replace with list comprehension for py3 compatibility --- pelican/tools/pelican_quickstart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 74633630..2f1129dc 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -162,7 +162,7 @@ def ask(question, answer=str_compat, default=None, l=None): def ask_timezone(question, default, tzurl): """Prompt for time zone and validate input""" - lower_tz = map(str.lower, pytz.all_timezones) + lower_tz = [tz.lower() for tz in pytz.all_timezones] while True: r = ask(question, str_compat, default) r = r.strip().replace(' ', '_').lower() From 90a283bc44da299e0860dcc27516bfefe327fd56 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Thu, 16 Apr 2015 19:05:15 -0400 Subject: [PATCH 0156/1173] Fixes #1686: posixize paths in context['filenames'] to fix windows issues --- pelican/generators.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index 75bd6b2a..0ce6439d 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -22,7 +22,8 @@ from jinja2 import (Environment, FileSystemLoader, PrefixLoader, ChoiceLoader, from pelican.contents import Article, Draft, Page, Static, is_valid_content from pelican.readers import Readers from pelican.utils import (copy, process_translations, mkdir_p, DateFormatter, - FileStampDataCacher, python_2_unicode_compatible) + FileStampDataCacher, python_2_unicode_compatible, + posixize_path) from pelican import signals @@ -160,7 +161,7 @@ class Generator(object): (For example, one that was missing mandatory metadata.) The path argument is expected to be relative to self.path. """ - self.context['filenames'][os.path.normpath(path)] = None + self.context['filenames'][posixize_path(os.path.normpath(path))] = None def _is_potential_source_path(self, path): """Return True if path was supposed to be used as a source file. @@ -168,7 +169,7 @@ class Generator(object): before this method is called, even if they failed to process.) The path argument is expected to be relative to self.path. """ - return os.path.normpath(path) in self.context['filenames'] + return posixize_path(os.path.normpath(path)) in self.context['filenames'] def _update_context(self, items): """Update the context with the given items from the currrent From dceea3aabe36e206d014e2540f86572dcb7c8859 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Thu, 16 Apr 2015 19:16:23 -0400 Subject: [PATCH 0157/1173] Change URLWrapper.slug to a property Main goal is to delay `slugify` call until `slug` is needed. `slugify` can be expensive depending on the input string (see #1172). Changing it to a property gives plugins time to place custom `slug`s before `name` is unnecessarily slugified. With this change, default behavior is same except the time slugification happens. But if you set custom slug, `slugify` won't be used at all. So, this is a partial solution to #1172. The rest, setting a custom slug, would best be handled by a plugin. --- pelican/urlwrappers.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py index 06cfa2ab..60bc6a3a 100644 --- a/pelican/urlwrappers.py +++ b/pelican/urlwrappers.py @@ -13,12 +13,10 @@ logger = logging.getLogger(__name__) @functools.total_ordering class URLWrapper(object): def __init__(self, name, settings): - # next 2 lines are redundant with the setter of the name property - # but are here for clarity self.settings = settings self._name = name - self.slug = slugify(name, self.settings.get('SLUG_SUBSTITUTIONS', ())) - self.name = name + self._slug = None + self._slug_from_name = True @property def name(self): @@ -27,11 +25,28 @@ class URLWrapper(object): @name.setter def name(self, name): self._name = name - self.slug = slugify(name, self.settings.get('SLUG_SUBSTITUTIONS', ())) + # if slug wasn't explicitly set, it needs to be regenerated from name + # so, changing name should reset slug for slugification + if self._slug_from_name: + self._slug = None + + @property + def slug(self): + if self._slug is None: + self._slug = slugify(self.name, + self.settings.get('SLUG_SUBSTITUTIONS', ())) + return self._slug + + @slug.setter + def slug(self, slug): + # if slug is expliticly set, changing name won't alter slug + self._slug_from_name = False + self._slug = slug def as_dict(self): d = self.__dict__ d['name'] = self.name + d['slug'] = self.slug return d def __hash__(self): From 9dd4080fe6162849aec0946cc8406b04da764157 Mon Sep 17 00:00:00 2001 From: winlu Date: Mon, 20 Apr 2015 12:16:05 +0200 Subject: [PATCH 0158/1173] remove tag_cloud from core --- docs/settings.rst | 47 ------------------------------------------- pelican/generators.py | 32 ++--------------------------- pelican/settings.py | 2 -- 3 files changed, 2 insertions(+), 79 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 73837181..2eec1e50 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -620,53 +620,6 @@ This would cause the first page to be written to ``page/{number}`` directories. -Tag cloud -========= - -If you want to generate a tag cloud with all your tags, you can do so using the -following settings. - -================================================ ===================================================== -Setting name (followed by default value) What does it do? -================================================ ===================================================== -``TAG_CLOUD_STEPS = 4`` Count of different font sizes in the tag - cloud. -``TAG_CLOUD_MAX_ITEMS = 100`` Maximum number of tags in the cloud. -================================================ ===================================================== - -The default theme does not include a tag cloud, but it is pretty easy to add one:: - -
    - {% for tag in tag_cloud %} -
  • {{ tag.0 }}
  • - {% endfor %} -
- -You should then also define CSS styles with appropriate classes (tag-1 to tag-N, -where N matches ``TAG_CLOUD_STEPS``), tag-1 being the most frequent, and -define a ``ul.tagcloud`` class with appropriate list-style to create the cloud. -For example:: - - ul.tagcloud { - list-style: none; - padding: 0; - } - - ul.tagcloud li { - display: inline-block; - } - - li.tag-1 { - font-size: 150%; - } - - li.tag-2 { - font-size: 120%; - } - - ... - - Translations ============ diff --git a/pelican/generators.py b/pelican/generators.py index 75bd6b2a..672850d3 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -3,8 +3,6 @@ from __future__ import unicode_literals, print_function import os import six -import math -import random import logging import shutil import fnmatch @@ -14,7 +12,7 @@ from codecs import open from collections import defaultdict from functools import partial from itertools import chain, groupby -from operator import attrgetter, itemgetter +from operator import attrgetter from jinja2 import (Environment, FileSystemLoader, PrefixLoader, ChoiceLoader, BaseLoader, TemplateNotFound) @@ -552,32 +550,6 @@ class ArticlesGenerator(CachingGenerator): self.dates.sort(key=attrgetter('date'), reverse=self.context['NEWEST_FIRST_ARCHIVES']) - # create tag cloud - tag_cloud = defaultdict(int) - for article in self.articles: - for tag in getattr(article, 'tags', []): - tag_cloud[tag] += 1 - - tag_cloud = sorted(tag_cloud.items(), key=itemgetter(1), reverse=True) - tag_cloud = tag_cloud[:self.settings.get('TAG_CLOUD_MAX_ITEMS')] - - tags = list(map(itemgetter(1), tag_cloud)) - if tags: - max_count = max(tags) - steps = self.settings.get('TAG_CLOUD_STEPS') - - # calculate word sizes - self.tag_cloud = [ - ( - tag, - int(math.floor(steps - (steps - 1) * math.log(count) - / (math.log(max_count)or 1))) - ) - for tag, count in tag_cloud - ] - # put words in chaos - random.shuffle(self.tag_cloud) - # and generate the output :) # order the categories per name @@ -589,7 +561,7 @@ class ArticlesGenerator(CachingGenerator): self.authors.sort() self._update_context(('articles', 'dates', 'tags', 'categories', - 'tag_cloud', 'authors', 'related_posts')) + 'authors', 'related_posts')) self.save_cache() self.readers.save_cache() signals.article_generator_finalized.send(self) diff --git a/pelican/settings.py b/pelican/settings.py index 0d69c08e..0c54e89b 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -93,8 +93,6 @@ DEFAULT_CONFIG = { 'DAY_ARCHIVE_SAVE_AS': '', 'RELATIVE_URLS': False, 'DEFAULT_LANG': 'en', - 'TAG_CLOUD_STEPS': 4, - 'TAG_CLOUD_MAX_ITEMS': 100, 'DIRECT_TEMPLATES': ['index', 'tags', 'categories', 'authors', 'archives'], 'EXTRA_TEMPLATES_PATHS': [], 'PAGINATED_DIRECT_TEMPLATES': ['index'], From 3effe464c6a893c61d8a399f77531e14d348afd1 Mon Sep 17 00:00:00 2001 From: winlu Date: Mon, 20 Apr 2015 16:36:18 +0200 Subject: [PATCH 0159/1173] add faq entry about tag-cloud outsourcing --- docs/faq.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/faq.rst b/docs/faq.rst index 86f12462..ff473624 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -241,3 +241,12 @@ purposes. This can be achieved by explicitly specifying only the filenames of those articles in ``ARTICLE_PATHS``. A list of such filenames could be found using a command similar to ``cd content; find -name '*.md' | head -n 10``. + +My tag-cloud is missing/broken since I upgraded Pelican +======================================================= + +In an ongoing effort to steamline Pelican, `tag_cloud` generation has been +moved out of the pelican core and into a separate `plugin +`_. +See the :ref:`plugins` documentation further information about the +Pelican plugin system. From d6ebf772e3fa90910714e06cd9f5529cc2956df0 Mon Sep 17 00:00:00 2001 From: Matthew Scott Date: Thu, 23 Apr 2015 13:30:24 -0500 Subject: [PATCH 0160/1173] Allow `--path` even when using a virtualenv project --- pelican/tools/pelican_quickstart.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 2f1129dc..58da4649 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -59,6 +59,13 @@ if six.PY3: else: str_compat = unicode +# Create a 'marked' default path, to determine if someone has supplied +# a path on the command-line. +class _DEFAULT_PATH_TYPE(str_compat): + is_default_path = True + +_DEFAULT_PATH = _DEFAULT_PATH_TYPE(os.curdir) + def decoding_strings(f): def wrapper(*args, **kwargs): out = f(*args, **kwargs) @@ -178,7 +185,7 @@ def main(): parser = argparse.ArgumentParser( description="A kickstarter for Pelican", formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('-p', '--path', default=os.curdir, + parser.add_argument('-p', '--path', default=_DEFAULT_PATH, help="The path to generate the blog into") parser.add_argument('-t', '--title', metavar="title", help='Set the title of the website') @@ -200,7 +207,8 @@ needed by Pelican. project = os.path.join( os.environ.get('VIRTUAL_ENV', os.curdir), '.project') - if os.path.isfile(project): + no_path_was_specified = hasattr(args.path, 'is_default_path') + if os.path.isfile(project) and no_path_was_specified: CONF['basedir'] = open(project, 'r').read().rstrip("\n") print('Using project associated with current virtual environment.' 'Will save to:\n%s\n' % CONF['basedir']) From df5495303257d0809139f418db0e9b2607917830 Mon Sep 17 00:00:00 2001 From: Ingmar Steen Date: Wed, 29 Apr 2015 15:08:46 +0200 Subject: [PATCH 0161/1173] Fix intra-site links to drafts. Overwrite an article's source path registration when it is 'upgraded' to a draft. Fixes #865 --- pelican/generators.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pelican/generators.py b/pelican/generators.py index 75bd6b2a..e95ec3ab 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -525,6 +525,7 @@ class ArticlesGenerator(CachingGenerator): preread_sender=self, context_signal=signals.article_generator_context, context_sender=self) + self.add_source_path(draft) all_drafts.append(draft) else: logger.error("Unknown status '%s' for file %s, skipping it.", From 264d75b9e04c4aa517a0bfd14473dfe78ee06d2f Mon Sep 17 00:00:00 2001 From: Forest Date: Fri, 8 May 2015 13:39:59 -0700 Subject: [PATCH 0162/1173] Clarify STATIC_EXCLUDE_SOURCES documentatoin. --- docs/settings.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/settings.rst b/docs/settings.rst index 73837181..a18e24bf 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -159,7 +159,12 @@ Setting name (followed by default value, if any) Pelican's default settings include the "images" directory here. ``STATIC_EXCLUDES = []`` A list of directories to exclude when looking for static files. ``STATIC_EXCLUDE_SOURCES = True`` If set to False, content source files will not be skipped when - copying files found in ``STATIC_PATHS``. + copying files found in ``STATIC_PATHS``. This setting is for + backward compatibility with Pelican releases before version 3.5. + It has no effect unless ``STATIC_PATHS`` contains a directory that + is also in ``ARTICLE_PATHS`` or ``PAGE_PATHS``. If you are trying + to publish your site's source files, consider using the + ``OUTPUT_SOURCES` setting instead. ``TIMEZONE`` The timezone used in the date information, to generate Atom and RSS feeds. See the *Timezone* section below for more info. From 2f048d365222edd98119dfc9366e202008e5b57f Mon Sep 17 00:00:00 2001 From: Forest Date: Sat, 9 May 2015 21:20:07 -0700 Subject: [PATCH 0163/1173] Add backtick missing from my last docs commit. --- docs/settings.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.rst b/docs/settings.rst index a18e24bf..330ec2a6 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -164,7 +164,7 @@ Setting name (followed by default value, if any) It has no effect unless ``STATIC_PATHS`` contains a directory that is also in ``ARTICLE_PATHS`` or ``PAGE_PATHS``. If you are trying to publish your site's source files, consider using the - ``OUTPUT_SOURCES` setting instead. + ``OUTPUT_SOURCES`` setting instead. ``TIMEZONE`` The timezone used in the date information, to generate Atom and RSS feeds. See the *Timezone* section below for more info. From ab2dc71d34f0b3387afafb9251b0f6c0cae3998e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Tue, 12 May 2015 02:55:45 +0300 Subject: [PATCH 0164/1173] Improve internal error reporting. Fixes #1717. --- pelican/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 056c45ef..12da111a 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -444,7 +444,7 @@ def main(): except Exception as e: if (args.verbosity == logging.DEBUG): - logger.critical(e.args) + logger.critical('Internal failure: %r', e, exc_info=True) raise logger.warning( 'Caught exception "%s". Reloading.', e) From e10ae8a187876b7954d38d1968c0c8f8b6606e22 Mon Sep 17 00:00:00 2001 From: derwinlu Date: Wed, 13 May 2015 21:08:39 +0200 Subject: [PATCH 0165/1173] make tox 2 compatible --- .travis.yml | 2 +- tox.ini | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cc124ea4..5d7d4a5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ before_install: - sudo apt-get update -qq - sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8 install: - - pip install tox + - pip install tox==2.0.1 script: tox -e $TOX_ENV notifications: irc: diff --git a/tox.ini b/tox.ini index 11b3cae8..775241b8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,12 @@ [tox] -envlist = py27,py33,py34,docs +envlist = py{27,33,34},docs [testenv] basepython = py27: python2.7 py33: python3.3 py34: python3.4 +passenv = * usedevelop=True deps = -rdev_requirements.txt From f8f89a84761ede361fd97b15d96f0a7cdbef2c31 Mon Sep 17 00:00:00 2001 From: Alex Chan Date: Sun, 24 May 2015 12:58:00 +0100 Subject: [PATCH 0166/1173] Fix spelling mistakes in docs --- docs/pelican-themes.rst | 6 +++--- docs/themes.rst | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/pelican-themes.rst b/docs/pelican-themes.rst index 7090c648..e18ebadf 100644 --- a/docs/pelican-themes.rst +++ b/docs/pelican-themes.rst @@ -130,11 +130,11 @@ This is useful for theme development: $ sudo pelican-themes -s ~/Dev/Python/pelican-themes/two-column $ pelican ~/Blog/content -o /tmp/out -t two-column $ firefox /tmp/out/index.html - $ vim ~/Dev/Pelican/pelican-themes/two-coumn/static/css/main.css + $ vim ~/Dev/Pelican/pelican-themes/two-column/static/css/main.css $ pelican ~/Blog/content -o /tmp/out -t two-column - $ cp /tmp/bg.png ~/Dev/Pelican/pelican-themes/two-coumn/static/img/bg.png + $ cp /tmp/bg.png ~/Dev/Pelican/pelican-themes/two-column/static/img/bg.png $ pelican ~/Blog/content -o /tmp/out -t two-column - $ vim ~/Dev/Pelican/pelican-themes/two-coumn/templates/index.html + $ vim ~/Dev/Pelican/pelican-themes/two-column/templates/index.html $ pelican ~/Blog/content -o /tmp/out -t two-column diff --git a/docs/themes.rst b/docs/themes.rst index 7f2693ff..d64683bb 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -359,7 +359,7 @@ default_template Default template name. in_default_lang Boolean representing if the article is written in the default language. lang Language of the article. -locale_date Date formated by the `date_format`. +locale_date Date formatted by the `date_format`. metadata Article header metadata `dict`. save_as Location to save the article page. slug Page slug. @@ -414,7 +414,7 @@ default_template Default template name. in_default_lang Boolean representing if the article is written in the default language. lang Language of the article. -locale_date Date formated by the `date_format`. +locale_date Date formatted by the `date_format`. metadata Page header metadata `dict`. save_as Location to save the page. slug Page slug. From 641b3ffa71801eb5e4f5b818e925021f2287f699 Mon Sep 17 00:00:00 2001 From: Alex Chan Date: Sun, 24 May 2015 13:04:35 +0100 Subject: [PATCH 0167/1173] Fix capitalisation of WordPress --- docs/changelog.rst | 2 +- pelican/tools/pelican_import.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 52367cec..4e297562 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -205,7 +205,7 @@ Release history 2.5 (2010-11-20) ================== -* Import from Wordpress +* Import from WordPress * Added some new themes (martyalchin / wide-notmyidea) * First bug report! * Linkedin support diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index a6424ace..92e8c919 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -101,12 +101,12 @@ def decode_wp_content(content, br=True): return content def get_items(xml): - """Opens a wordpress xml file and returns a list of items""" + """Opens a WordPress xml file and returns a list of items""" try: from bs4 import BeautifulSoup except ImportError: error = ('Missing dependency ' - '"BeautifulSoup4" and "lxml" required to import Wordpress XML files.') + '"BeautifulSoup4" and "lxml" required to import WordPress XML files.') sys.exit(error) with open(xml, encoding='utf-8') as infile: xmlfile = infile.read() @@ -586,7 +586,7 @@ def get_attachments(xml): return attachedposts def download_attachments(output_path, urls): - """Downloads wordpress attachments and returns a list of paths to + """Downloads WordPress attachments and returns a list of paths to attachments that can be associated with a post (relative path to output directory). Files that fail to download, will not be added to posts""" locations = [] From 77ebd05fce1176f0f4c9cd2e48ffa47fb30d1901 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Sun, 24 May 2015 16:59:23 +0100 Subject: [PATCH 0168/1173] Actually stopping server When running `make devserver` and then running `make stopserver` it doesn't stop the server. This patch fixes that. --- pelican/tools/templates/Makefile.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in index 5e3635c3..b97fbe43 100644 --- a/pelican/tools/templates/Makefile.in +++ b/pelican/tools/templates/Makefile.in @@ -92,8 +92,7 @@ else endif stopserver: - kill -9 `cat pelican.pid` - kill -9 `cat srv.pid` + $(BASEDIR)/develop_server.sh stop @echo 'Stopped Pelican and SimpleHTTPServer processes running in background.' publish: From 940eb76b7f70b1c9c7f833d5328d44cb19bde406 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 2 Jun 2015 08:35:12 -0700 Subject: [PATCH 0169/1173] Load external resources via HTTPS when available This replaces all `http://` and `//:` links with `https:`. The protocol- relative URL scheme is now deemed to be an anti-pattern. There are security advantages to using HTTPS, and there are no significant performance concerns. In short, if the asset we need is available via HTTPS, then that asset should always be loaded via HTTPS. Fixes #1736 --- pelican/tests/output/basic/a-markdown-powered-article.html | 2 +- pelican/tests/output/basic/archives.html | 2 +- pelican/tests/output/basic/article-1.html | 2 +- pelican/tests/output/basic/article-2.html | 2 +- pelican/tests/output/basic/article-3.html | 2 +- pelican/tests/output/basic/author/alexis-metaireau.html | 2 +- pelican/tests/output/basic/authors.html | 2 +- pelican/tests/output/basic/categories.html | 2 +- pelican/tests/output/basic/category/bar.html | 2 +- pelican/tests/output/basic/category/cat1.html | 2 +- pelican/tests/output/basic/category/misc.html | 2 +- pelican/tests/output/basic/category/yeah.html | 2 +- pelican/tests/output/basic/filename_metadata-example.html | 2 +- pelican/tests/output/basic/index.html | 2 +- pelican/tests/output/basic/oh-yeah.html | 2 +- pelican/tests/output/basic/override/index.html | 2 +- .../output/basic/pages/this-is-a-test-hidden-page.html | 2 +- pelican/tests/output/basic/pages/this-is-a-test-page.html | 2 +- pelican/tests/output/basic/second-article-fr.html | 2 +- pelican/tests/output/basic/second-article.html | 2 +- pelican/tests/output/basic/tag/bar.html | 2 +- pelican/tests/output/basic/tag/baz.html | 2 +- pelican/tests/output/basic/tag/foo.html | 2 +- pelican/tests/output/basic/tag/foobar.html | 2 +- pelican/tests/output/basic/tag/oh.html | 2 +- pelican/tests/output/basic/tag/yeah.html | 2 +- pelican/tests/output/basic/tags.html | 2 +- pelican/tests/output/basic/theme/css/main.css | 2 +- pelican/tests/output/basic/this-is-a-super-article.html | 2 +- pelican/tests/output/basic/unbelievable.html | 2 +- pelican/tests/output/custom/a-markdown-powered-article.html | 6 +++--- pelican/tests/output/custom/archives.html | 6 +++--- pelican/tests/output/custom/article-1.html | 6 +++--- pelican/tests/output/custom/article-2.html | 6 +++--- pelican/tests/output/custom/article-3.html | 6 +++--- pelican/tests/output/custom/author/alexis-metaireau.html | 6 +++--- pelican/tests/output/custom/author/alexis-metaireau2.html | 6 +++--- pelican/tests/output/custom/author/alexis-metaireau3.html | 6 +++--- pelican/tests/output/custom/authors.html | 6 +++--- pelican/tests/output/custom/categories.html | 6 +++--- pelican/tests/output/custom/category/bar.html | 6 +++--- pelican/tests/output/custom/category/cat1.html | 6 +++--- pelican/tests/output/custom/category/misc.html | 6 +++--- pelican/tests/output/custom/category/yeah.html | 6 +++--- pelican/tests/output/custom/drafts/a-draft-article.html | 6 +++--- pelican/tests/output/custom/filename_metadata-example.html | 6 +++--- pelican/tests/output/custom/index.html | 6 +++--- pelican/tests/output/custom/index2.html | 6 +++--- pelican/tests/output/custom/index3.html | 6 +++--- pelican/tests/output/custom/jinja2_template.html | 6 +++--- pelican/tests/output/custom/oh-yeah-fr.html | 6 +++--- pelican/tests/output/custom/oh-yeah.html | 6 +++--- pelican/tests/output/custom/override/index.html | 6 +++--- .../output/custom/pages/this-is-a-test-hidden-page.html | 6 +++--- pelican/tests/output/custom/pages/this-is-a-test-page.html | 6 +++--- pelican/tests/output/custom/second-article-fr.html | 6 +++--- pelican/tests/output/custom/second-article.html | 6 +++--- pelican/tests/output/custom/tag/bar.html | 6 +++--- pelican/tests/output/custom/tag/baz.html | 6 +++--- pelican/tests/output/custom/tag/foo.html | 6 +++--- pelican/tests/output/custom/tag/foobar.html | 6 +++--- pelican/tests/output/custom/tag/oh.html | 6 +++--- pelican/tests/output/custom/tag/yeah.html | 6 +++--- pelican/tests/output/custom/tags.html | 6 +++--- pelican/tests/output/custom/theme/css/main.css | 2 +- pelican/tests/output/custom/this-is-a-super-article.html | 6 +++--- pelican/tests/output/custom/unbelievable.html | 6 +++--- pelican/tests/output/custom_locale/archives.html | 6 +++--- .../tests/output/custom_locale/author/alexis-metaireau.html | 6 +++--- .../output/custom_locale/author/alexis-metaireau2.html | 6 +++--- .../output/custom_locale/author/alexis-metaireau3.html | 6 +++--- pelican/tests/output/custom_locale/authors.html | 6 +++--- pelican/tests/output/custom_locale/categories.html | 6 +++--- pelican/tests/output/custom_locale/category/bar.html | 6 +++--- pelican/tests/output/custom_locale/category/cat1.html | 6 +++--- pelican/tests/output/custom_locale/category/misc.html | 6 +++--- pelican/tests/output/custom_locale/category/yeah.html | 6 +++--- .../tests/output/custom_locale/drafts/a-draft-article.html | 6 +++--- pelican/tests/output/custom_locale/index.html | 6 +++--- pelican/tests/output/custom_locale/index2.html | 6 +++--- pelican/tests/output/custom_locale/index3.html | 6 +++--- pelican/tests/output/custom_locale/jinja2_template.html | 6 +++--- pelican/tests/output/custom_locale/oh-yeah-fr.html | 6 +++--- pelican/tests/output/custom_locale/override/index.html | 6 +++--- .../custom_locale/pages/this-is-a-test-hidden-page.html | 6 +++--- .../output/custom_locale/pages/this-is-a-test-page.html | 6 +++--- .../2010/décembre/02/this-is-a-super-article/index.html | 6 +++--- .../posts/2010/octobre/15/unbelievable/index.html | 6 +++--- .../custom_locale/posts/2010/octobre/20/oh-yeah/index.html | 6 +++--- .../2011/avril/20/a-markdown-powered-article/index.html | 6 +++--- .../posts/2011/février/17/article-1/index.html | 6 +++--- .../posts/2011/février/17/article-2/index.html | 6 +++--- .../posts/2011/février/17/article-3/index.html | 6 +++--- .../posts/2012/février/29/second-article/index.html | 6 +++--- .../2012/novembre/30/filename_metadata-example/index.html | 6 +++--- pelican/tests/output/custom_locale/second-article-fr.html | 6 +++--- pelican/tests/output/custom_locale/tag/bar.html | 6 +++--- pelican/tests/output/custom_locale/tag/baz.html | 6 +++--- pelican/tests/output/custom_locale/tag/foo.html | 6 +++--- pelican/tests/output/custom_locale/tag/foobar.html | 6 +++--- pelican/tests/output/custom_locale/tag/oh.html | 6 +++--- pelican/tests/output/custom_locale/tag/yeah.html | 6 +++--- pelican/tests/output/custom_locale/tags.html | 6 +++--- pelican/tests/output/custom_locale/theme/css/main.css | 2 +- pelican/themes/notmyidea/static/css/main.css | 2 +- pelican/themes/notmyidea/templates/analytics.html | 2 +- pelican/themes/notmyidea/templates/base.html | 2 +- pelican/themes/notmyidea/templates/disqus_script.html | 2 +- pelican/themes/notmyidea/templates/github.html | 4 ++-- pelican/themes/notmyidea/templates/twitter.html | 2 +- pelican/themes/simple/templates/gosquared.html | 2 +- 111 files changed, 256 insertions(+), 256 deletions(-) diff --git a/pelican/tests/output/basic/a-markdown-powered-article.html b/pelican/tests/output/basic/a-markdown-powered-article.html index 5fcc42a9..dd92d691 100644 --- a/pelican/tests/output/basic/a-markdown-powered-article.html +++ b/pelican/tests/output/basic/a-markdown-powered-article.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/archives.html b/pelican/tests/output/basic/archives.html index f8f1a67f..27b7c862 100644 --- a/pelican/tests/output/basic/archives.html +++ b/pelican/tests/output/basic/archives.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/article-1.html b/pelican/tests/output/basic/article-1.html index 4ea7f4e3..b09eef79 100644 --- a/pelican/tests/output/basic/article-1.html +++ b/pelican/tests/output/basic/article-1.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/article-2.html b/pelican/tests/output/basic/article-2.html index 45130e55..e340a4f6 100644 --- a/pelican/tests/output/basic/article-2.html +++ b/pelican/tests/output/basic/article-2.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/article-3.html b/pelican/tests/output/basic/article-3.html index 8603430f..08bf4fc1 100644 --- a/pelican/tests/output/basic/article-3.html +++ b/pelican/tests/output/basic/article-3.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/author/alexis-metaireau.html b/pelican/tests/output/basic/author/alexis-metaireau.html index 11d54185..eeca537a 100644 --- a/pelican/tests/output/basic/author/alexis-metaireau.html +++ b/pelican/tests/output/basic/author/alexis-metaireau.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/authors.html b/pelican/tests/output/basic/authors.html index 20df01d2..288543b5 100644 --- a/pelican/tests/output/basic/authors.html +++ b/pelican/tests/output/basic/authors.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/categories.html b/pelican/tests/output/basic/categories.html index 55e955c8..9a6682c0 100644 --- a/pelican/tests/output/basic/categories.html +++ b/pelican/tests/output/basic/categories.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/category/bar.html b/pelican/tests/output/basic/category/bar.html index 18e434cb..d3eb38da 100644 --- a/pelican/tests/output/basic/category/bar.html +++ b/pelican/tests/output/basic/category/bar.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/category/cat1.html b/pelican/tests/output/basic/category/cat1.html index f99eb497..f21bc9ab 100644 --- a/pelican/tests/output/basic/category/cat1.html +++ b/pelican/tests/output/basic/category/cat1.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/category/misc.html b/pelican/tests/output/basic/category/misc.html index fc724edb..0368793e 100644 --- a/pelican/tests/output/basic/category/misc.html +++ b/pelican/tests/output/basic/category/misc.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/category/yeah.html b/pelican/tests/output/basic/category/yeah.html index 7fe75a86..09db53bc 100644 --- a/pelican/tests/output/basic/category/yeah.html +++ b/pelican/tests/output/basic/category/yeah.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/filename_metadata-example.html b/pelican/tests/output/basic/filename_metadata-example.html index 638c65dd..9f492fc2 100644 --- a/pelican/tests/output/basic/filename_metadata-example.html +++ b/pelican/tests/output/basic/filename_metadata-example.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/index.html b/pelican/tests/output/basic/index.html index f3814b00..3066172d 100644 --- a/pelican/tests/output/basic/index.html +++ b/pelican/tests/output/basic/index.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/oh-yeah.html b/pelican/tests/output/basic/oh-yeah.html index 76be69fe..caeb8ddb 100644 --- a/pelican/tests/output/basic/oh-yeah.html +++ b/pelican/tests/output/basic/oh-yeah.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/override/index.html b/pelican/tests/output/basic/override/index.html index ed9fa92a..d98009a2 100644 --- a/pelican/tests/output/basic/override/index.html +++ b/pelican/tests/output/basic/override/index.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html b/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html index ac31987a..b98f0008 100644 --- a/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html +++ b/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/pages/this-is-a-test-page.html b/pelican/tests/output/basic/pages/this-is-a-test-page.html index 43e5f72e..282fe965 100644 --- a/pelican/tests/output/basic/pages/this-is-a-test-page.html +++ b/pelican/tests/output/basic/pages/this-is-a-test-page.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/second-article-fr.html b/pelican/tests/output/basic/second-article-fr.html index 551027da..c13acce6 100644 --- a/pelican/tests/output/basic/second-article-fr.html +++ b/pelican/tests/output/basic/second-article-fr.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/second-article.html b/pelican/tests/output/basic/second-article.html index ed350752..e9a5b14c 100644 --- a/pelican/tests/output/basic/second-article.html +++ b/pelican/tests/output/basic/second-article.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/tag/bar.html b/pelican/tests/output/basic/tag/bar.html index 5331767b..c461cac5 100644 --- a/pelican/tests/output/basic/tag/bar.html +++ b/pelican/tests/output/basic/tag/bar.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/tag/baz.html b/pelican/tests/output/basic/tag/baz.html index dc26d8e1..7961b19f 100644 --- a/pelican/tests/output/basic/tag/baz.html +++ b/pelican/tests/output/basic/tag/baz.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/tag/foo.html b/pelican/tests/output/basic/tag/foo.html index aed3ad17..1a97fd4a 100644 --- a/pelican/tests/output/basic/tag/foo.html +++ b/pelican/tests/output/basic/tag/foo.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/tag/foobar.html b/pelican/tests/output/basic/tag/foobar.html index 540cde25..891b6866 100644 --- a/pelican/tests/output/basic/tag/foobar.html +++ b/pelican/tests/output/basic/tag/foobar.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/tag/oh.html b/pelican/tests/output/basic/tag/oh.html index ef876b8d..61148527 100644 --- a/pelican/tests/output/basic/tag/oh.html +++ b/pelican/tests/output/basic/tag/oh.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/tag/yeah.html b/pelican/tests/output/basic/tag/yeah.html index b8da2bc6..bd5ff204 100644 --- a/pelican/tests/output/basic/tag/yeah.html +++ b/pelican/tests/output/basic/tag/yeah.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/tags.html b/pelican/tests/output/basic/tags.html index 0eda47d7..44b45591 100644 --- a/pelican/tests/output/basic/tags.html +++ b/pelican/tests/output/basic/tags.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/theme/css/main.css b/pelican/tests/output/basic/theme/css/main.css index 9d7221a2..03a77e69 100644 --- a/pelican/tests/output/basic/theme/css/main.css +++ b/pelican/tests/output/basic/theme/css/main.css @@ -12,7 +12,7 @@ @import url("reset.css"); @import url("pygment.css"); @import url("typogrify.css"); -@import url(//fonts.googleapis.com/css?family=Yanone+Kaffeesatz&subset=latin); +@import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz&subset=latin); /***** Global *****/ /* Body */ diff --git a/pelican/tests/output/basic/this-is-a-super-article.html b/pelican/tests/output/basic/this-is-a-super-article.html index cf957ebf..1fe944eb 100644 --- a/pelican/tests/output/basic/this-is-a-super-article.html +++ b/pelican/tests/output/basic/this-is-a-super-article.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/basic/unbelievable.html b/pelican/tests/output/basic/unbelievable.html index b9b52031..dfb0c54e 100644 --- a/pelican/tests/output/basic/unbelievable.html +++ b/pelican/tests/output/basic/unbelievable.html @@ -7,7 +7,7 @@ diff --git a/pelican/tests/output/custom/a-markdown-powered-article.html b/pelican/tests/output/custom/a-markdown-powered-article.html index 577d61b8..59ffa4d1 100644 --- a/pelican/tests/output/custom/a-markdown-powered-article.html +++ b/pelican/tests/output/custom/a-markdown-powered-article.html @@ -8,13 +8,13 @@ -Fork me on GitHub +Fork me on GitHub
(function () { var s = document.createElement('script'); s.async = true; s.type = 'text/javascript'; - s.src = '//' + disqus_shortname + '.disqus.com/count.js'; + s.src = 'https://' + disqus_shortname + '.disqus.com/count.js'; (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s); }()); diff --git a/pelican/tests/output/custom/authors.html b/pelican/tests/output/custom/authors.html index d9aaef34..f157de26 100644 --- a/pelican/tests/output/custom/authors.html +++ b/pelican/tests/output/custom/authors.html @@ -8,13 +8,13 @@ -Fork me on GitHub +Fork me on GitHub
(function () { var s = document.createElement('script'); s.async = true; s.type = 'text/javascript'; - s.src = '//' + disqus_shortname + '.disqus.com/count.js'; + s.src = 'https://' + disqus_shortname + '.disqus.com/count.js'; (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s); }()); diff --git a/pelican/tests/output/custom/category/yeah.html b/pelican/tests/output/custom/category/yeah.html index a632664f..127eac13 100644 --- a/pelican/tests/output/custom/category/yeah.html +++ b/pelican/tests/output/custom/category/yeah.html @@ -8,13 +8,13 @@ -Fork me on GitHub +Fork me on GitHub
(function () { var s = document.createElement('script'); s.async = true; s.type = 'text/javascript'; - s.src = '//' + disqus_shortname + '.disqus.com/count.js'; + s.src = 'https://' + disqus_shortname + '.disqus.com/count.js'; (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s); }()); diff --git a/pelican/tests/output/custom/jinja2_template.html b/pelican/tests/output/custom/jinja2_template.html index 0eafa913..21f678f9 100644 --- a/pelican/tests/output/custom/jinja2_template.html +++ b/pelican/tests/output/custom/jinja2_template.html @@ -8,13 +8,13 @@ -Fork me on GitHub +Fork me on GitHub
(function () { var s = document.createElement('script'); s.async = true; s.type = 'text/javascript'; - s.src = '//' + disqus_shortname + '.disqus.com/count.js'; + s.src = 'https://' + disqus_shortname + '.disqus.com/count.js'; (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s); }()); diff --git a/pelican/tests/output/custom_locale/authors.html b/pelican/tests/output/custom_locale/authors.html index 2558c4d8..91ea479d 100644 --- a/pelican/tests/output/custom_locale/authors.html +++ b/pelican/tests/output/custom_locale/authors.html @@ -8,13 +8,13 @@ -Fork me on GitHub +Fork me on GitHub
(function () { var s = document.createElement('script'); s.async = true; s.type = 'text/javascript'; - s.src = '//' + disqus_shortname + '.disqus.com/count.js'; + s.src = 'https://' + disqus_shortname + '.disqus.com/count.js'; (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s); }()); diff --git a/pelican/tests/output/custom_locale/category/yeah.html b/pelican/tests/output/custom_locale/category/yeah.html index 7ee2bbe9..d36c1608 100644 --- a/pelican/tests/output/custom_locale/category/yeah.html +++ b/pelican/tests/output/custom_locale/category/yeah.html @@ -8,13 +8,13 @@ -Fork me on GitHub +Fork me on GitHub
(function () { var s = document.createElement('script'); s.async = true; s.type = 'text/javascript'; - s.src = '//' + disqus_shortname + '.disqus.com/count.js'; + s.src = 'https://' + disqus_shortname + '.disqus.com/count.js'; (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s); }()); diff --git a/pelican/tests/output/custom_locale/jinja2_template.html b/pelican/tests/output/custom_locale/jinja2_template.html index 0eafa913..21f678f9 100644 --- a/pelican/tests/output/custom_locale/jinja2_template.html +++ b/pelican/tests/output/custom_locale/jinja2_template.html @@ -8,13 +8,13 @@ -Fork me on GitHub +Fork me on GitHub

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom/category/cat1.html b/pelican/tests/output/custom/category/cat1.html index 237943d7..d9132d10 100644 --- a/pelican/tests/output/custom/category/cat1.html +++ b/pelican/tests/output/custom/category/cat1.html @@ -120,9 +120,6 @@

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom/category/misc.html b/pelican/tests/output/custom/category/misc.html index 5fcde24b..fa71085d 100644 --- a/pelican/tests/output/custom/category/misc.html +++ b/pelican/tests/output/custom/category/misc.html @@ -131,9 +131,6 @@ pelican.conf, it ...

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom/category/yeah.html b/pelican/tests/output/custom/category/yeah.html index 127eac13..6d94c9ad 100644 --- a/pelican/tests/output/custom/category/yeah.html +++ b/pelican/tests/output/custom/category/yeah.html @@ -59,9 +59,6 @@

→ And now try with some utf8 hell: ééé

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom/tag/bar.html b/pelican/tests/output/custom/tag/bar.html index 2e0d047c..f462ffbd 100644 --- a/pelican/tests/output/custom/tag/bar.html +++ b/pelican/tests/output/custom/tag/bar.html @@ -110,9 +110,6 @@ YEAH !

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom/tag/foo.html b/pelican/tests/output/custom/tag/foo.html index 7c38c24c..9c58956b 100644 --- a/pelican/tests/output/custom/tag/foo.html +++ b/pelican/tests/output/custom/tag/foo.html @@ -80,9 +80,6 @@ as well as inline markup.

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom/tag/foobar.html b/pelican/tests/output/custom/tag/foobar.html index 42705281..7d17d1fb 100644 --- a/pelican/tests/output/custom/tag/foobar.html +++ b/pelican/tests/output/custom/tag/foobar.html @@ -59,9 +59,6 @@

→ And now try with some utf8 hell: ééé

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom/tag/yeah.html b/pelican/tests/output/custom/tag/yeah.html index 4d7b506b..e3c765fa 100644 --- a/pelican/tests/output/custom/tag/yeah.html +++ b/pelican/tests/output/custom/tag/yeah.html @@ -51,9 +51,6 @@ YEAH !

alternate text

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom_locale/category/bar.html b/pelican/tests/output/custom_locale/category/bar.html index 7c8f947d..c416b358 100644 --- a/pelican/tests/output/custom_locale/category/bar.html +++ b/pelican/tests/output/custom_locale/category/bar.html @@ -51,9 +51,6 @@ YEAH !

alternate text

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom_locale/category/cat1.html b/pelican/tests/output/custom_locale/category/cat1.html index 25bc6179..871b2e3f 100644 --- a/pelican/tests/output/custom_locale/category/cat1.html +++ b/pelican/tests/output/custom_locale/category/cat1.html @@ -120,9 +120,6 @@

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom_locale/category/misc.html b/pelican/tests/output/custom_locale/category/misc.html index 8062c0d6..bb78a8cc 100644 --- a/pelican/tests/output/custom_locale/category/misc.html +++ b/pelican/tests/output/custom_locale/category/misc.html @@ -131,9 +131,6 @@ pelican.conf, it ...

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom_locale/category/yeah.html b/pelican/tests/output/custom_locale/category/yeah.html index d36c1608..c5e6c7f0 100644 --- a/pelican/tests/output/custom_locale/category/yeah.html +++ b/pelican/tests/output/custom_locale/category/yeah.html @@ -59,9 +59,6 @@

→ And now try with some utf8 hell: ééé

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom_locale/tag/bar.html b/pelican/tests/output/custom_locale/tag/bar.html index 8ba1aed2..c1f33e64 100644 --- a/pelican/tests/output/custom_locale/tag/bar.html +++ b/pelican/tests/output/custom_locale/tag/bar.html @@ -110,9 +110,6 @@ YEAH !

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom_locale/tag/foo.html b/pelican/tests/output/custom_locale/tag/foo.html index ed67ede0..288f1768 100644 --- a/pelican/tests/output/custom_locale/tag/foo.html +++ b/pelican/tests/output/custom_locale/tag/foo.html @@ -80,9 +80,6 @@ as well as inline markup.

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom_locale/tag/foobar.html b/pelican/tests/output/custom_locale/tag/foobar.html index a569b526..59dcede1 100644 --- a/pelican/tests/output/custom_locale/tag/foobar.html +++ b/pelican/tests/output/custom_locale/tag/foobar.html @@ -59,9 +59,6 @@

→ And now try with some utf8 hell: ééé

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/tests/output/custom_locale/tag/yeah.html b/pelican/tests/output/custom_locale/tag/yeah.html index ba767748..4dc36ce5 100644 --- a/pelican/tests/output/custom_locale/tag/yeah.html +++ b/pelican/tests/output/custom_locale/tag/yeah.html @@ -51,9 +51,6 @@ YEAH !

alternate text

There are comments.

-

- Page 1 / 1 -

diff --git a/pelican/themes/notmyidea/templates/index.html b/pelican/themes/notmyidea/templates/index.html index 3eac8a3a..6019987b 100644 --- a/pelican/themes/notmyidea/templates/index.html +++ b/pelican/themes/notmyidea/templates/index.html @@ -11,9 +11,6 @@

{{ article.title }}

{% include 'article_infos.html' %}{{ article.content }}{% include 'comments.html' %} - {% if loop.length == 1 %} - {% include 'pagination.html' %} - {% endif %} {% if loop.length > 1 %}
@@ -42,13 +39,11 @@ {% endif %} {% if loop.last %} - {% if loop.length > 1 %} + {% if loop.length > 1 or articles_page.has_other_pages() %} - {% endif %} - {% if articles_page.has_previous() or loop.length > 1 %} - {% include 'pagination.html' %} - {% endif %} - {% if loop.length > 1 %} + {% if articles_page.has_other_pages() %} + {% include 'pagination.html' %} + {% endif %}
{% endif %} {% endif %} From 21544a404cf51b60d407b0fd4eeadfc1d8339a9c Mon Sep 17 00:00:00 2001 From: Elana Hashman Date: Sun, 21 Jun 2015 13:35:41 -0600 Subject: [PATCH 0193/1173] Update simple theme to fix #1068 --- pelican/themes/simple/templates/index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pelican/themes/simple/templates/index.html b/pelican/themes/simple/templates/index.html index 20104db9..4bd81985 100644 --- a/pelican/themes/simple/templates/index.html +++ b/pelican/themes/simple/templates/index.html @@ -21,6 +21,8 @@ {% endfor %} -{% include 'pagination.html' %} +{% if articles_page.has_other_pages() %} + {% include 'pagination.html' %} +{% endif %}
{% endblock content %} From ff88c4bb34779d979b8ce4ba042c07621204ea1a Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Fri, 26 Jun 2015 09:44:45 -0700 Subject: [PATCH 0194/1173] Document enabling per-block Markdown line numbers Since users frequently ask how to enable line numbers in Markdown-formatted code blocks, adding instructions to the docs will allow us to point users there. Fixes #1773 --- docs/content.rst | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/content.rst b/docs/content.rst index 0e3310f1..1e6ba099 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -420,22 +420,29 @@ which posts are translations:: Syntax highlighting =================== -Pelican is able to provide colorized syntax highlighting for your code blocks. -To do so, you have to use the following conventions inside your content files. +Pelican can provide colorized syntax highlighting for your code blocks. +To do so, you must use the following conventions inside your content files. -For reStructuredText, use the code-block directive:: +For reStructuredText, use the ``code-block`` directive to specify the type +of code to be highlighted (in these examples, we'll use ``python``):: - .. code-block:: identifier + .. code-block:: python - + print("Pelican is a static site generator.") -For Markdown, include the language identifier just above the code block, -indenting both the identifier and code:: +For Markdown, which utilizes the `CodeHilite extension`_ to provide syntax +highlighting, include the language identifier just above the code block, +indenting both the identifier and the code:: - A block of text. + There are two ways to specify the identifier: - :::identifier - + :::python + print("The triple-colon syntax will *not* show line numbers.") + + To display line numbers, use a path-less shebang instead of colons: + + #!python + print("The path-less shebang syntax *will* show line numbers.") The specified identifier (e.g. ``python``, ``ruby``) should be one that appears on the `list of available lexers `_. @@ -521,4 +528,5 @@ metadata to include ``Status: published``. .. _AsciiDoc: http://www.methods.co.nz/asciidoc/ .. _pelican-plugins: http://github.com/getpelican/pelican-plugins .. _Markdown Extensions: http://pythonhosted.org/Markdown/extensions/ +.. _CodeHilite extension: http://pythonhosted.org/Markdown/extensions/code_hilite.html#syntax .. _i18n_subsites plugin: http://github.com/getpelican/pelican-plugins/tree/master/i18n_subsites From ec5c77b25145f7c20fee24c6b85b478295dbc956 Mon Sep 17 00:00:00 2001 From: derwinlu Date: Thu, 18 Jun 2015 23:33:20 +0200 Subject: [PATCH 0195/1173] remove PAGES; use pages instead * remove PAGES from context as pages is available * add section to FAQ to provide guidance --- docs/faq.rst | 13 +++++++++++++ docs/themes.rst | 2 ++ pelican/generators.py | 1 - pelican/themes/notmyidea/templates/base.html | 2 +- pelican/themes/simple/templates/base.html | 2 +- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index ff473624..08df017d 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -250,3 +250,16 @@ moved out of the pelican core and into a separate `plugin `_. See the :ref:`plugins` documentation further information about the Pelican plugin system. + +Since I upgraded Pelican my Pages are no longer rendered +======================================================== +Pages were available to Themes as lowercase ``pages`` and uppercase +``PAGES``. To bring this inline with the :ref:`templates-variables` section, +``PAGES`` has been removed. This is quickly resolved by updating your theme +to iterate over ``pages`` instead of ``PAGES``. Just replace:: + + {% for pg in PAGES %} + +with something like:: + + {% for pg in pages %} diff --git a/docs/themes.rst b/docs/themes.rst index 6ca753c6..3978e693 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -47,6 +47,8 @@ To make your own theme, you must follow the following structure:: if it helps you keep things organized while creating your theme. +.. _templates-variables: + Templates and variables ======================= diff --git a/pelican/generators.py b/pelican/generators.py index 0a5298e4..da651252 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -644,7 +644,6 @@ class PagesGenerator(CachingGenerator): process_translations(hidden_pages)) self._update_context(('pages', 'hidden_pages')) - self.context['PAGES'] = self.pages self.save_cache() self.readers.save_cache() diff --git a/pelican/themes/notmyidea/templates/base.html b/pelican/themes/notmyidea/templates/base.html index 45ab4044..188715d4 100644 --- a/pelican/themes/notmyidea/templates/base.html +++ b/pelican/themes/notmyidea/templates/base.html @@ -25,7 +25,7 @@
  • {{ title }}
  • {% endfor %} {% if DISPLAY_PAGES_ON_MENU -%} - {% for pg in PAGES %} + {% for pg in pages %} {{ pg.title }} {% endfor %} {% endif %} diff --git a/pelican/themes/simple/templates/base.html b/pelican/themes/simple/templates/base.html index bde7983b..760ca5da 100644 --- a/pelican/themes/simple/templates/base.html +++ b/pelican/themes/simple/templates/base.html @@ -40,7 +40,7 @@
  • {{ title }}
  • {% endfor %} {% if DISPLAY_PAGES_ON_MENU %} - {% for p in PAGES %} + {% for p in pages %} {{ p.title }} {% endfor %} {% else %} From 5389543a39f3772afdb5701c5bcbb71e44883f05 Mon Sep 17 00:00:00 2001 From: derwinlu Date: Fri, 19 Jun 2015 11:19:21 +0200 Subject: [PATCH 0196/1173] improve URLWrapper comparison * speed up via reduced slugify calls (only call when needed) * fix __repr__ to not contain str, should call repr on name * add test_urlwrappers and move URLWrappers tests there * add new equality test * cleanup header additionally: * Content is now decorated with python_2_unicode_compatible instead of treating __str__ differently * better formatting for test_article_metadata_key_lowercase to actually output the conflict instead of a non descriptive error --- pelican/contents.py | 8 ++--- pelican/tests/test_contents.py | 27 ---------------- pelican/tests/test_readers.py | 13 ++++---- pelican/tests/test_urlwrappers.py | 51 +++++++++++++++++++++++++++++++ pelican/urlwrappers.py | 29 ++++++++++++------ 5 files changed, 80 insertions(+), 48 deletions(-) create mode 100644 pelican/tests/test_urlwrappers.py diff --git a/pelican/contents.py b/pelican/contents.py index 005d045c..0e91933b 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -25,6 +25,7 @@ from pelican.urlwrappers import (URLWrapper, Author, Category, Tag) # NOQA logger = logging.getLogger(__name__) +@python_2_unicode_compatible class Content(object): """Represents a content. @@ -148,12 +149,7 @@ class Content(object): signals.content_object_init.send(self) def __str__(self): - if self.source_path is None: - return repr(self) - elif six.PY3: - return self.source_path or repr(self) - else: - return str(self.source_path.encode('utf-8', 'replace')) + return self.source_path or repr(self) def check_properties(self): """Test mandatory properties are set.""" diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 5879bde2..01d2ed3b 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -578,30 +578,3 @@ class TestStatic(unittest.TestCase): content = page.get_content('') self.assertNotEqual(content, html) - - -class TestURLWrapper(unittest.TestCase): - def test_comparisons(self): - # URLWrappers are sorted by name - wrapper_a = URLWrapper(name='first', settings={}) - wrapper_b = URLWrapper(name='last', settings={}) - self.assertFalse(wrapper_a > wrapper_b) - self.assertFalse(wrapper_a >= wrapper_b) - self.assertFalse(wrapper_a == wrapper_b) - self.assertTrue(wrapper_a != wrapper_b) - self.assertTrue(wrapper_a <= wrapper_b) - self.assertTrue(wrapper_a < wrapper_b) - wrapper_b.name = 'first' - self.assertFalse(wrapper_a > wrapper_b) - self.assertTrue(wrapper_a >= wrapper_b) - self.assertTrue(wrapper_a == wrapper_b) - self.assertFalse(wrapper_a != wrapper_b) - self.assertTrue(wrapper_a <= wrapper_b) - self.assertFalse(wrapper_a < wrapper_b) - wrapper_a.name = 'last' - self.assertTrue(wrapper_a > wrapper_b) - self.assertTrue(wrapper_a >= wrapper_b) - self.assertFalse(wrapper_a == wrapper_b) - self.assertTrue(wrapper_a != wrapper_b) - self.assertFalse(wrapper_a <= wrapper_b) - self.assertFalse(wrapper_a < wrapper_b) diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index 18e5111e..dd7e5fc2 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -29,12 +29,10 @@ class ReaderTest(unittest.TestCase): self.assertEqual( value, real_value, - str('Expected %r to have value %r, but was %r') - % (key, value, real_value)) + 'Expected %s to have value %s, but was %s' % (key, value, real_value)) else: self.fail( - str('Expected %r to have value %r, but was not in Dict') - % (key, value)) + 'Expected %s to have value %s, but was not in Dict' % (key, value)) class TestAssertDictHasSubset(ReaderTest): def setUp(self): @@ -566,9 +564,12 @@ class HTMLReaderTest(ReaderTest): def test_article_metadata_key_lowercase(self): # Keys of metadata should be lowercase. page = self.read_file(path='article_with_uppercase_metadata.html') + + # Key should be lowercase self.assertIn('category', page.metadata, 'Key should be lowercase.') - self.assertEqual('Yeah', page.metadata.get('category'), - 'Value keeps cases.') + + # Value should keep cases + self.assertEqual('Yeah', page.metadata.get('category')) def test_article_with_nonconformant_meta_tags(self): page = self.read_file(path='article_with_nonconformant_meta_tags.html') diff --git a/pelican/tests/test_urlwrappers.py b/pelican/tests/test_urlwrappers.py new file mode 100644 index 00000000..20a87114 --- /dev/null +++ b/pelican/tests/test_urlwrappers.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from pelican.urlwrappers import URLWrapper, Tag, Category +from pelican.tests.support import unittest + +class TestURLWrapper(unittest.TestCase): + def test_ordering(self): + # URLWrappers are sorted by name + wrapper_a = URLWrapper(name='first', settings={}) + wrapper_b = URLWrapper(name='last', settings={}) + self.assertFalse(wrapper_a > wrapper_b) + self.assertFalse(wrapper_a >= wrapper_b) + self.assertFalse(wrapper_a == wrapper_b) + self.assertTrue(wrapper_a != wrapper_b) + self.assertTrue(wrapper_a <= wrapper_b) + self.assertTrue(wrapper_a < wrapper_b) + wrapper_b.name = 'first' + self.assertFalse(wrapper_a > wrapper_b) + self.assertTrue(wrapper_a >= wrapper_b) + self.assertTrue(wrapper_a == wrapper_b) + self.assertFalse(wrapper_a != wrapper_b) + self.assertTrue(wrapper_a <= wrapper_b) + self.assertFalse(wrapper_a < wrapper_b) + wrapper_a.name = 'last' + self.assertTrue(wrapper_a > wrapper_b) + self.assertTrue(wrapper_a >= wrapper_b) + self.assertFalse(wrapper_a == wrapper_b) + self.assertTrue(wrapper_a != wrapper_b) + self.assertFalse(wrapper_a <= wrapper_b) + self.assertFalse(wrapper_a < wrapper_b) + + def test_equality(self): + tag = Tag('test', settings={}) + cat = Category('test', settings={}) + + # same name, but different class + self.assertNotEqual(tag, cat) + + # should be equal vs text representing the same name + self.assertEqual(tag, u'test') + + # should not be equal vs binary + self.assertNotEqual(tag, b'test') + + # Tags describing the same should be equal + tag_equal = Tag('Test', settings={}) + self.assertEqual(tag, tag_equal) + + cat_ascii = Category('指導書', settings={}) + self.assertEqual(cat_ascii, u'zhi-dao-shu') diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py index 60bc6a3a..65dee23b 100644 --- a/pelican/urlwrappers.py +++ b/pelican/urlwrappers.py @@ -1,7 +1,9 @@ -import os +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + import functools import logging - +import os import six from pelican.utils import (slugify, python_2_unicode_compatible) @@ -52,27 +54,36 @@ class URLWrapper(object): def __hash__(self): return hash(self.slug) - def _key(self): - return self.slug - def _normalize_key(self, key): subs = self.settings.get('SLUG_SUBSTITUTIONS', ()) return six.text_type(slugify(key, subs)) def __eq__(self, other): - return self._key() == self._normalize_key(other) + if isinstance(other, self.__class__): + return self.slug == other.slug + if isinstance(other, six.text_type): + return self.slug == self._normalize_key(other) + return False def __ne__(self, other): - return self._key() != self._normalize_key(other) + if isinstance(other, self.__class__): + return self.slug != other.slug + if isinstance(other, six.text_type): + return self.slug != self._normalize_key(other) + return True def __lt__(self, other): - return self._key() < self._normalize_key(other) + if isinstance(other, self.__class__): + return self.slug < other.slug + if isinstance(other, six.text_type): + return self.slug < self._normalize_key(other) + return False def __str__(self): return self.name def __repr__(self): - return '<{} {}>'.format(type(self).__name__, str(self)) + return '<{} {}>'.format(type(self).__name__, repr(self._name)) def _from_settings(self, key, get_page_name=False): """Returns URL information as defined in settings. From ba9b4a1d9bab0dba7cc8563e14a0bbb80ae10b43 Mon Sep 17 00:00:00 2001 From: guanidene Date: Tue, 30 Jun 2015 20:56:21 +0530 Subject: [PATCH 0197/1173] Watch for changes in mtime of symlinked folders and files too --- pelican/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/utils.py b/pelican/utils.py index 6ad4de24..fb8ed9dc 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -576,7 +576,7 @@ def folder_watcher(path, extensions, ignores=[]): def file_times(path): '''Return `mtime` for each file in path''' - for root, dirs, files in os.walk(path): + for root, dirs, files in os.walk(path, followlinks=True): dirs[:] = [x for x in dirs if not x.startswith(os.curdir)] for f in files: From aa318a1366b4a1f8ce4eb8e50d52421c8abca031 Mon Sep 17 00:00:00 2001 From: Jotham Apaloo Date: Fri, 19 Jun 2015 14:50:12 -0400 Subject: [PATCH 0198/1173] Update tips.rst add 404 page instructions for amazon s3. --- docs/tips.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/tips.rst b/docs/tips.rst index 15d68769..82018400 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -28,6 +28,11 @@ configuration file's ``location`` block:: For Apache:: ErrorDocument 404 /404.html + +For Amazon S3, first navigate to the ``Static Site Hosting`` menu in the +bucket settings on your AWS cosole. From there:: + + Error Document: 404.html Publishing to GitHub ==================== From bd5dfa0b3ec9560a1a453907f9f3767b9b89ea01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Fri, 10 Jul 2015 00:48:41 +0300 Subject: [PATCH 0199/1173] Avoid adding the tests in bdists (like wheel bdist). Supersedes and closes #1618. --- setup.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 93d7e5f6..01d40ae4 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,7 @@ #!/usr/bin/env python +from os import walk +from os.path import join, relpath, dirname + from setuptools import setup requires = ['feedgenerator >= 1.6', 'jinja2 >= 2.7', 'pygments', 'docutils', @@ -14,11 +17,9 @@ entry_points = { ] } - README = open('README.rst').read() CHANGELOG = open('docs/changelog.rst').read() - setup( name="pelican", version="3.6.1.dev", @@ -29,7 +30,19 @@ setup( "Markdown input files.", long_description=README + '\n' + CHANGELOG, packages=['pelican', 'pelican.tools'], - include_package_data=True, + package_data={ + # we manually collect the package data, as opposed to using include_package_data=True + # because we don't want the tests to be included automatically as package data + # (MANIFEST.in is too greedy) + 'pelican': [ + relpath(join(root, name), 'pelican') + for root, _, names in walk(join('pelican', 'themes')) for name in names + ], + 'pelican.tools': [ + relpath(join(root, name), join('pelican', 'tools')) + for root, _, names in walk(join('pelican', 'tools', 'templates')) for name in names + ], + }, install_requires=requires, entry_points=entry_points, classifiers=[ From 379f8666c1ada0f091edb0792a7e84a5e273576b Mon Sep 17 00:00:00 2001 From: Andrea Corbellini Date: Thu, 30 Jul 2015 21:04:28 +0200 Subject: [PATCH 0200/1173] Rewrite pelican.utils.truncate_html_words() to use an HTML parser instead of regular expressions. --- pelican/tests/test_utils.py | 26 +++++++++ pelican/utils.py | 107 +++++++++++++++++++----------------- 2 files changed, 84 insertions(+), 49 deletions(-) diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index f5a60584..0f8878af 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -144,6 +144,32 @@ class TestUtils(LoggedTestCase): for value, expected in samples: self.assertEqual(utils.get_relative_path(value), expected) + def test_truncate_html_words(self): + self.assertEqual( + utils.truncate_html_words('short string', 20), + 'short string') + + self.assertEqual( + utils.truncate_html_words('word ' * 100, 20), + 'word ' * 20 + '...') + + self.assertEqual( + utils.truncate_html_words('

    ' + 'word ' * 100 + '

    ', 20), + '

    ' + 'word ' * 20 + '...

    ') + + self.assertEqual( + utils.truncate_html_words( + '' + 'word ' * 100 + '', 20), + '' + 'word ' * 20 + '...') + + self.assertEqual( + utils.truncate_html_words('
    ' + 'word ' * 100, 20), + '
    ' + 'word ' * 20 + '...') + + self.assertEqual( + utils.truncate_html_words('' + 'word ' * 100, 20), + '' + 'word ' * 20 + '...') + def test_process_translations(self): # create a bunch of articles # 1: no translation metadata diff --git a/pelican/utils.py b/pelican/utils.py index fb8ed9dc..43dca212 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -24,6 +24,7 @@ from itertools import groupby from jinja2 import Markup from operator import attrgetter from posixpath import join as posix_join +from six.moves.html_parser import HTMLParser logger = logging.getLogger(__name__) @@ -402,6 +403,58 @@ def posixize_path(rel_path): return rel_path.replace(os.sep, '/') +class _HTMLWordTruncator(HTMLParser): + + _word_regex = re.compile(r'\w[\w-]*', re.U) + _singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', + 'hr', 'input') + + def __init__(self, max_words): + # In Python 2, HTMLParser is not a new-style class, + # hence super() cannot be used. + HTMLParser.__init__(self) + + self.max_words = max_words + self.words_found = 0 + self.open_tags = [] + self.truncate_at = None + + def handle_starttag(self, tag, attrs): + if self.truncate_at is not None: + return + if tag not in self._singlets: + self.open_tags.insert(0, tag) + + def handle_endtag(self, tag): + if self.truncate_at is not None: + return + try: + i = self.open_tags.index(tag) + except ValueError: + pass + else: + # SGML: An end tag closes, back to the matching start tag, + # all unclosed intervening start tags with omitted end tags + del self.open_tags[:i + 1] + + def handle_data(self, data): + word_end = 0 + + while self.words_found < self.max_words: + match = self._word_regex.search(data, word_end) + if not match: + break + word_end = match.end(0) + self.words_found += 1 + + if self.words_found == self.max_words: + line_start = 0 + lineno, line_offset = self.getpos() + for i in range(lineno - 1): + line_start = self.rawdata.index('\n', line_start) + 1 + self.truncate_at = line_start + line_offset + word_end + + def truncate_html_words(s, num, end_text='...'): """Truncates HTML to a certain number of words. @@ -414,59 +467,15 @@ def truncate_html_words(s, num, end_text='...'): length = int(num) if length <= 0: return '' - html4_singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', - 'hr', 'input') - - # Set up regular expressions - re_words = re.compile(r'&.*?;|<.*?>|(\w[\w-]*)', re.U) - re_tag = re.compile(r'<(/)?([^ ]+?)(?: (/)| .*?)?>') - # Count non-HTML words and keep note of open tags - pos = 0 - end_text_pos = 0 - words = 0 - open_tags = [] - while words <= length: - m = re_words.search(s, pos) - if not m: - # Checked through whole string - break - pos = m.end(0) - if m.group(1): - # It's an actual non-HTML word - words += 1 - if words == length: - end_text_pos = pos - continue - # Check for tag - tag = re_tag.match(m.group(0)) - if not tag or end_text_pos: - # Don't worry about non tags or tags after our truncate point - continue - closing_tag, tagname, self_closing = tag.groups() - tagname = tagname.lower() # Element names are always case-insensitive - if self_closing or tagname in html4_singlets: - pass - elif closing_tag: - # Check for match in open tags list - try: - i = open_tags.index(tagname) - except ValueError: - pass - else: - # SGML: An end tag closes, back to the matching start tag, - # all unclosed intervening start tags with omitted end tags - open_tags = open_tags[i + 1:] - else: - # Add it to the start of the open tags list - open_tags.insert(0, tagname) - if words <= length: - # Don't try to close tags if we don't need to truncate + truncator = _HTMLWordTruncator(length) + truncator.feed(s) + if truncator.truncate_at is None: return s - out = s[:end_text_pos] + out = s[:truncator.truncate_at] if end_text: out += ' ' + end_text # Close any tags still open - for tag in open_tags: + for tag in truncator.open_tags: out += '' % tag # Return string return out From 5e36a87916dd2cb431b7c916f53348605a687542 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sat, 1 Aug 2015 13:20:04 -0700 Subject: [PATCH 0201/1173] Update changelog --- docs/changelog.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index f08f67ec..72f71fa5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,7 +4,10 @@ Release history Next release ============ -- Nothing yet +* Fix installation errors related to Unicode in tests +* Don't show pagination in ``notmyidea`` theme if there's only one page +* Make hidden pages available in context +* Improve URLWrapper comparison 3.6.0 (2015-06-15) ================== From 96b23e03bd7a2b8a904fafb70d87484f0c81c86e Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sat, 1 Aug 2015 13:39:10 -0700 Subject: [PATCH 0202/1173] Bump version 3.6.2 --- docs/changelog.rst | 4 ++-- docs/conf.py | 2 +- pelican/__init__.py | 2 +- setup.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 72f71fa5..664e1310 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,8 +1,8 @@ Release history ############### -Next release -============ +3.6.2 (2015-08-01) +================== * Fix installation errors related to Unicode in tests * Don't show pagination in ``notmyidea`` theme if there's only one page diff --git a/docs/conf.py b/docs/conf.py index 43af8da4..15ae49b9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ copyright = '2015, Alexis Metaireau and contributors' exclude_patterns = ['_build'] release = __version__ version = '.'.join(release.split('.')[:1]) -last_stable = '3.6.0' +last_stable = '3.6.2' rst_prolog = ''' .. |last_stable| replace:: :pelican-doc:`{0}` '''.format(last_stable) diff --git a/pelican/__init__.py b/pelican/__init__.py index 932974db..0b5c62d4 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -26,7 +26,7 @@ from pelican.utils import (clean_output_dir, folder_watcher, file_watcher, maybe_pluralize) from pelican.writers import Writer -__version__ = "3.6.1.dev" +__version__ = "3.6.2" DEFAULT_CONFIG_NAME = 'pelicanconf.py' diff --git a/setup.py b/setup.py index 01d40ae4..b6b5c3d8 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ CHANGELOG = open('docs/changelog.rst').read() setup( name="pelican", - version="3.6.1.dev", + version="3.6.2", url='http://getpelican.com/', author='Alexis Metaireau', author_email='authors@getpelican.com', From 7181cc36d59b7e50d1d2da0f4f03e0a77fe02be5 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sat, 1 Aug 2015 13:39:24 -0700 Subject: [PATCH 0203/1173] Prepare version 3.6.3.dev for next development cycle --- docs/changelog.rst | 5 +++++ pelican/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 664e1310..b90fd997 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,11 @@ Release history ############### +Next release +============ + +- Nothing yet + 3.6.2 (2015-08-01) ================== diff --git a/pelican/__init__.py b/pelican/__init__.py index 0b5c62d4..2762ae71 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -26,7 +26,7 @@ from pelican.utils import (clean_output_dir, folder_watcher, file_watcher, maybe_pluralize) from pelican.writers import Writer -__version__ = "3.6.2" +__version__ = "3.6.3.dev" DEFAULT_CONFIG_NAME = 'pelicanconf.py' diff --git a/setup.py b/setup.py index b6b5c3d8..806464f2 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ CHANGELOG = open('docs/changelog.rst').read() setup( name="pelican", - version="3.6.2", + version="3.6.3.dev", url='http://getpelican.com/', author='Alexis Metaireau', author_email='authors@getpelican.com', From 2ca7aa8c80fa225021ff57da150ce25faaf315db Mon Sep 17 00:00:00 2001 From: Kubilay Kocak Date: Fri, 7 Aug 2015 14:46:12 +1000 Subject: [PATCH 0204/1173] Add missing *.markdown files to PyPI sdist The following file is missing from the PyPI source distribution (sdist) due to *.markdown not being referenced in MANIFEST.in: pelican/tests/content/article_with_markdown_extension.markdown This missing file appears to cause a number of errors running the test suite. An alternative to this change would be to rename the file to .md so that the file is covered by the existing *.md entry. --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index dcf9ea45..64ed4b3c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ include *.rst -recursive-include pelican *.html *.css *png *.in *.rst *.md *.mkd *.xml *.py +recursive-include pelican *.html *.css *png *.in *.rst *.markdown *.md *.mkd *.xml *.py include LICENSE THANKS docs/changelog.rst From 657ffdd75fc49f5364d7198e0f6ce80f1f473aa8 Mon Sep 17 00:00:00 2001 From: winlu Date: Sat, 8 Aug 2015 14:38:25 +0200 Subject: [PATCH 0205/1173] add warning for unknown replacement indicators If an unknown replacement indicator {bla} was used, it was ignored silently. This commit adds a warning when an unmatched indicator occurs to help identify the issue. closes #1794 --- pelican/contents.py | 5 +++++ pelican/tests/test_contents.py | 24 +++++++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 0e91933b..a6b8cc5f 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -248,6 +248,11 @@ class Content(object): origin = '/'.join((siteurl, Category(path, self.settings).url)) elif what == 'tag': origin = '/'.join((siteurl, Tag(path, self.settings).url)) + else: + logger.warning( + "Replacement Indicator '%s' not recognized, " + "skipping replacement", + what) # keep all other parts, such as query, fragment, etc. parts = list(value) diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 01d2ed3b..145a53b6 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals, absolute_import +import logging import locale import os.path import six @@ -11,7 +12,7 @@ from pelican.contents import (Page, Article, Static, URLWrapper, Author, Category) from pelican.settings import DEFAULT_CONFIG from pelican.signals import content_object_init -from pelican.tests.support import unittest, get_settings +from pelican.tests.support import LoggedTestCase, mute, unittest, get_settings from pelican.utils import (path_to_url, truncate_html_words, SafeDatetime, posix_join) @@ -413,10 +414,10 @@ class TestArticle(TestPage): self.assertEqual(article.save_as, 'obrien/csharp-stuff/fnord/index.html') -class TestStatic(unittest.TestCase): +class TestStatic(LoggedTestCase): def setUp(self): - + super(TestStatic, self).setUp() self.settings = get_settings( STATIC_SAVE_AS='{path}', STATIC_URL='{path}', @@ -578,3 +579,20 @@ class TestStatic(unittest.TestCase): content = page.get_content('') self.assertNotEqual(content, html) + + def test_unknown_link_syntax(self): + "{unknown} link syntax should trigger warning." + + html = 'link' + page = Page(content=html, + metadata={'title': 'fakepage'}, settings=self.settings, + source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), + context=self.context) + content = page.get_content('') + + self.assertEqual(content, html) + self.assertLogCountEqual( + count=1, + msg="Replacement Indicator 'unknown' not recognized, " + "skipping replacement", + level=logging.WARNING) From 45ff578f69df5c0f8eb3728569e9e583469d7f1c Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 10 Aug 2015 15:44:13 -0400 Subject: [PATCH 0206/1173] Added optional override_output to write_feed. The write_feed function now takes an override_output parameter that does the same thing as override_output does to write_file. Implemented for custom generators. --- pelican/writers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pelican/writers.py b/pelican/writers.py index e90a0004..36d2f038 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -79,7 +79,8 @@ class Writer(object): self._written_files.add(filename) return open(filename, 'w', encoding=encoding) - def write_feed(self, elements, context, path=None, feed_type='atom'): + def write_feed(self, elements, context, path=None, feed_type='atom', + override_output=False): """Generate a feed with the list of articles provided Return the feed. If no path or output_path is specified, just @@ -89,6 +90,9 @@ class Writer(object): :param context: the context to get the feed metadata. :param path: the path to output. :param feed_type: the feed type to use (atom or rss) + :param override_output: boolean telling if we can override previous + output with the same name (and if next files written with the same + name should be skipped to keep that one) """ if not is_selected_for_writing(self.settings, path): return @@ -115,7 +119,7 @@ class Writer(object): pass encoding = 'utf-8' if six.PY3 else None - with self._open_w(complete_path, encoding) as fp: + with self._open_w(complete_path, encoding, override_output) as fp: feed.write(fp, 'utf-8') logger.info('Writing %s', complete_path) From ed34ee1808699fa34d2170bf4fa8a26152f27514 Mon Sep 17 00:00:00 2001 From: jah Date: Tue, 11 Aug 2015 20:03:43 +0100 Subject: [PATCH 0207/1173] Correct render of article description meta in the simple theme: the template incorrectly assumed that the source metadata is made available as a list of strings. Fixes #1792. --- pelican/themes/simple/templates/article.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pelican/themes/simple/templates/article.html b/pelican/themes/simple/templates/article.html index d558183d..8ddda4d0 100644 --- a/pelican/themes/simple/templates/article.html +++ b/pelican/themes/simple/templates/article.html @@ -5,9 +5,9 @@ {% endfor %} - {% for description in article.description %} - - {% endfor %} + {% if article.description %} + + {% endif %} {% for tag in article.tags %} From ed83ad75a9aeaea97c6ab530b0679d1555c7691c Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Fri, 14 Aug 2015 16:34:25 -0700 Subject: [PATCH 0208/1173] Bump version 3.6.3 --- docs/changelog.rst | 5 +++++ docs/conf.py | 2 +- pelican/__init__.py | 2 +- setup.py | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 664e1310..70a96b30 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,11 @@ Release history ############### +3.6.3 (2015-08-14) +================== + +* Fix permissions issue in release tarball + 3.6.2 (2015-08-01) ================== diff --git a/docs/conf.py b/docs/conf.py index 15ae49b9..d3f58905 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ copyright = '2015, Alexis Metaireau and contributors' exclude_patterns = ['_build'] release = __version__ version = '.'.join(release.split('.')[:1]) -last_stable = '3.6.2' +last_stable = '3.6.3' rst_prolog = ''' .. |last_stable| replace:: :pelican-doc:`{0}` '''.format(last_stable) diff --git a/pelican/__init__.py b/pelican/__init__.py index 0b5c62d4..a738506a 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -26,7 +26,7 @@ from pelican.utils import (clean_output_dir, folder_watcher, file_watcher, maybe_pluralize) from pelican.writers import Writer -__version__ = "3.6.2" +__version__ = "3.6.3" DEFAULT_CONFIG_NAME = 'pelicanconf.py' diff --git a/setup.py b/setup.py index b6b5c3d8..bdcdea37 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ CHANGELOG = open('docs/changelog.rst').read() setup( name="pelican", - version="3.6.2", + version="3.6.3", url='http://getpelican.com/', author='Alexis Metaireau', author_email='authors@getpelican.com', From e06d0046b1e0ac988167810c0e8906d52d16960a Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Fri, 14 Aug 2015 17:24:29 -0700 Subject: [PATCH 0209/1173] Prepare version 3.6.4.dev0 for next development cycle --- docs/changelog.rst | 5 +++++ pelican/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 70a96b30..f52d6449 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,11 @@ Release history ############### +Next release +============ + +- Nothing yet + 3.6.3 (2015-08-14) ================== diff --git a/pelican/__init__.py b/pelican/__init__.py index a738506a..1af14897 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -26,7 +26,7 @@ from pelican.utils import (clean_output_dir, folder_watcher, file_watcher, maybe_pluralize) from pelican.writers import Writer -__version__ = "3.6.3" +__version__ = "3.6.4.dev0" DEFAULT_CONFIG_NAME = 'pelicanconf.py' diff --git a/setup.py b/setup.py index bdcdea37..86028424 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ CHANGELOG = open('docs/changelog.rst').read() setup( name="pelican", - version="3.6.3", + version="3.6.4.dev0", url='http://getpelican.com/', author='Alexis Metaireau', author_email='authors@getpelican.com', From 5dc6d2914e702c2b599a592fb8d6faeb179e3b0e Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 16 Aug 2015 09:57:55 -0700 Subject: [PATCH 0210/1173] Minor tweaks to FAQ docs --- docs/faq.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index 08df017d..195a4e33 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -12,10 +12,10 @@ How can I help? ================ There are several ways to help out. First, you can report any Pelican -suggestions or problems you might have via IRC or the `issue tracker -`_. If submitting an issue -report, please first check the existing issue list (both open and closed) in -order to avoid submitting a duplicate issue. +suggestions or problems you might have via IRC (preferred) or the +`issue tracker `_. If submitting +an issue report, please first check the existing issue list (both open and +closed) in order to avoid submitting a duplicate issue. If you want to contribute, please fork `the git repository `_, create a new feature branch, make @@ -25,20 +25,20 @@ section for more details. You can also contribute by creating themes and improving the documentation. -Is it mandatory to have a configuration file? +Is the Pelican settings file mandatory? ============================================= Configuration files are optional and are just an easy way to configure Pelican. For basic operations, it's possible to specify options while invoking Pelican via the command line. See ``pelican --help`` for more information. -Changes to the setting file take no effect +Changes to the settings file take no effect ========================================== When experimenting with different settings (especially the metadata ones) caching may interfere and the changes may not be visible. In -such cases disable caching with ``LOAD_CONTENT_CACHE = False`` or -use the ``--ignore-cache`` command-line switch. +such cases, ensure that caching is disabled via ``LOAD_CONTENT_CACHE = False`` +or use the ``--ignore-cache`` command-line switch. I'm creating my own theme. How do I use Pygments for syntax highlighting? ========================================================================= @@ -251,15 +251,15 @@ moved out of the pelican core and into a separate `plugin See the :ref:`plugins` documentation further information about the Pelican plugin system. -Since I upgraded Pelican my Pages are no longer rendered +Since I upgraded Pelican my pages are no longer rendered ======================================================== -Pages were available to Themes as lowercase ``pages`` and uppercase +Pages were available to themes as lowercase ``pages`` and uppercase ``PAGES``. To bring this inline with the :ref:`templates-variables` section, ``PAGES`` has been removed. This is quickly resolved by updating your theme to iterate over ``pages`` instead of ``PAGES``. Just replace:: {% for pg in PAGES %} -with something like:: +...with something like:: {% for pg in pages %} From bd3bec493eb395699e6564383a50e1b89a11d25f Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 16 Aug 2015 10:14:49 -0700 Subject: [PATCH 0211/1173] Fix reST error in docs --- docs/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.rst b/docs/faq.rst index 195a4e33..cd7f598a 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -260,6 +260,6 @@ to iterate over ``pages`` instead of ``PAGES``. Just replace:: {% for pg in PAGES %} -...with something like:: +with something like:: {% for pg in pages %} From de6bd537b51ccba24f0666ee5d732e3d8453b08e Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 16 Aug 2015 10:37:43 -0700 Subject: [PATCH 0212/1173] Fix FAQ header underlines --- docs/faq.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index cd7f598a..bb4cd9e6 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -9,7 +9,7 @@ What's the best way to communicate a problem, question, or suggestion? Please read our :doc:`feedback guidelines `. How can I help? -================ +=============== There are several ways to help out. First, you can report any Pelican suggestions or problems you might have via IRC (preferred) or the @@ -26,14 +26,14 @@ section for more details. You can also contribute by creating themes and improving the documentation. Is the Pelican settings file mandatory? -============================================= +======================================= Configuration files are optional and are just an easy way to configure Pelican. For basic operations, it's possible to specify options while invoking Pelican via the command line. See ``pelican --help`` for more information. Changes to the settings file take no effect -========================================== +=========================================== When experimenting with different settings (especially the metadata ones) caching may interfere and the changes may not be visible. In @@ -60,12 +60,12 @@ CSS file to your new theme:: Don't forget to import your ``pygment.css`` file from your main CSS file. How do I create my own theme? -============================== +============================= Please refer to :ref:`theming-pelican`. I want to use Markdown, but I got an error. -========================================================================== +=========================================== If you try to generate Markdown content without first installing the Markdown library, may see a message that says ``No valid files found in content``. @@ -77,7 +77,7 @@ permissions require it:: pip install markdown Can I use arbitrary metadata in my templates? -============================================== +============================================= Yes. For example, to include a modified date in a Markdown post, one could include the following at the top of the article:: From 44f9cfaaf1c4cb7553314be8c00357825520aaa9 Mon Sep 17 00:00:00 2001 From: derwinlu Date: Mon, 8 Jun 2015 12:50:35 +0200 Subject: [PATCH 0213/1173] add flake8 testing environment --- .travis.yml | 1 + tox.ini | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5d7d4a5f..1be196f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ python: - "2.7" env: - TOX_ENV=docs + - TOX_ENV=flake8 - TOX_ENV=py27 - TOX_ENV=py33 - TOX_ENV=py34 diff --git a/tox.ini b/tox.ini index ff16929e..34335b82 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{27,33,34},docs +envlist = py{27,33,34},docs,flake8 [testenv] basepython = @@ -27,3 +27,15 @@ deps = changedir = docs commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . _build/html + +[flake8] +application-import-names = pelican +import-order-style = cryptography + +[testenv:flake8] +basepython = python2.7 +deps = + flake8 <= 2.4.1 + git+https://github.com/public/flake8-import-order@2ac7052a4e02b4a8a0125a106d87465a3b9fd688 +commands = + flake8 pelican From 8993c55e6edc4993790e2aef182b924ff60b5239 Mon Sep 17 00:00:00 2001 From: derwinlu Date: Tue, 16 Jun 2015 09:25:09 +0200 Subject: [PATCH 0214/1173] fulfil pep8 standard --- pelican/__init__.py | 68 +++--- pelican/cache.py | 11 +- pelican/contents.py | 66 +++-- pelican/generators.py | 100 ++++---- pelican/log.py | 30 +-- pelican/paginator.py | 13 +- pelican/readers.py | 100 ++++---- pelican/rstdirectives.py | 14 +- pelican/server.py | 16 +- pelican/settings.py | 88 ++++--- pelican/signals.py | 3 +- pelican/tests/default_conf.py | 7 +- pelican/tests/support.py | 32 +-- pelican/tests/test_cache.py | 26 +- pelican/tests/test_contents.py | 138 ++++++----- pelican/tests/test_generators.py | 158 +++++++----- pelican/tests/test_importer.py | 161 ++++++++----- pelican/tests/test_paginator.py | 18 +- pelican/tests/test_pelican.py | 70 +++--- pelican/tests/test_readers.py | 86 ++++--- pelican/tests/test_rstdirectives.py | 7 +- pelican/tests/test_settings.py | 65 ++--- pelican/tests/test_urlwrappers.py | 3 +- pelican/tests/test_utils.py | 111 +++++---- pelican/tools/pelican_import.py | 361 +++++++++++++++++----------- pelican/tools/pelican_quickstart.py | 160 ++++++++---- pelican/tools/pelican_themes.py | 122 ++++++---- pelican/urlwrappers.py | 3 +- pelican/utils.py | 85 ++++--- pelican/writers.py | 46 ++-- tox.ini | 1 + 31 files changed, 1280 insertions(+), 889 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 1af14897..7fb8dfe4 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -1,45 +1,41 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function -import six +from __future__ import print_function, unicode_literals +import argparse +import collections +import locale +import logging import os import re import sys import time -import logging -import argparse -import locale -import collections + +import six # pelican.log has to be the first pelican module to be loaded # because logging.setLoggerClass has to be called before logging.getLogger -from pelican.log import init - +from pelican.log import init # noqa from pelican import signals - from pelican.generators import (ArticlesGenerator, PagesGenerator, - StaticGenerator, SourceFileGenerator, + SourceFileGenerator, StaticGenerator, TemplatePagesGenerator) from pelican.readers import Readers from pelican.settings import read_settings -from pelican.utils import (clean_output_dir, folder_watcher, - file_watcher, maybe_pluralize) +from pelican.utils import (clean_output_dir, file_watcher, + folder_watcher, maybe_pluralize) from pelican.writers import Writer __version__ = "3.6.4.dev0" - DEFAULT_CONFIG_NAME = 'pelicanconf.py' - - logger = logging.getLogger(__name__) class Pelican(object): def __init__(self, settings): - """ - Pelican initialisation, performs some checks on the environment before - doing anything else. + """Pelican initialisation + + Performs some checks on the environment before doing anything else. """ # define the default settings @@ -152,7 +148,7 @@ class Pelican(object): context = self.settings.copy() # Share these among all the generators and content objects: context['filenames'] = {} # maps source path to Content object or None - context['localsiteurl'] = self.settings['SITEURL'] + context['localsiteurl'] = self.settings['SITEURL'] generators = [ cls( @@ -190,23 +186,23 @@ class Pelican(object): if isinstance(g, PagesGenerator)) pluralized_articles = maybe_pluralize( - len(articles_generator.articles) + - len(articles_generator.translations), + (len(articles_generator.articles) + + len(articles_generator.translations)), 'article', 'articles') pluralized_drafts = maybe_pluralize( - len(articles_generator.drafts) + - len(articles_generator.drafts_translations), + (len(articles_generator.drafts) + + len(articles_generator.drafts_translations)), 'draft', 'drafts') pluralized_pages = maybe_pluralize( - len(pages_generator.pages) + - len(pages_generator.translations), + (len(pages_generator.pages) + + len(pages_generator.translations)), 'page', 'pages') pluralized_hidden_pages = maybe_pluralize( - len(pages_generator.hidden_pages) + - len(pages_generator.hidden_translations), + (len(pages_generator.hidden_pages) + + len(pages_generator.hidden_translations)), 'hidden page', 'hidden pages') @@ -243,8 +239,8 @@ class Pelican(object): return generators def get_writer(self): - writers = [ w for (_, w) in signals.get_writer.send(self) - if isinstance(w, type) ] + writers = [w for (_, w) in signals.get_writer.send(self) + if isinstance(w, type)] writers_found = len(writers) if writers_found == 0: return Writer(self.output_path, settings=self.settings) @@ -254,15 +250,15 @@ class Pelican(object): logger.debug('Found writer: %s', writer) else: logger.warning( - '%s writers found, using only first one: %s', + '%s writers found, using only first one: %s', writers_found, writer) return writer(self.output_path, settings=self.settings) def parse_arguments(): parser = argparse.ArgumentParser( - description="""A tool to generate a static blog, - with restructured text input files.""", + description='A tool to generate a static blog, ' + ' with restructured text input files.', formatter_class=argparse.ArgumentDefaultsHelpFormatter ) @@ -354,7 +350,7 @@ def get_config(args): # argparse returns bytes in Py2. There is no definite answer as to which # encoding argparse (or sys.argv) uses. # "Best" option seems to be locale.getpreferredencoding() - # ref: http://mail.python.org/pipermail/python-list/2006-October/405766.html + # http://mail.python.org/pipermail/python-list/2006-October/405766.html if not six.PY3: enc = locale.getpreferredencoding() for key in config: @@ -424,7 +420,8 @@ def main(): # Added static paths # Add new watchers and set them as modified - for static_path in set(new_static).difference(old_static): + new_watchers = set(new_static).difference(old_static) + for static_path in new_watchers: static_key = '[static]%s' % static_path watchers[static_key] = folder_watcher( os.path.join(pelican.path, static_path), @@ -434,7 +431,8 @@ def main(): # Removed static paths # Remove watchers and modified values - for static_path in set(old_static).difference(new_static): + old_watchers = set(old_static).difference(new_static) + for static_path in old_watchers: static_key = '[static]%s' % static_path watchers.pop(static_key) modified.pop(static_key) diff --git a/pelican/cache.py b/pelican/cache.py index d955ae08..e6c10cb9 100644 --- a/pelican/cache.py +++ b/pelican/cache.py @@ -1,16 +1,14 @@ +# -*- coding: utf-8 -*- from __future__ import unicode_literals import hashlib import logging import os -try: - import cPickle as pickle -except: - import pickle + +from six.moves import cPickle as pickle from pelican.utils import mkdir_p - logger = logging.getLogger(__name__) @@ -83,6 +81,7 @@ class FileStampDataCacher(FileDataCacher): """This sublcass additionally sets filestamp function and base path for filestamping operations """ + super(FileStampDataCacher, self).__init__(settings, cache_name, caching_policy, load_policy) @@ -118,6 +117,7 @@ class FileStampDataCacher(FileDataCacher): a hash for a function name in the hashlib module or an empty bytes string otherwise """ + try: return self._filestamp_func(filename) except (IOError, OSError, TypeError) as err: @@ -133,6 +133,7 @@ class FileStampDataCacher(FileDataCacher): Modification is checked by comparing the cached and current file stamp. """ + stamp, data = super(FileStampDataCacher, self).get_cached_data( filename, (None, default)) if stamp != self._get_file_stamp(filename): diff --git a/pelican/contents.py b/pelican/contents.py index a6b8cc5f..16d1f074 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -1,23 +1,24 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function -import six -from six.moves.urllib.parse import urlparse, urlunparse +from __future__ import print_function, unicode_literals import copy import locale import logging -import functools import os import re import sys import pytz +import six +from six.moves.urllib.parse import urlparse, urlunparse + from pelican import signals from pelican.settings import DEFAULT_CONFIG -from pelican.utils import (slugify, truncate_html_words, memoized, strftime, - python_2_unicode_compatible, deprecated_attribute, - path_to_url, posixize_path, set_date_tzinfo, SafeDatetime) +from pelican.utils import (SafeDatetime, deprecated_attribute, memoized, + path_to_url, posixize_path, + python_2_unicode_compatible, set_date_tzinfo, + slugify, strftime, truncate_html_words) # Import these so that they're avalaible when you import from pelican.contents. from pelican.urlwrappers import (URLWrapper, Author, Category, Tag) # NOQA @@ -66,7 +67,7 @@ class Content(object): # also keep track of the metadata attributes available 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() # First, read the authors from "authors", if not, fallback to "author" @@ -94,13 +95,16 @@ class Content(object): # create the slug if not existing, generate slug according to # setting of SLUG_ATTRIBUTE 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, - settings.get('SLUG_SUBSTITUTIONS', ())) - elif settings['SLUGIFY_SOURCE'] == 'basename' and source_path != None: - basename = os.path.basename(os.path.splitext(source_path)[0]) - self.slug = slugify(basename, - settings.get('SLUG_SUBSTITUTIONS', ())) + settings.get('SLUG_SUBSTITUTIONS', ())) + elif (settings['SLUGIFY_SOURCE'] == 'basename' and + source_path is not None): + basename = os.path.basename( + os.path.splitext(source_path)[0]) + self.slug = slugify( + basename, settings.get('SLUG_SUBSTITUTIONS', ())) self.source_path = source_path @@ -233,7 +237,8 @@ class Content(object): if isinstance(linked_content, Static): linked_content.attach_to(self) 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.", self.get_relative_source_path()) origin = '/'.join((siteurl, linked_content.url)) @@ -241,7 +246,7 @@ class Content(object): else: logger.warning( "Unable to find `%s`, skipping url replacement.", - value.geturl(), extra = { + value.geturl(), extra={ 'limit_msg': ("Other resources were not found " "and their urls not replaced")}) elif what == 'category': @@ -250,9 +255,9 @@ class Content(object): origin = '/'.join((siteurl, Tag(path, self.settings).url)) else: logger.warning( - "Replacement Indicator '%s' not recognized, " - "skipping replacement", - what) + "Replacement Indicator '%s' not recognized, " + "skipping replacement", + what) # keep all other parts, such as query, fragment, etc. parts = list(value) @@ -337,7 +342,9 @@ class Content(object): return posixize_path( 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']) )) @@ -402,9 +409,12 @@ class Static(Page): def attach_to(self, content): """Override our output directory with that of the given content object. """ - # Determine our file's new output path relative to the linking document. - # If it currently lives beneath the linking document's source directory, - # preserve that relationship on output. Otherwise, make it a sibling. + + # Determine our file's new output path relative to the linking + # document. If it currently lives beneath the linking + # document's source directory, preserve that relationship on output. + # Otherwise, make it a sibling. + linking_source_dir = os.path.dirname(content.source_path) tail_path = os.path.relpath(self.source_path, linking_source_dir) if tail_path.startswith(os.pardir + os.sep): @@ -420,11 +430,14 @@ class Static(Page): # 'some/content' with a file named 'index.html'.) Rather than trying # to figure it out by comparing the linking document's url and save_as # path, we simply build our new url from our new save_as path. + new_url = path_to_url(new_save_as) def _log_reason(reason): - logger.warning("The {attach} link in %s cannot relocate %s " - "because %s. Falling back to {filename} link behavior instead.", + logger.warning( + "The {attach} link in %s cannot relocate " + "%s because %s. Falling back to " + "{filename} link behavior instead.", content.get_relative_source_path(), self.get_relative_source_path(), reason, extra={'limit_msg': "More {attach} warnings silenced."}) @@ -452,5 +465,6 @@ def is_valid_content(content, f): content.check_properties() return True 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 diff --git a/pelican/generators.py b/pelican/generators.py index da651252..ff9a9d7c 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -1,28 +1,28 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +from __future__ import print_function, unicode_literals -import os -import six -import logging -import shutil -import fnmatch import calendar - +import fnmatch +import logging +import os +import shutil from codecs import open from collections import defaultdict from functools import partial from itertools import chain, groupby from operator import attrgetter -from jinja2 import (Environment, FileSystemLoader, PrefixLoader, ChoiceLoader, - BaseLoader, TemplateNotFound) +from jinja2 import (BaseLoader, ChoiceLoader, Environment, FileSystemLoader, + PrefixLoader, TemplateNotFound) +import six + +from pelican import signals from pelican.cache import FileStampDataCacher from pelican.contents import Article, Draft, Page, Static, is_valid_content from pelican.readers import Readers -from pelican.utils import (copy, process_translations, mkdir_p, DateFormatter, - python_2_unicode_compatible, posixize_path) -from pelican import signals +from pelican.utils import (DateFormatter, copy, mkdir_p, posixize_path, + process_translations, python_2_unicode_compatible) logger = logging.getLogger(__name__) @@ -31,6 +31,7 @@ logger = logging.getLogger(__name__) class PelicanTemplateNotFound(Exception): pass + @python_2_unicode_compatible class Generator(object): """Baseclass generator""" @@ -90,8 +91,9 @@ class Generator(object): try: self._templates[name] = self.env.get_template(name + '.html') except TemplateNotFound: - raise PelicanTemplateNotFound('[templates] unable to load %s.html from %s' - % (name, self._templates_path)) + raise PelicanTemplateNotFound( + '[templates] unable to load {}.html from {}'.format( + name, self._templates_path)) return self._templates[name] def _include_path(self, path, extensions=None): @@ -105,7 +107,7 @@ class Generator(object): extensions = tuple(self.readers.extensions) basename = os.path.basename(path) - #check IGNORE_FILES + # check IGNORE_FILES ignores = self.settings['IGNORE_FILES'] if any(fnmatch.fnmatch(basename, ignore) for ignore in ignores): return False @@ -122,8 +124,9 @@ class Generator(object): :param extensions: the list of allowed extensions (if False, all extensions are allowed) """ + # backward compatibility for older generators if isinstance(paths, six.string_types): - paths = [paths] # backward compatibility for older generators + paths = [paths] # group the exclude dir names by parent path, for use with os.walk() exclusions_by_dirpath = {} @@ -138,7 +141,8 @@ class Generator(object): root = os.path.join(self.path, path) if path else self.path if os.path.isdir(root): - for dirpath, dirs, temp_files in os.walk(root, followlinks=True): + for dirpath, dirs, temp_files in os.walk( + root, followlinks=True): drop = [] excl = exclusions_by_dirpath.get(dirpath, ()) for d in dirs: @@ -178,7 +182,8 @@ class Generator(object): before this method is called, even if they failed to process.) The path argument is expected to be relative to self.path. """ - return posixize_path(os.path.normpath(path)) in self.context['filenames'] + return (posixize_path(os.path.normpath(path)) + in self.context['filenames']) def _update_context(self, items): """Update the context with the given items from the currrent @@ -211,7 +216,8 @@ class CachingGenerator(Generator, FileStampDataCacher): readers_cache_name=(cls_name + '-Readers'), **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'] load_policy = cache_this_level and self.settings['LOAD_CONTENT_CACHE'] FileStampDataCacher.__init__(self, self.settings, cls_name, @@ -259,14 +265,14 @@ class ArticlesGenerator(CachingGenerator): def __init__(self, *args, **kwargs): """initialize properties""" - self.articles = [] # only articles in default language + self.articles = [] # only articles in default language self.translations = [] self.dates = {} self.tags = defaultdict(list) self.categories = defaultdict(list) self.related_posts = [] self.authors = defaultdict(list) - self.drafts = [] # only drafts in default language + self.drafts = [] # only drafts in default language self.drafts_translations = [] super(ArticlesGenerator, self).__init__(*args, **kwargs) signals.article_generator_init.send(self) @@ -282,8 +288,8 @@ class ArticlesGenerator(CachingGenerator): writer.write_feed(self.articles, self.context, self.settings['FEED_RSS'], feed_type='rss') - if (self.settings.get('FEED_ALL_ATOM') - or self.settings.get('FEED_ALL_RSS')): + if (self.settings.get('FEED_ALL_ATOM') or + self.settings.get('FEED_ALL_RSS')): all_articles = list(self.articles) for article in self.articles: all_articles.extend(article.translations) @@ -322,8 +328,8 @@ class ArticlesGenerator(CachingGenerator): self.settings['AUTHOR_FEED_RSS'] % auth.slug, feed_type='rss') - if (self.settings.get('TAG_FEED_ATOM') - or self.settings.get('TAG_FEED_RSS')): + if (self.settings.get('TAG_FEED_ATOM') or + self.settings.get('TAG_FEED_RSS')): for tag, arts in self.tags.items(): arts.sort(key=attrgetter('date'), reverse=True) if self.settings.get('TAG_FEED_ATOM'): @@ -336,8 +342,8 @@ class ArticlesGenerator(CachingGenerator): self.settings['TAG_FEED_RSS'] % tag.slug, feed_type='rss') - if (self.settings.get('TRANSLATION_FEED_ATOM') - or self.settings.get('TRANSLATION_FEED_RSS')): + if (self.settings.get('TRANSLATION_FEED_ATOM') or + self.settings.get('TRANSLATION_FEED_RSS')): translations_feeds = defaultdict(list) for article in chain(self.articles, self.translations): translations_feeds[article.lang].append(article) @@ -472,9 +478,9 @@ class ArticlesGenerator(CachingGenerator): """Generate drafts pages.""" for draft in chain(self.drafts_translations, self.drafts): write(draft.save_as, self.get_template(draft.template), - self.context, article=draft, category=draft.category, - override_output=hasattr(draft, 'override_save_as'), - blog=True, all_articles=self.articles) + self.context, article=draft, category=draft.category, + override_output=hasattr(draft, 'override_save_as'), + blog=True, all_articles=self.articles) def generate_pages(self, writer): """Generate the pages on the disk""" @@ -503,7 +509,8 @@ class ArticlesGenerator(CachingGenerator): exclude=self.settings['ARTICLE_EXCLUDES']): article_or_draft = self.get_cached_data(f, 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: article_or_draft = self.readers.read_file( base_path=self.path, path=f, content_class=Article, @@ -513,7 +520,8 @@ class ArticlesGenerator(CachingGenerator): context_signal=signals.article_generator_context, context_sender=self) 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)) self._add_failed_source_path(f) continue @@ -535,8 +543,9 @@ class ArticlesGenerator(CachingGenerator): self.add_source_path(article_or_draft) all_drafts.append(article_or_draft) else: - logger.error("Unknown status '%s' for file %s, skipping it.", - article_or_draft.status, f) + logger.error( + "Unknown status '%s' for file %s, skipping it.", + article_or_draft.status, f) self._add_failed_source_path(f) continue @@ -544,9 +553,9 @@ class ArticlesGenerator(CachingGenerator): self.add_source_path(article_or_draft) - - self.articles, self.translations = process_translations(all_articles, - order_by=self.settings['ARTICLE_ORDER_BY']) + self.articles, self.translations = process_translations( + all_articles, + order_by=self.settings['ARTICLE_ORDER_BY']) self.drafts, self.drafts_translations = \ process_translations(all_drafts) @@ -615,7 +624,8 @@ class PagesGenerator(CachingGenerator): context_signal=signals.page_generator_context, context_sender=self) 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)) self._add_failed_source_path(f) continue @@ -629,8 +639,9 @@ class PagesGenerator(CachingGenerator): elif page.status.lower() == "hidden": hidden_pages.append(page) else: - logger.error("Unknown status '%s' for file %s, skipping it.", - page.status, f) + logger.error( + "Unknown status '%s' for file %s, skipping it.", + page.status, f) self._add_failed_source_path(f) continue @@ -638,10 +649,11 @@ class PagesGenerator(CachingGenerator): self.add_source_path(page) - self.pages, self.translations = process_translations(all_pages, - order_by=self.settings['PAGE_ORDER_BY']) - self.hidden_pages, self.hidden_translations = ( - process_translations(hidden_pages)) + self.pages, self.translations = process_translations( + all_pages, + order_by=self.settings['PAGE_ORDER_BY']) + self.hidden_pages, self.hidden_translations = \ + process_translations(hidden_pages) self._update_context(('pages', 'hidden_pages')) diff --git a/pelican/log.py b/pelican/log.py index c83c5810..0f4b795b 100644 --- a/pelican/log.py +++ b/pelican/log.py @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +from __future__ import print_function, unicode_literals + +import locale +import logging +import os +import sys +from collections import Mapping, defaultdict + +import six __all__ = [ 'init' ] -import os -import sys -import logging -import locale - -from collections import defaultdict, Mapping - -import six class BaseFormatter(logging.Formatter): def __init__(self, fmt=None, datefmt=None): @@ -20,7 +20,8 @@ class BaseFormatter(logging.Formatter): super(BaseFormatter, self).__init__(fmt=FORMAT, datefmt=datefmt) def format(self, record): - record.__dict__['customlevelname'] = self._get_levelname(record.levelname) + customlevel = self._get_levelname(record.levelname) + record.__dict__['customlevelname'] = customlevel # format multiline messages 'nicely' to make it clear they are together record.msg = record.msg.replace('\n', '\n | ') return super(BaseFormatter, self).format(record) @@ -132,13 +133,13 @@ class SafeLogger(logging.Logger): def _log(self, level, msg, args, exc_info=None, extra=None): # if the only argument is a Mapping, Logger uses that for formatting # 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()},) # otherwise, format each arg else: args = tuple(self._decode_arg(arg) for arg in args) - super(SafeLogger, self)._log(level, msg, args, - exc_info=exc_info, extra=extra) + super(SafeLogger, self)._log( + level, msg, args, exc_info=exc_info, extra=extra) def _decode_arg(self, arg): ''' @@ -175,8 +176,7 @@ def init(level=None, handler=logging.StreamHandler()): logger = logging.getLogger() - if (os.isatty(sys.stdout.fileno()) - and not sys.platform.startswith('win')): + if os.isatty(sys.stdout.fileno()) and not sys.platform.startswith('win'): fmt = ANSIFormatter() else: fmt = TextFormatter() diff --git a/pelican/paginator.py b/pelican/paginator.py index 0189ec91..9aca550b 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -1,18 +1,15 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function -import six +from __future__ import print_function, unicode_literals -# From django.core.paginator -from collections import namedtuple import functools import logging import os - +from collections import namedtuple from math import ceil +import six + logger = logging.getLogger(__name__) - - PaginationRule = namedtuple( 'PaginationRule', 'min_page URL SAVE_AS', @@ -143,7 +140,7 @@ class Page(object): 'settings': self.settings, 'base_name': os.path.dirname(self.name), 'number_sep': '/', - 'extension': self.extension, + 'extension': self.extension, } if self.number == 1: diff --git a/pelican/readers.py b/pelican/readers.py index c1c8dbfa..bc4515e7 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +from __future__ import print_function, unicode_literals import logging import os @@ -9,24 +9,50 @@ import docutils import docutils.core import docutils.io from docutils.writers.html4css1 import HTMLTranslator -import six -# import the directives to have pygments support +import six +from six.moves.html_parser import HTMLParser + from pelican import rstdirectives # NOQA +from pelican import signals +from pelican.cache import FileStampDataCacher +from pelican.contents import Author, Category, Page, Tag +from pelican.utils import SafeDatetime, get_date, pelican_open, posixize_path + try: from markdown import Markdown except ImportError: Markdown = False # NOQA + try: from html import escape except ImportError: from cgi import escape -from six.moves.html_parser import HTMLParser -from pelican import signals -from pelican.cache import FileStampDataCacher -from pelican.contents import Page, Category, Tag, Author -from pelican.utils import get_date, pelican_open, SafeDatetime, posixize_path +# Metadata processors have no way to discard an unwanted value, so we have +# them return this value instead to signal that it should be discarded later. +# This means that _filter_discardable_metadata() must be called on processed +# metadata dicts before use, to remove the items with the special value. +_DISCARD = object() +METADATA_PROCESSORS = { + 'tags': lambda x, y: ([ + Tag(tag, y) + for tag in ensure_metadata_list(x) + ] or _DISCARD), + 'date': lambda x, y: get_date(x.replace('_', ' ')), + 'modified': lambda x, y: get_date(x), + 'status': lambda x, y: x.strip() or _DISCARD, + 'category': lambda x, y: _process_if_nonempty(Category, x, y), + 'author': lambda x, y: _process_if_nonempty(Author, x, y), + 'authors': lambda x, y: ([ + Author(author, y) + for author in ensure_metadata_list(x) + ] or _DISCARD), + 'slug': lambda x, y: x.strip() or _DISCARD, +} + +logger = logging.getLogger(__name__) + def ensure_metadata_list(text): """Canonicalize the format of a list of authors or tags. This works @@ -49,13 +75,6 @@ def ensure_metadata_list(text): return [v for v in (w.strip() for w in text) if v] -# Metadata processors have no way to discard an unwanted value, so we have -# them return this value instead to signal that it should be discarded later. -# This means that _filter_discardable_metadata() must be called on processed -# metadata dicts before use, to remove the items with the special value. -_DISCARD = object() - - def _process_if_nonempty(processor, name, settings): """Removes extra whitespace from name and applies a metadata processor. If name is empty or all whitespace, returns _DISCARD instead. @@ -64,28 +83,11 @@ def _process_if_nonempty(processor, name, settings): return processor(name, settings) if name else _DISCARD -METADATA_PROCESSORS = { - 'tags': lambda x, y: ([Tag(tag, y) for tag in ensure_metadata_list(x)] - or _DISCARD), - 'date': lambda x, y: get_date(x.replace('_', ' ')), - 'modified': lambda x, y: get_date(x), - 'status': lambda x, y: x.strip() or _DISCARD, - 'category': lambda x, y: _process_if_nonempty(Category, x, y), - 'author': lambda x, y: _process_if_nonempty(Author, x, y), - 'authors': lambda x, y: ([Author(author, y) - for author in ensure_metadata_list(x)] - or _DISCARD), - 'slug': lambda x, y: x.strip() or _DISCARD, -} - - def _filter_discardable_metadata(metadata): """Return a copy of a dict, minus any items marked as discardable.""" return {name: val for name, val in metadata.items() if val is not _DISCARD} -logger = logging.getLogger(__name__) - class BaseReader(object): """Base class to read files. @@ -267,8 +269,10 @@ class MarkdownReader(BaseReader): output[name] = self.process_metadata(name, summary) elif name in METADATA_PROCESSORS: if len(value) > 1: - logger.warning('Duplicate definition of `%s` ' - 'for %s. Using first one.', name, self._source_path) + logger.warning( + 'Duplicate definition of `%s` ' + 'for %s. Using first one.', + name, self._source_path) output[name] = self.process_metadata(name, value[0]) elif len(value) > 1: # handle list metadata as list of string @@ -380,7 +384,8 @@ class HTMLReader(BaseReader): def _handle_meta_tag(self, attrs): name = self._attr_value(attrs, 'name') if name is None: - attr_serialized = ', '.join(['{}="{}"'.format(k, v) for k, v in attrs]) + attr_list = ['{}="{}"'.format(k, v) for k, v in attrs] + attr_serialized = ', '.join(attr_list) logger.warning("Meta tag in file %s does not have a 'name' " "attribute, skipping. Attributes: %s", self._filename, attr_serialized) @@ -394,9 +399,9 @@ class HTMLReader(BaseReader): "Meta tag attribute 'contents' used in file %s, should" " be changed to 'content'", self._filename, - extra={'limit_msg': ("Other files have meta tag " - "attribute 'contents' that should " - "be changed to 'content'")}) + extra={'limit_msg': "Other files have meta tag " + "attribute 'contents' that should " + "be changed to 'content'"}) if name == 'keywords': name = 'tags' @@ -474,7 +479,8 @@ class Readers(FileStampDataCacher): path = os.path.abspath(os.path.join(base_path, 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__) if not fmt: @@ -486,7 +492,8 @@ class Readers(FileStampDataCacher): 'Pelican does not know how to parse %s', path) if preread_signal: - logger.debug('Signal %s.send(%s)', + logger.debug( + 'Signal %s.send(%s)', preread_signal.name, preread_sender) preread_signal.send(preread_sender) @@ -527,7 +534,9 @@ class Readers(FileStampDataCacher): def typogrify_wrapper(text): """Ensures ignore_tags feature is backward compatible""" try: - return typogrify(text, self.settings['TYPOGRIFY_IGNORE_TAGS']) + return typogrify( + text, + self.settings['TYPOGRIFY_IGNORE_TAGS']) except TypeError: return typogrify(text) @@ -539,8 +548,10 @@ class Readers(FileStampDataCacher): metadata['summary'] = typogrify_wrapper(metadata['summary']) if context_signal: - logger.debug('Signal %s.send(%s, )', - context_signal.name, context_sender) + logger.debug( + 'Signal %s.send(%s, )', + context_signal.name, + context_sender) context_signal.send(context_sender, metadata=metadata) return content_class(content=content, metadata=metadata, @@ -591,7 +602,8 @@ def default_metadata(settings=None, process=None): if process: value = process('category', value) metadata['category'] = value - if settings.get('DEFAULT_DATE', None) and settings['DEFAULT_DATE'] != 'fs': + if settings.get('DEFAULT_DATE', None) and \ + settings['DEFAULT_DATE'] != 'fs': metadata['date'] = SafeDatetime(*settings['DEFAULT_DATE']) return metadata diff --git a/pelican/rstdirectives.py b/pelican/rstdirectives.py index 1c25cc42..b52785dd 100644 --- a/pelican/rstdirectives.py +++ b/pelican/rstdirectives.py @@ -1,13 +1,17 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +from __future__ import print_function, unicode_literals + +import re from docutils import nodes, utils -from docutils.parsers.rst import directives, roles, Directive -from pygments.formatters import HtmlFormatter +from docutils.parsers.rst import Directive, directives, roles + from pygments import highlight -from pygments.lexers import get_lexer_by_name, TextLexer -import re +from pygments.formatters import HtmlFormatter +from pygments.lexers import TextLexer, get_lexer_by_name + import six + import pelican.settings as pys diff --git a/pelican/server.py b/pelican/server.py index f58ac085..9074135b 100644 --- a/pelican/server.py +++ b/pelican/server.py @@ -1,16 +1,18 @@ -from __future__ import print_function +# -*- coding: utf-8 -*- +from __future__ import print_function, unicode_literals + +import logging import os import sys -import logging - -from six.moves import SimpleHTTPServer as srvmod -from six.moves import socketserver try: from magic import from_file as magic_from_file except ImportError: magic_from_file = None +from six.moves import SimpleHTTPServer as srvmod +from six.moves import socketserver + class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler): SUFFIXES = ['', '.html', '/index.html'] @@ -54,12 +56,12 @@ if __name__ == '__main__': socketserver.TCPServer.allow_reuse_address = True try: - httpd = socketserver.TCPServer((SERVER, PORT), ComplexHTTPRequestHandler) + httpd = socketserver.TCPServer( + (SERVER, PORT), ComplexHTTPRequestHandler) except OSError as e: logging.error("Could not listen on port %s, server %s.", PORT, SERVER) sys.exit(getattr(e, 'exitcode', 1)) - logging.info("Serving at port %s, server %s.", PORT, SERVER) try: httpd.serve_forever() diff --git a/pelican/settings.py b/pelican/settings.py index c1a902cd..4d75333a 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -1,31 +1,32 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function -import six +from __future__ import print_function, unicode_literals import copy import inspect -import os import locale import logging +import os +from os.path import isabs +from posixpath import join as posix_join + +import six + +from pelican.log import LimitFilter try: # SourceFileLoader is the recommended way in 3.3+ from importlib.machinery import SourceFileLoader - load_source = lambda name, path: SourceFileLoader(name, path).load_module() + + def load_source(name, path): + return SourceFileLoader(name, path).load_module() except ImportError: # but it does not exist in 3.2-, so fall back to imp import imp load_source = imp.load_source -from os.path import isabs -from pelican.utils import posix_join - -from pelican.log import LimitFilter - logger = logging.getLogger(__name__) - DEFAULT_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'themes', 'notmyidea') DEFAULT_CONFIG = { @@ -131,7 +132,7 @@ DEFAULT_CONFIG = { 'LOAD_CONTENT_CACHE': False, 'WRITE_SELECTED': [], 'FORMATTED_FIELDS': ['summary'], - } +} PYGMENTS_RST_OPTIONS = None @@ -158,8 +159,20 @@ def read_settings(path=None, override=None): "has been deprecated (should be a list)") local_settings['PLUGIN_PATHS'] = [local_settings['PLUGIN_PATHS']] 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))) - if not isabs(pluginpath) else pluginpath for pluginpath in local_settings['PLUGIN_PATHS']] + def getabs(path, pluginpath): + if isabs(pluginpath): + return pluginpath + else: + path_dirname = os.path.dirname(path) + path_joined = os.path.join(path_dirname, pluginpath) + path_normed = os.path.normpath(path_joined) + path_absolute = os.path.abspath(path_normed) + return path_absolute + + pluginpath_list = [getabs(path, pluginpath) + for pluginpath + in local_settings['PLUGIN_PATHS']] + local_settings['PLUGIN_PATHS'] = pluginpath_list else: local_settings = copy.deepcopy(DEFAULT_CONFIG) @@ -199,13 +212,13 @@ def configure_settings(settings): settings. Also, specify the log messages to be ignored. """ - if not 'PATH' in settings or not os.path.isdir(settings['PATH']): + if 'PATH' not in settings or not os.path.isdir(settings['PATH']): raise Exception('You need to specify a path containing the content' ' (see pelican --help for more information)') # specify the log messages to be ignored - LimitFilter._ignore.update(set(settings.get('LOG_FILTER', - DEFAULT_CONFIG['LOG_FILTER']))) + log_filter = settings.get('LOG_FILTER', DEFAULT_CONFIG['LOG_FILTER']) + LimitFilter._ignore.update(set(log_filter)) # lookup the theme in "pelican/themes" if the given one doesn't exist if not os.path.isdir(settings['THEME']): @@ -223,19 +236,15 @@ def configure_settings(settings): settings['WRITE_SELECTED'] = [ os.path.abspath(path) for path in settings.get('WRITE_SELECTED', DEFAULT_CONFIG['WRITE_SELECTED']) - ] + ] # standardize strings to lowercase strings - for key in [ - 'DEFAULT_LANG', - ]: + for key in ['DEFAULT_LANG']: if key in settings: settings[key] = settings[key].lower() # standardize strings to lists - for key in [ - 'LOCALE', - ]: + for key in ['LOCALE']: if key in settings and isinstance(settings[key], six.string_types): settings[key] = [settings[key]] @@ -243,12 +252,13 @@ def configure_settings(settings): for key, types in [ ('OUTPUT_SOURCES_EXTENSION', six.string_types), ('FILENAME_METADATA', six.string_types), - ]: + ]: if key in settings and not isinstance(settings[key], types): value = settings.pop(key) - logger.warn('Detected misconfigured %s (%s), ' - 'falling back to the default (%s)', - key, value, DEFAULT_CONFIG[key]) + logger.warn( + 'Detected misconfigured %s (%s), ' + 'falling back to the default (%s)', + key, value, DEFAULT_CONFIG[key]) # try to set the different locales, fallback on the default. locales = settings.get('LOCALE', DEFAULT_CONFIG['LOCALE']) @@ -270,16 +280,16 @@ def configure_settings(settings): logger.warning("Removed extraneous trailing slash from SITEURL.") # If SITEURL is defined but FEED_DOMAIN isn't, # set FEED_DOMAIN to SITEURL - if not 'FEED_DOMAIN' in settings: + if 'FEED_DOMAIN' not in settings: settings['FEED_DOMAIN'] = settings['SITEURL'] # check content caching layer and warn of incompatibilities - if (settings.get('CACHE_CONTENT', False) and - settings.get('CONTENT_CACHING_LAYER', '') == 'generator' and - settings.get('WITH_FUTURE_DATES', DEFAULT_CONFIG['WITH_FUTURE_DATES'])): - logger.warning('WITH_FUTURE_DATES conflicts with ' - "CONTENT_CACHING_LAYER set to 'generator', " - "use 'reader' layer instead") + if settings.get('CACHE_CONTENT', False) and \ + settings.get('CONTENT_CACHING_LAYER', '') == 'generator' and \ + settings.get('WITH_FUTURE_DATES', False): + logger.warning( + "WITH_FUTURE_DATES conflicts with CONTENT_CACHING_LAYER " + "set to 'generator', use 'reader' layer instead") # Warn if feeds are generated with both SITEURL & FEED_DOMAIN undefined feed_keys = [ @@ -296,7 +306,7 @@ def configure_settings(settings): logger.warning('Feeds generated without SITEURL set properly may' ' not be valid') - if not 'TIMEZONE' in settings: + if 'TIMEZONE' not in settings: logger.warning( 'No timezone information specified in the settings. Assuming' ' your timezone is UTC for feed generation. Check ' @@ -321,7 +331,8 @@ def configure_settings(settings): old_key = key + '_DIR' new_key = key + '_PATHS' 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) settings[new_key] = [settings[old_key]] # also make a list del settings[old_key] @@ -365,8 +376,9 @@ def configure_settings(settings): for old, new, doc in [ ('LESS_GENERATOR', 'the Webassets plugin', None), ('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: message = 'The {} setting has been removed in favor of {}'.format( old, new) diff --git a/pelican/signals.py b/pelican/signals.py index 65c98df7..aeeea9f6 100644 --- a/pelican/signals.py +++ b/pelican/signals.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +from __future__ import print_function, unicode_literals + from blinker import signal # Run-level signals: diff --git a/pelican/tests/default_conf.py b/pelican/tests/default_conf.py index f38ef804..77c2b947 100644 --- a/pelican/tests/default_conf.py +++ b/pelican/tests/default_conf.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +from __future__ import print_function, unicode_literals AUTHOR = 'Alexis Métaireau' SITENAME = "Alexis' log" SITEURL = 'http://blog.notmyidea.org' @@ -31,17 +31,16 @@ DEFAULT_METADATA = {'yeah': 'it is'} # path-specific metadata EXTRA_PATH_METADATA = { 'extra/robots.txt': {'path': 'robots.txt'}, - } +} # static paths will be copied without parsing their contents STATIC_PATHS = [ 'pictures', 'extra/robots.txt', - ] +] FORMATTED_FIELDS = ['summary', 'custom_formatted_field'] # foobar will not be used, because it's not in caps. All configuration keys # have to be in caps foobar = "barbaz" - diff --git a/pelican/tests/support.py b/pelican/tests/support.py index 151fa3b6..3c2a203f 100644 --- a/pelican/tests/support.py +++ b/pelican/tests/support.py @@ -1,25 +1,26 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function -__all__ = ['get_article', 'unittest', ] +from __future__ import print_function, unicode_literals +import locale +import logging import os import re import subprocess import sys -from six import StringIO -import logging -from logging.handlers import BufferingHandler import unittest -import locale - -from functools import wraps from contextlib import contextmanager -from tempfile import mkdtemp +from functools import wraps +from logging.handlers import BufferingHandler from shutil import rmtree +from tempfile import mkdtemp + +from six import StringIO from pelican.contents import Article from pelican.settings import DEFAULT_CONFIG +__all__ = ['get_article', 'unittest', ] + @contextmanager def temporary_folder(): @@ -167,7 +168,7 @@ def get_settings(**kwargs): Set keyword arguments to override specific settings. """ settings = DEFAULT_CONFIG.copy() - for key,value in kwargs.items(): + for key, value in kwargs.items(): settings[key] = value return settings @@ -179,10 +180,13 @@ class LogCountHandler(BufferingHandler): logging.handlers.BufferingHandler.__init__(self, capacity) def count_logs(self, msg=None, level=None): - return len([l for l in self.buffer - if (msg is None or re.match(msg, l.getMessage())) - and (level is None or l.levelno == level) - ]) + return len([ + l + for l + in self.buffer + if (msg is None or re.match(msg, l.getMessage())) and + (level is None or l.levelno == level) + ]) class LoggedTestCase(unittest.TestCase): diff --git a/pelican/tests/test_cache.py b/pelican/tests/test_cache.py index 8a20c36b..006e421b 100644 --- a/pelican/tests/test_cache.py +++ b/pelican/tests/test_cache.py @@ -1,7 +1,14 @@ +# -*- coding: utf-8 -*- from __future__ import unicode_literals import os -from codecs import open + +from shutil import rmtree +from tempfile import mkdtemp + +from pelican.generators import ArticlesGenerator, PagesGenerator +from pelican.tests.support import get_settings, unittest + try: from unittest.mock import MagicMock except ImportError: @@ -10,12 +17,6 @@ except ImportError: except ImportError: MagicMock = False -from shutil import rmtree -from tempfile import mkdtemp - -from pelican.generators import ArticlesGenerator, PagesGenerator -from pelican.tests.support import unittest, get_settings - CUR_DIR = os.path.dirname(__file__) CONTENT_DIR = os.path.join(CUR_DIR, 'content') @@ -35,7 +36,6 @@ class TestCache(unittest.TestCase): settings['CACHE_PATH'] = self.temp_cache return settings - @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_article_object_caching(self): """Test Article objects caching at the generator level""" @@ -44,7 +44,6 @@ class TestCache(unittest.TestCase): settings['DEFAULT_DATE'] = (1970, 1, 1) settings['READERS'] = {'asc': None} - generator = ArticlesGenerator( context=settings.copy(), settings=settings, path=CONTENT_DIR, theme=settings['THEME'], output_path=None) @@ -108,7 +107,9 @@ class TestCache(unittest.TestCase): path=CONTENT_DIR, theme=settings['THEME'], output_path=None) generator.readers.read_file = MagicMock() 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') def test_page_object_caching(self): @@ -181,5 +182,6 @@ class TestCache(unittest.TestCase): path=CUR_DIR, theme=settings['THEME'], output_path=None) generator.readers.read_file = MagicMock() 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) diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 145a53b6..a3664383 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -1,20 +1,21 @@ -from __future__ import unicode_literals, absolute_import +# -*- coding: utf-8 -*- +from __future__ import absolute_import, unicode_literals -import logging import locale +import logging import os.path -import six - -from jinja2.utils import generate_lorem_ipsum +from posixpath import join as posix_join from sys import platform -from pelican.contents import (Page, Article, Static, URLWrapper, - Author, Category) +from jinja2.utils import generate_lorem_ipsum + +import six + +from pelican.contents import Article, Author, Category, Page, Static from pelican.settings import DEFAULT_CONFIG from pelican.signals import content_object_init -from pelican.tests.support import LoggedTestCase, mute, unittest, get_settings -from pelican.utils import (path_to_url, truncate_html_words, SafeDatetime, - posix_join) +from pelican.tests.support import LoggedTestCase, get_settings, unittest +from pelican.utils import SafeDatetime, path_to_url, truncate_html_words # generate one paragraph, enclosed with

    @@ -49,7 +50,7 @@ class TestPage(unittest.TestCase): # them to initialise object's attributes. metadata = {'foo': 'bar', 'foobar': 'baz', 'title': 'foobar', } page = Page(TEST_CONTENT, metadata=metadata, - context={'localsiteurl': ''}) + context={'localsiteurl': ''}) for key, value in metadata.items(): self.assertTrue(hasattr(page, key)) self.assertEqual(value, getattr(page, key)) @@ -139,14 +140,9 @@ class TestPage(unittest.TestCase): page = Page(**page_kwargs) # page.locale_date is a unicode string in both python2 and python3 - dt_date = dt.strftime(DEFAULT_CONFIG['DEFAULT_DATE_FORMAT']) - # 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) - if type(dt_date) != type(u''): - # python2: - dt_date = unicode(dt_date, 'utf8') + dt_date = dt.strftime(DEFAULT_CONFIG['DEFAULT_DATE_FORMAT']) - self.assertEqual(page.locale_date, dt_date ) + self.assertEqual(page.locale_date, dt_date) page_kwargs['settings'] = get_settings() # I doubt this can work on all platforms ... @@ -307,10 +303,14 @@ class TestPage(unittest.TestCase): args['settings'] = get_settings() args['source_path'] = 'content' args['context']['filenames'] = { - 'images/poster.jpg': type(cls_name, (object,), {'url': 'images/poster.jpg'}), - 'assets/video.mp4': type(cls_name, (object,), {'url': 'assets/video.mp4'}), - 'images/graph.svg': type(cls_name, (object,), {'url': 'images/graph.svg'}), - 'reference.rst': type(cls_name, (object,), {'url': 'reference.html'}), + 'images/poster.jpg': type( + cls_name, (object,), {'url': 'images/poster.jpg'}), + 'assets/video.mp4': type( + cls_name, (object,), {'url': 'assets/video.mp4'}), + 'images/graph.svg': type( + cls_name, (object,), {'url': 'images/graph.svg'}), + 'reference.rst': type( + cls_name, (object,), {'url': 'reference.html'}), } # video.poster @@ -325,20 +325,25 @@ class TestPage(unittest.TestCase): content, 'There is a video with poster ' '' ) # object.data args['content'] = ( 'There is a svg object ' - '' + '' + '' ) content = Page(**args).get_content('http://notmyidea.org') self.assertEqual( content, 'There is a svg object ' - '' + '' + '' ) # blockquote.cite @@ -350,7 +355,9 @@ class TestPage(unittest.TestCase): self.assertEqual( content, 'There is a blockquote with cite attribute ' - '

    blah blah
    ' + '
    ' + 'blah blah' + '
    ' ) def test_intrasite_link_markdown_spaces(self): @@ -401,17 +408,19 @@ class TestArticle(TestPage): def test_slugify_category_author(self): settings = get_settings() - settings['SLUG_SUBSTITUTIONS'] = [ ('C#', 'csharp') ] + settings['SLUG_SUBSTITUTIONS'] = [('C#', 'csharp')] settings['ARTICLE_URL'] = '{author}/{category}/{slug}/' settings['ARTICLE_SAVE_AS'] = '{author}/{category}/{slug}/index.html' article_kwargs = self._copy_page_kwargs() 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['settings'] = settings article = Article(**article_kwargs) 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): @@ -426,7 +435,8 @@ class TestStatic(LoggedTestCase): self.context = self.settings.copy() 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} @@ -436,8 +446,10 @@ class TestStatic(LoggedTestCase): def test_attach_to_same_dir(self): """attach_to() overrides a static file's save_as and url. """ - page = Page(content="fake page", - metadata={'title': 'fakepage'}, settings=self.settings, + page = Page( + content="fake page", + metadata={'title': 'fakepage'}, + settings=self.settings, source_path=os.path.join('dir', 'fakepage.md')) self.static.attach_to(page) @@ -449,7 +461,7 @@ class TestStatic(LoggedTestCase): """attach_to() preserves dirs inside the linking document dir. """ 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) expected_save_as = os.path.join('outpages', 'dir', 'foo.jpg') @@ -460,8 +472,8 @@ class TestStatic(LoggedTestCase): """attach_to() ignores dirs outside the linking document dir. """ page = Page(content="fake page", - metadata={'title': 'fakepage'}, settings=self.settings, - source_path=os.path.join('dir', 'otherdir', 'fakepage.md')) + metadata={'title': 'fakepage'}, settings=self.settings, + source_path=os.path.join('dir', 'otherdir', 'fakepage.md')) self.static.attach_to(page) expected_save_as = os.path.join('outpages', 'foo.jpg') @@ -472,8 +484,8 @@ class TestStatic(LoggedTestCase): """attach_to() does nothing when called a second time. """ page = Page(content="fake page", - metadata={'title': 'fakepage'}, settings=self.settings, - source_path=os.path.join('dir', 'fakepage.md')) + metadata={'title': 'fakepage'}, settings=self.settings, + source_path=os.path.join('dir', 'fakepage.md')) self.static.attach_to(page) @@ -481,8 +493,10 @@ class TestStatic(LoggedTestCase): otherdir_settings.update(dict( PAGE_SAVE_AS=os.path.join('otherpages', '{slug}.html'), PAGE_URL='otherpages/{slug}.html')) - otherdir_page = Page(content="other page", - metadata={'title': 'otherpage'}, settings=otherdir_settings, + otherdir_page = Page( + content="other page", + metadata={'title': 'otherpage'}, + settings=otherdir_settings, source_path=os.path.join('dir', 'otherpage.md')) self.static.attach_to(otherdir_page) @@ -497,8 +511,10 @@ class TestStatic(LoggedTestCase): """ original_save_as = self.static.save_as - page = Page(content="fake page", - metadata={'title': 'fakepage'}, settings=self.settings, + page = Page( + content="fake page", + metadata={'title': 'fakepage'}, + settings=self.settings, source_path=os.path.join('dir', 'fakepage.md')) self.static.attach_to(page) @@ -511,8 +527,10 @@ class TestStatic(LoggedTestCase): """ original_url = self.static.url - page = Page(content="fake page", - metadata={'title': 'fakepage'}, settings=self.settings, + page = Page( + content="fake page", + metadata={'title': 'fakepage'}, + settings=self.settings, source_path=os.path.join('dir', 'fakepage.md')) self.static.attach_to(page) @@ -523,13 +541,15 @@ class TestStatic(LoggedTestCase): """attach_to() does not override paths that were overridden elsewhere. (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'), settings=self.settings, 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, source_path=os.path.join('dir', 'fakepage.md')) @@ -542,13 +562,16 @@ class TestStatic(LoggedTestCase): """{attach} link syntax triggers output path override & url replacement. """ html = 'link' - page = Page(content=html, - metadata={'title': 'fakepage'}, settings=self.settings, + page = Page( + content=html, + metadata={'title': 'fakepage'}, + settings=self.settings, source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), context=self.context) content = page.get_content('') - self.assertNotEqual(content, html, + self.assertNotEqual( + content, html, "{attach} link syntax did not trigger URL replacement.") expected_save_as = os.path.join('outpages', 'foo.jpg') @@ -561,7 +584,8 @@ class TestStatic(LoggedTestCase): html = 'link' 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'), context=self.context) content = page.get_content('') @@ -572,8 +596,10 @@ class TestStatic(LoggedTestCase): "{category} link syntax triggers url replacement." html = 'link' - page = Page(content=html, - metadata={'title': 'fakepage'}, settings=self.settings, + page = Page( + content=html, + metadata={'title': 'fakepage'}, + settings=self.settings, source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), context=self.context) content = page.get_content('') @@ -588,11 +614,11 @@ class TestStatic(LoggedTestCase): metadata={'title': 'fakepage'}, settings=self.settings, source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), context=self.context) - content = page.get_content('') + content = page.get_content('') self.assertEqual(content, html) self.assertLogCountEqual( - count=1, - msg="Replacement Indicator 'unknown' not recognized, " - "skipping replacement", - level=logging.WARNING) + count=1, + msg="Replacement Indicator 'unknown' not recognized, " + "skipping replacement", + level=logging.WARNING) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index c424b60f..2cfca04f 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -1,8 +1,18 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import locale import os + from codecs import open +from shutil import rmtree +from tempfile import mkdtemp + +from pelican.generators import (ArticlesGenerator, Generator, PagesGenerator, + StaticGenerator, TemplatePagesGenerator) +from pelican.tests.support import get_settings, unittest +from pelican.writers import Writer + try: from unittest.mock import MagicMock except ImportError: @@ -10,14 +20,7 @@ except ImportError: from mock import MagicMock except ImportError: MagicMock = False -from shutil import rmtree -from tempfile import mkdtemp -from pelican.generators import (Generator, ArticlesGenerator, PagesGenerator, - StaticGenerator, TemplatePagesGenerator) -from pelican.writers import Writer -from pelican.tests.support import unittest, get_settings -import locale CUR_DIR = os.path.dirname(__file__) CONTENT_DIR = os.path.join(CUR_DIR, 'content') @@ -35,7 +38,6 @@ class TestGenerator(unittest.TestCase): def tearDown(self): locale.setlocale(locale.LC_ALL, self.old_locale) - def test_include_path(self): self.settings['IGNORE_FILES'] = {'ignored1.rst', 'ignored2.rst'} @@ -52,7 +54,8 @@ class TestGenerator(unittest.TestCase): """Test that Generator.get_files() properly excludes directories. """ # 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, path=os.path.join(CUR_DIR, 'nested_content'), theme=self.settings['THEME'], output_path=None) @@ -60,34 +63,42 @@ class TestGenerator(unittest.TestCase): filepaths = generator.get_files(paths=['maindir']) found_files = {os.path.basename(f) for f in filepaths} 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") # Test string as `paths` argument rather than list filepaths = generator.get_files(paths='maindir') found_files = {os.path.basename(f) for f in filepaths} 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") filepaths = generator.get_files(paths=[''], exclude=['maindir']) 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") - self.assertNotIn('subdir.md', found_files, + self.assertNotIn( + 'subdir.md', found_files, "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')]) 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") filepaths = generator.get_files(paths=[''], exclude=['subdir']) 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") + class TestArticlesGenerator(unittest.TestCase): @classmethod @@ -96,7 +107,7 @@ class TestArticlesGenerator(unittest.TestCase): settings['DEFAULT_CATEGORY'] = 'Default' settings['DEFAULT_DATE'] = (1970, 1, 1) settings['READERS'] = {'asc': None} - settings['CACHE_CONTENT'] = False # cache not needed for this logic tests + settings['CACHE_CONTENT'] = False cls.generator = ArticlesGenerator( context=settings.copy(), settings=settings, @@ -152,25 +163,30 @@ class TestArticlesGenerator(unittest.TestCase): ['Test mkd File', 'published', 'test', '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', 'Default', 'article'], ['This is an article with category !', 'published', 'yeah', 'article'], - ['This is an article with multiple authors!', 'published', 'Default', 'article'], - ['This is an article with multiple authors!', '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 with multiple authors!', 'published', + 'Default', 'article'], + ['This is an article with multiple authors!', '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', - 'article'], + 'article'], ['This is an article without category !', 'published', 'TestCategory', 'article'], ['An Article With Code Block To Test Typogrify Ignore', - 'published', 'Default', 'article'], - ['マックOS X 10.8でパイソンとVirtualenvをインストールと設定', 'published', - '指導書', 'article'], + 'published', 'Default', 'article'], + ['マックOS X 10.8でパイソンとVirtualenvをインストールと設定', + 'published', '指導書', 'article'], ] self.assertEqual(sorted(articles_expected), sorted(self.articles)) @@ -292,7 +308,7 @@ class TestArticlesGenerator(unittest.TestCase): generator.generate_period_archives(write) dates = [d for d in generator.dates if d.date.year == 1970] 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,) write.assert_called_with("posts/1970/index.html", generator.get_template("period_archives"), @@ -300,37 +316,42 @@ class TestArticlesGenerator(unittest.TestCase): blog=True, dates=dates) 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( context=settings, settings=settings, path=CONTENT_DIR, theme=settings['THEME'], output_path=None) generator.generate_context() write = MagicMock() generator.generate_period_archives(write) - dates = [d for d in generator.dates if d.date.year == 1970 - and d.date.month == 1] + dates = [d for d in generator.dates + if d.date.year == 1970 and d.date.month == 1] self.assertEqual(len(dates), 1) 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", generator.get_template("period_archives"), settings, blog=True, dates=dates) 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( context=settings, settings=settings, path=CONTENT_DIR, theme=settings['THEME'], output_path=None) generator.generate_context() write = MagicMock() generator.generate_period_archives(write) - dates = [d for d in generator.dates if d.date.year == 1970 - and d.date.month == 1 - and d.date.day == 1] + dates = [ + d for d in generator.dates if + d.date.year == 1970 and + d.date.month == 1 and + d.date.day == 1 + ] self.assertEqual(len(dates), 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", generator.get_template("period_archives"), settings, @@ -347,11 +368,14 @@ class TestArticlesGenerator(unittest.TestCase): def test_generate_authors(self): """Check authors generation.""" 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) # test for slug 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)) def test_standard_metadata_in_default_metadata(self): @@ -391,7 +415,6 @@ class TestArticlesGenerator(unittest.TestCase): settings = get_settings(filenames={}) settings['DEFAULT_CATEGORY'] = 'Default' settings['DEFAULT_DATE'] = (1970, 1, 1) - settings['CACHE_CONTENT'] = False # cache not needed for this logic tests settings['ARTICLE_ORDER_BY'] = 'title' generator = ArticlesGenerator( @@ -420,7 +443,8 @@ class TestArticlesGenerator(unittest.TestCase): 'This is a super article !', 'This is a super article !', 'This is an article with category !', - 'This is an article with multiple authors in lastname, firstname format!', + ('This is an article with multiple authors in lastname, ' + 'firstname format!'), 'This is an article with multiple authors in list format!', 'This is an article with multiple authors!', 'This is an article with multiple authors!', @@ -435,7 +459,6 @@ class TestArticlesGenerator(unittest.TestCase): settings = get_settings(filenames={}) settings['DEFAULT_CATEGORY'] = 'Default' settings['DEFAULT_DATE'] = (1970, 1, 1) - settings['CACHE_CONTENT'] = False # cache not needed for this logic tests settings['ARTICLE_ORDER_BY'] = 'reversed-title' generator = ArticlesGenerator( @@ -561,7 +584,7 @@ class TestPageGenerator(unittest.TestCase): are generated correctly on pages """ 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['DEFAULT_DATE'] = (1970, 1, 1) @@ -586,7 +609,6 @@ class TestTemplatePagesGenerator(unittest.TestCase): self.old_locale = locale.setlocale(locale.LC_ALL) locale.setlocale(locale.LC_ALL, str('C')) - def tearDown(self): rmtree(self.temp_content) rmtree(self.temp_output) @@ -632,59 +654,67 @@ class TestStaticGenerator(unittest.TestCase): def test_static_excludes(self): """Test that StaticGenerator respects STATIC_EXCLUDES. """ - settings = get_settings(STATIC_EXCLUDES=['subdir'], - PATH=self.content_path, STATIC_PATHS=['']) + settings = get_settings( + STATIC_EXCLUDES=['subdir'], + PATH=self.content_path, + STATIC_PATHS=[''], + filenames={}) context = settings.copy() - context['filenames'] = {} - StaticGenerator(context=context, settings=settings, + StaticGenerator( + context=context, settings=settings, path=settings['PATH'], output_path=None, theme=settings['THEME']).generate_context() 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") - self.assertIn('fake_image.jpg', staticnames, + self.assertIn( + 'fake_image.jpg', staticnames, "StaticGenerator skipped a file that it should have included") def test_static_exclude_sources(self): """Test that StaticGenerator respects STATIC_EXCLUDE_SOURCES. """ - # Test STATIC_EXCLUDE_SOURCES=True - settings = get_settings(STATIC_EXCLUDE_SOURCES=True, - PATH=self.content_path, PAGE_PATHS=[''], STATIC_PATHS=[''], - CACHE_CONTENT=False) + settings = get_settings( + STATIC_EXCLUDE_SOURCES=True, + PATH=self.content_path, + PAGE_PATHS=[''], + STATIC_PATHS=[''], + CACHE_CONTENT=False, + filenames={}) context = settings.copy() - context['filenames'] = {} for generator_class in (PagesGenerator, StaticGenerator): - generator_class(context=context, settings=settings, + generator_class( + context=context, settings=settings, path=settings['PATH'], output_path=None, theme=settings['THEME']).generate_context() 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") - # Test STATIC_EXCLUDE_SOURCES=False - settings.update(STATIC_EXCLUDE_SOURCES=False) context = settings.copy() context['filenames'] = {} for generator_class in (PagesGenerator, StaticGenerator): - generator_class(context=context, settings=settings, + generator_class( + context=context, settings=settings, path=settings['PATH'], output_path=None, theme=settings['THEME']).generate_context() 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") - diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 4ace5ccc..6af59212 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -1,16 +1,19 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +# -*- coding: utf-8 -*- +from __future__ import print_function, unicode_literals +import locale import os import re -import locale from codecs import open -from pelican.tools.pelican_import import wp2fields, fields2pelican, decode_wp_content, build_header, build_markdown_header, get_attachments, download_attachments -from pelican.tests.support import (unittest, temporary_folder, mute, - skipIfNoExecutable) -from pelican.utils import slugify, path_to_file_url +from pelican.tests.support import (mute, skipIfNoExecutable, temporary_folder, + unittest) +from pelican.tools.pelican_import import (build_header, build_markdown_header, + decode_wp_content, + download_attachments, fields2pelican, + get_attachments, wp2fields) +from pelican.utils import path_to_file_url, slugify CUR_DIR = os.path.abspath(os.path.dirname(__file__)) WORDPRESS_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'wordpressexport.xml') @@ -32,7 +35,6 @@ except ImportError: LXML = False - @skipIfNoExecutable(['pandoc', '--version']) @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') class TestWordpressXmlImporter(unittest.TestCase): @@ -48,17 +50,19 @@ class TestWordpressXmlImporter(unittest.TestCase): def test_ignore_empty_posts(self): self.assertTrue(self.posts) - for title, content, fname, date, author, categ, tags, status, kind, format in self.posts: - self.assertTrue(title.strip()) + for (title, content, fname, date, author, + categ, tags, status, kind, format) in self.posts: + self.assertTrue(title.strip()) def test_recognise_page_kind(self): """ Check that we recognise pages in wordpress, as opposed to posts """ self.assertTrue(self.posts) # Collect (title, filename, kind) of non-empty posts recognised as page pages_data = [] - for title, content, fname, date, author, categ, tags, status, kind, format in self.posts: - if kind == 'page': - pages_data.append((title, fname)) + for (title, content, fname, date, author, + categ, tags, status, kind, format) in self.posts: + if kind == 'page': + pages_data.append((title, fname)) self.assertEqual(2, len(pages_data)) self.assertEqual(('Page', 'contact'), pages_data[0]) self.assertEqual(('Empty Page', 'empty'), pages_data[1]) @@ -67,7 +71,8 @@ class TestWordpressXmlImporter(unittest.TestCase): silent_f2p = mute(True)(fields2pelican) test_post = filter(lambda p: p[0].startswith("Empty Page"), self.posts) 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)) def test_dircat(self): @@ -75,10 +80,11 @@ class TestWordpressXmlImporter(unittest.TestCase): test_posts = [] for post in self.posts: # check post kind - if len(post[5]) > 0: # Has a category + if len(post[5]) > 0: # Has a category test_posts.append(post) 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 for post in test_posts: name = post[2] @@ -92,25 +98,33 @@ class TestWordpressXmlImporter(unittest.TestCase): def test_unless_custom_post_all_items_should_be_pages_or_posts(self): self.assertTrue(self.posts) pages_data = [] - for title, content, fname, date, author, categ, tags, status, kind, format in self.posts: - if kind == 'page' or kind == 'article': - pass - else: - pages_data.append((title, fname)) + for (title, content, fname, date, author, categ, + tags, status, kind, format) in self.posts: + if kind == 'page' or kind == 'article': + pass + else: + pages_data.append((title, fname)) self.assertEqual(0, len(pages_data)) def test_recognise_custom_post_type(self): self.assertTrue(self.custposts) cust_data = [] - for title, content, fname, date, author, categ, tags, status, kind, format in self.custposts: - if kind == 'article' or kind == 'page': - pass - else: - cust_data.append((title, kind)) + for (title, content, fname, date, author, categ, + tags, status, kind, format) in self.custposts: + if kind == 'article' or kind == 'page': + pass + else: + cust_data.append((title, kind)) self.assertEqual(3, len(cust_data)) - self.assertEqual(('A custom post in category 4', 'custom1'), cust_data[0]) - 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]) + self.assertEqual( + ('A custom post in category 4', 'custom1'), + cust_data[0]) + 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): silent_f2p = mute(True)(fields2pelican) @@ -122,7 +136,8 @@ class TestWordpressXmlImporter(unittest.TestCase): else: test_posts.append(post) 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 for post in test_posts: name = post[2] @@ -144,7 +159,7 @@ class TestWordpressXmlImporter(unittest.TestCase): test_posts.append(post) with temporary_folder() as temp: fnames = list(silent_f2p(test_posts, 'markdown', temp, - wp_custpost=True, dircat=True)) + wp_custpost=True, dircat=True)) index = 0 for post in test_posts: name = post[2] @@ -157,7 +172,7 @@ class TestWordpressXmlImporter(unittest.TestCase): index += 1 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) test_posts = [] for post in self.custposts: @@ -166,7 +181,7 @@ class TestWordpressXmlImporter(unittest.TestCase): test_posts.append(post) with temporary_folder() as temp: fnames = list(silent_f2p(test_posts, 'markdown', temp, - wp_custpost=True, dirpage=False)) + wp_custpost=True, dirpage=False)) index = 0 for post in test_posts: name = post[2] @@ -175,7 +190,6 @@ class TestWordpressXmlImporter(unittest.TestCase): out_name = fnames[index] self.assertFalse(out_name.endswith(filename)) - def test_can_toggle_raw_html_code_parsing(self): def r(f): with open(f, encoding='utf-8') as infile: @@ -184,10 +198,12 @@ class TestWordpressXmlImporter(unittest.TestCase): 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(' entities in the" - " title. You can't miss them.") + self.assertTrue(title, "A normal post with some entities in " + "the title. You can't miss them.") self.assertNotIn('&', title) def test_decode_wp_content_returns_empty(self): @@ -216,14 +233,18 @@ class TestWordpressXmlImporter(unittest.TestCase): encoded_content = encoded_file.read() with open(WORDPRESS_DECODED_CONTENT_SAMPLE, 'r') as decoded_file: 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 r(f): with open(f, encoding='utf-8') as infile: return infile.read() 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: md = [r(f) for f in silent_f2p(test_post, 'markdown', temp)][0] self.assertTrue(re.search(r'\s+a = \[1, 2, 3\]', md)) @@ -231,14 +252,17 @@ class TestWordpressXmlImporter(unittest.TestCase): 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) - 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 r(f): with open(f, encoding='utf-8') as infile: return infile.read() 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: 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) @@ -285,26 +309,29 @@ class TestBuildHeader(unittest.TestCase): self.assertEqual(build_header(*header_data), expected_docutils) self.assertEqual(build_markdown_header(*header_data), expected_md) - def test_build_header_with_east_asian_characters(self): header = build_header('これは広い幅の文字だけで構成されたタイトルです', - None, None, None, None, None) + None, None, None, None, None) self.assertEqual(header, - 'これは広い幅の文字だけで構成されたタイトルです\n' + - '##############################################\n\n') + ('これは広い幅の文字だけで構成されたタイトルです\n' + '##############################################' + '\n\n')) def test_galleries_added_to_header(self): - header = build_header('test', None, None, None, None, - None, attachments=['output/test1', 'output/test2']) - self.assertEqual(header, 'test\n####\n' + ':attachments: output/test1, ' - + 'output/test2\n\n') + header = build_header('test', None, None, None, None, None, + attachments=['output/test1', 'output/test2']) + self.assertEqual(header, ('test\n####\n' + ':attachments: output/test1, ' + 'output/test2\n\n')) def test_galleries_added_to_markdown_header(self): header = build_markdown_header('test', None, None, None, None, None, - attachments=['output/test1', 'output/test2']) - self.assertEqual(header, 'Title: test\n' + 'Attachments: output/test1, ' - + 'output/test2\n\n') + attachments=['output/test1', + 'output/test2']) + self.assertEqual( + header, + 'Title: test\nAttachments: output/test1, output/test2\n\n') @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') @@ -326,14 +353,24 @@ class TestWordpressXMLAttachements(unittest.TestCase): self.assertTrue(self.attachments) for post in self.attachments.keys(): 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') + expected = ('https://upload.wikimedia.org/wikipedia/commons/' + 'thumb/2/2c/Pelican_lakes_entrance02.jpg/' + '240px-Pelican_lakes_entrance02.jpg') + self.assertEqual(self.attachments[post][0], expected) elif post == 'with-excerpt': - self.assertTrue(self.attachments[post][0] == 'http://thisurlisinvalid.notarealdomain/not_an_image.jpg') - self.assertTrue(self.attachments[post][1] == 'http://en.wikipedia.org/wiki/File:Pelikan_Walvis_Bay.jpg') + expected_invalid = ('http://thisurlisinvalid.notarealdomain/' + 'not_an_image.jpg') + expected_pelikan = ('http://en.wikipedia.org/wiki/' + 'File:Pelikan_Walvis_Bay.jpg') + self.assertEqual(self.attachments[post][0], expected_invalid) + self.assertEqual(self.attachments[post][1], expected_pelikan) elif post == 'with-tags': - self.assertTrue(self.attachments[post][0] == 'http://thisurlisinvalid.notarealdomain') + expected_invalid = ('http://thisurlisinvalid.notarealdomain') + self.assertEqual(self.attachments[post][0], expected_invalid) 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): real_file = os.path.join(CUR_DIR, 'content/article.rst') @@ -344,4 +381,6 @@ class TestWordpressXMLAttachements(unittest.TestCase): locations = list(silent_da(temp, [good_url, bad_url])) self.assertEqual(1, len(locations)) 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) diff --git a/pelican/tests/test_paginator.py b/pelican/tests/test_paginator.py index 002d9e07..903a0305 100644 --- a/pelican/tests/test_paginator.py +++ b/pelican/tests/test_paginator.py @@ -1,18 +1,21 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import +from __future__ import absolute_import, unicode_literals + import locale -from pelican.tests.support import unittest, get_settings - -from pelican.paginator import Paginator -from pelican.contents import Article, Author -from pelican.settings import DEFAULT_CONFIG from jinja2.utils import generate_lorem_ipsum +from pelican.contents import Article, Author +from pelican.paginator import Paginator +from pelican.settings import DEFAULT_CONFIG +from pelican.tests.support import get_settings, unittest + + # generate one paragraph, enclosed with

    TEST_CONTENT = str(generate_lorem_ipsum(n=1)) TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False) + class TestPage(unittest.TestCase): def setUp(self): super(TestPage, self).setUp() @@ -49,7 +52,8 @@ class TestPage(unittest.TestCase): ) 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) page = paginator.page(1) self.assertEqual(page.save_as, 'foobar.foo') diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index c6332487..b88ad287 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -1,23 +1,25 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +from __future__ import print_function, unicode_literals import collections -import os -import sys -from tempfile import mkdtemp -from shutil import rmtree import locale import logging +import os import subprocess +import sys + +from shutil import rmtree +from tempfile import mkdtemp from pelican import Pelican from pelican.generators import StaticGenerator from pelican.settings import read_settings -from pelican.tests.support import LoggedTestCase, mute, locale_available, unittest +from pelican.tests.support import (LoggedTestCase, locale_available, + mute, unittest) CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) 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')) INPUT_PATH = os.path.join(SAMPLES_PATH, "content") @@ -27,13 +29,10 @@ SAMPLE_FR_CONFIG = os.path.join(SAMPLES_PATH, "pelican.conf_FR.py") def recursiveDiff(dcmp): diff = { - 'diff_files': [os.path.join(dcmp.right, f) - for f in dcmp.diff_files], - 'left_only': [os.path.join(dcmp.right, f) - for f in dcmp.left_only], - 'right_only': [os.path.join(dcmp.right, f) - for f in dcmp.right_only], - } + 'diff_files': [os.path.join(dcmp.right, f) for f in dcmp.diff_files], + 'left_only': [os.path.join(dcmp.right, f) for f in dcmp.left_only], + 'right_only': [os.path.join(dcmp.right, f) for f in dcmp.right_only], + } for sub_dcmp in dcmp.subdirs.values(): for k, v in recursiveDiff(sub_dcmp).items(): diff[k] += v @@ -60,9 +59,13 @@ class TestPelican(LoggedTestCase): def assertDirsEqual(self, left_path, right_path): out, err = subprocess.Popen( - ['git', 'diff', '--no-ext-diff', '--exit-code', '-w', left_path, right_path], - env={str('PAGER'): str('')}, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ['git', 'diff', '--no-ext-diff', '--exit-code', + '-w', left_path, right_path], + env={str('PAGER'): str('')}, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE ).communicate() + def ignorable_git_crlf_errors(line): # Work around for running tests on Windows for msg in [ @@ -85,9 +88,11 @@ class TestPelican(LoggedTestCase): pelican = Pelican(settings=read_settings(path=None)) 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!") - self.assertIsInstance(generator_classes, collections.Sequence, + self.assertIsInstance( + generator_classes, collections.Sequence, "get_generator_classes() must return a Sequence to preserve order") def test_basic_generation_works(self): @@ -98,10 +103,11 @@ class TestPelican(LoggedTestCase): 'OUTPUT_PATH': self.temp_path, 'CACHE_PATH': self.temp_cache, 'LOCALE': locale.normalize('en_US'), - }) + }) pelican = Pelican(settings=settings) 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( count=3, msg="Unable to find.*skipping url replacement", @@ -114,10 +120,11 @@ class TestPelican(LoggedTestCase): 'OUTPUT_PATH': self.temp_path, 'CACHE_PATH': self.temp_cache, 'LOCALE': locale.normalize('en_US'), - }) + }) pelican = Pelican(settings=settings) 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 locale_available('French'), 'French locale needed') @@ -133,10 +140,11 @@ class TestPelican(LoggedTestCase): 'OUTPUT_PATH': self.temp_path, 'CACHE_PATH': self.temp_cache, 'LOCALE': our_locale, - }) + }) pelican = Pelican(settings=settings) 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): # the same thing with a specified set of settings should work @@ -146,8 +154,9 @@ class TestPelican(LoggedTestCase): 'CACHE_PATH': self.temp_cache, 'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'very'), os.path.join(SAMPLES_PATH, 'kinda'), - os.path.join(SAMPLES_PATH, 'theme_standard')] - }) + os.path.join(SAMPLES_PATH, + 'theme_standard')] + }) pelican = Pelican(settings=settings) mute(True)(pelican.run)() theme_output = os.path.join(self.temp_path, 'theme') @@ -165,8 +174,9 @@ class TestPelican(LoggedTestCase): 'PATH': INPUT_PATH, 'OUTPUT_PATH': self.temp_path, '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) mute(True)(pelican.run)() @@ -184,9 +194,9 @@ class TestPelican(LoggedTestCase): 'WRITE_SELECTED': [ os.path.join(self.temp_path, 'oh-yeah.html'), os.path.join(self.temp_path, 'categories.html'), - ], + ], 'LOCALE': locale.normalize('en_US'), - }) + }) pelican = Pelican(settings=settings) logger = logging.getLogger() orig_level = logger.getEffectiveLevel() diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index dd7e5fc2..71394ee4 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +from __future__ import print_function, unicode_literals import os from pelican import readers +from pelican.tests.support import get_settings, unittest from pelican.utils import SafeDatetime -from pelican.tests.support import unittest, get_settings + CUR_DIR = os.path.dirname(__file__) CONTENT_PATH = os.path.join(CUR_DIR, 'content') @@ -29,22 +30,26 @@ class ReaderTest(unittest.TestCase): self.assertEqual( value, real_value, - '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: 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): def setUp(self): self.dictionary = { - 'key-a' : 'val-a', - 'key-b' : 'val-b'} + 'key-a': 'val-a', + 'key-b': 'val-b' + } def tearDown(self): self.dictionary = None def test_subset(self): - self.assertDictHasSubset(self.dictionary, {'key-a':'val-a'}) + self.assertDictHasSubset(self.dictionary, {'key-a': 'val-a'}) def test_equal(self): self.assertDictHasSubset(self.dictionary, self.dictionary) @@ -54,18 +59,17 @@ class TestAssertDictHasSubset(ReaderTest): AssertionError, 'Expected.*key-c.*to have value.*val-c.*but was not in Dict', self.assertDictHasSubset, - self.dictionary, - {'key-c':'val-c'} - ) + self.dictionary, + {'key-c': 'val-c'}) def test_fail_wrong_val(self): self.assertRaisesRegexp( AssertionError, 'Expected .*key-a.* to have value .*val-b.* but was .*val-a.*', self.assertDictHasSubset, - self.dictionary, - {'key-a':'val-b'} - ) + self.dictionary, + {'key-a': 'val-b'}) + class DefaultReaderTest(ReaderTest): @@ -153,17 +157,17 @@ class RstReaderTest(ReaderTest): '(?P\d{4}-\d{2}-\d{2})' '_(?P.*)' '#(?P.*)-(?P.*)' - ), + ), EXTRA_PATH_METADATA={ input_with_metadata: { 'key-1a': 'value-1a', 'key-1b': 'value-1b' - } } - ) + } + ) expected_metadata = { 'category': 'yeah', - 'author' : 'Alexis Métaireau', + 'author': 'Alexis Métaireau', 'title': 'Rst with filename metadata', 'date': SafeDatetime(2012, 11, 29), 'slug': 'rst_w_filename_meta', @@ -179,38 +183,41 @@ class RstReaderTest(ReaderTest): path=input_file_path_without_metadata, EXTRA_PATH_METADATA={ input_file_path_without_metadata: { - 'author': 'Charlès Overwrite'} + 'author': 'Charlès Overwrite' } - ) + } + ) expected_without_metadata = { - 'category' : 'misc', - 'author' : 'Charlès Overwrite', - 'title' : 'Article title', - 'reader' : 'rst', + 'category': 'misc', + 'author': 'Charlès Overwrite', + 'title': 'Article title', + 'reader': 'rst', } self.assertDictHasSubset( page_without_metadata.metadata, expected_without_metadata) def test_article_extra_path_metadata_dont_overwrite(self): - #EXTRA_PATH_METADATA['author'] should get ignored - #since we don't overwrite already set values + # EXTRA_PATH_METADATA['author'] should get ignored + # since we don't overwrite already set values input_file_path = '2012-11-29_rst_w_filename_meta#foo-bar.rst' page = self.read_file( path=input_file_path, FILENAME_METADATA=( '(?P\d{4}-\d{2}-\d{2})' '_(?P.*)' - '#(?P.*)-(?P.*)'), + '#(?P.*)-(?P.*)' + ), EXTRA_PATH_METADATA={ input_file_path: { 'author': 'Charlès Overwrite', - 'key-1b': 'value-1b'} + 'key-1b': 'value-1b' } - ) + } + ) expected = { 'category': 'yeah', - 'author' : 'Alexis Métaireau', + 'author': 'Alexis Métaireau', 'title': 'Rst with filename metadata', 'date': SafeDatetime(2012, 11, 29), 'slug': 'rst_w_filename_meta', @@ -273,7 +280,7 @@ class RstReaderTest(ReaderTest): # typogrify should be able to ignore user specified tags, # but tries to be clever with widont extension page = self.read_file(path='article.rst', TYPOGRIFY=True, - TYPOGRIFY_IGNORE_TAGS = ['p']) + TYPOGRIFY_IGNORE_TAGS=['p']) expected = ('

    THIS is some content. With some stuff to ' '"typogrify"...

    \n

    Now with added ' 'support for ' @@ -284,7 +291,7 @@ class RstReaderTest(ReaderTest): # typogrify should ignore code blocks by default because # code blocks are composed inside the pre tag page = self.read_file(path='article_with_code_block.rst', - TYPOGRIFY=True) + TYPOGRIFY=True) expected = ('

    An article with some code

    \n' '
    x'
    @@ -292,13 +299,17 @@ class RstReaderTest(ReaderTest):
                             ' y\n
    \n' '

    A block quote:

    \n
    \nx ' '& y
    \n' - '

    Normal:\nx & y

    \n') + '

    Normal:\nx' + ' &' + ' y' + '

    \n') self.assertEqual(page.content, expected) # instruct typogrify to also ignore blockquotes page = self.read_file(path='article_with_code_block.rst', - TYPOGRIFY=True, TYPOGRIFY_IGNORE_TAGS = ['blockquote']) + TYPOGRIFY=True, + TYPOGRIFY_IGNORE_TAGS=['blockquote']) expected = ('

    An article with some code

    \n' '
    x'
    @@ -306,7 +317,10 @@ class RstReaderTest(ReaderTest):
                             ' y\n
    \n' '

    A block quote:

    \n
    \nx ' '& y
    \n' - '

    Normal:\nx & y

    \n') + '

    Normal:\nx' + ' &' + ' y' + '

    \n') self.assertEqual(page.content, expected) except ImportError: @@ -339,6 +353,7 @@ class RstReaderTest(ReaderTest): self.assertDictHasSubset(page.metadata, expected) + @unittest.skipUnless(readers.Markdown, "markdown isn't installed") class MdReaderTest(ReaderTest): @@ -400,7 +415,8 @@ class MdReaderTest(ReaderTest): 'modified': SafeDatetime(2012, 11, 1), 'multiline': [ 'Line Metadata should be handle properly.', - 'See syntax of Meta-Data extension of Python Markdown package:', + 'See syntax of Meta-Data extension of ' + 'Python Markdown package:', 'If a line is indented by 4 or more spaces,', 'that line is assumed to be an additional line of the value', 'for the previous keyword.', diff --git a/pelican/tests/test_rstdirectives.py b/pelican/tests/test_rstdirectives.py index 7c5f8adf..f6a7221f 100644 --- a/pelican/tests/test_rstdirectives.py +++ b/pelican/tests/test_rstdirectives.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +from __future__ import print_function, unicode_literals + +from pelican.tests.support import unittest + try: from unittest.mock import Mock except ImportError: @@ -7,7 +10,7 @@ except ImportError: from mock import Mock except ImportError: Mock = False -from pelican.tests.support import unittest + @unittest.skipUnless(Mock, 'Needs Mock module') class Test_abbr_role(unittest.TestCase): diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 5653d07a..7b1e36df 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function -import copy -import os -import locale -from sys import platform -from os.path import dirname, abspath, join +from __future__ import print_function, unicode_literals -from pelican.settings import (read_settings, configure_settings, - DEFAULT_CONFIG, DEFAULT_THEME) +import copy +import locale +import os +from os.path import abspath, dirname, join +from sys import platform + + +from pelican.settings import (DEFAULT_CONFIG, DEFAULT_THEME, + configure_settings, read_settings) from pelican.tests.support import unittest @@ -28,12 +30,14 @@ class TestSettingsConfiguration(unittest.TestCase): def test_overwrite_existing_settings(self): self.assertEqual(self.settings.get('SITENAME'), "Alexis' log") - self.assertEqual(self.settings.get('SITEURL'), - 'http://blog.notmyidea.org') + self.assertEqual( + self.settings.get('SITEURL'), + 'http://blog.notmyidea.org') def test_keep_default_settings(self): # Keep default settings if not defined. - self.assertEqual(self.settings.get('DEFAULT_CATEGORY'), + self.assertEqual( + self.settings.get('DEFAULT_CATEGORY'), DEFAULT_CONFIG['DEFAULT_CATEGORY']) def test_dont_copy_small_keys(self): @@ -69,28 +73,31 @@ class TestSettingsConfiguration(unittest.TestCase): def test_static_path_settings_safety(self): # Disallow static paths from being strings - settings = {'STATIC_PATHS': 'foo/bar', - 'THEME_STATIC_PATHS': 'bar/baz', - # These 4 settings are required to run configure_settings - 'PATH': '.', - 'THEME': DEFAULT_THEME, - 'SITEURL': 'http://blog.notmyidea.org/', - 'LOCALE': '', - } + settings = { + 'STATIC_PATHS': 'foo/bar', + 'THEME_STATIC_PATHS': 'bar/baz', + # These 4 settings are required to run configure_settings + 'PATH': '.', + 'THEME': DEFAULT_THEME, + 'SITEURL': 'http://blog.notmyidea.org/', + 'LOCALE': '', + } configure_settings(settings) - self.assertEqual(settings['STATIC_PATHS'], - DEFAULT_CONFIG['STATIC_PATHS']) - self.assertEqual(settings['THEME_STATIC_PATHS'], - DEFAULT_CONFIG['THEME_STATIC_PATHS']) + self.assertEqual( + settings['STATIC_PATHS'], + DEFAULT_CONFIG['STATIC_PATHS']) + self.assertEqual( + settings['THEME_STATIC_PATHS'], + DEFAULT_CONFIG['THEME_STATIC_PATHS']) def test_configure_settings(self): # Manipulations to settings should be applied correctly. settings = { - 'SITEURL': 'http://blog.notmyidea.org/', - 'LOCALE': '', - 'PATH': os.curdir, - 'THEME': DEFAULT_THEME, - } + 'SITEURL': 'http://blog.notmyidea.org/', + 'LOCALE': '', + 'PATH': os.curdir, + 'THEME': DEFAULT_THEME, + } configure_settings(settings) # SITEURL should not have a trailing slash @@ -154,7 +161,7 @@ class TestSettingsConfiguration(unittest.TestCase): settings['PATH'] = '' self.assertRaises(Exception, configure_settings, settings) - # Test nonexistent THEME + # Test nonexistent THEME settings['PATH'] = os.curdir settings['THEME'] = 'foo' diff --git a/pelican/tests/test_urlwrappers.py b/pelican/tests/test_urlwrappers.py index 20a87114..ae6eaaec 100644 --- a/pelican/tests/test_urlwrappers.py +++ b/pelican/tests/test_urlwrappers.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from pelican.urlwrappers import URLWrapper, Tag, Category from pelican.tests.support import unittest +from pelican.urlwrappers import Category, Tag, URLWrapper + class TestURLWrapper(unittest.TestCase): def test_ordering(self): diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 0f8878af..d6fdf70e 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -1,20 +1,22 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function, absolute_import -import logging -import shutil -import os -import time +from __future__ import absolute_import, print_function, unicode_literals + import locale +import logging +import os +import shutil +import time from sys import platform from tempfile import mkdtemp import pytz -from pelican.generators import TemplatePagesGenerator -from pelican.writers import Writer -from pelican.settings import read_settings from pelican import utils -from pelican.tests.support import get_article, LoggedTestCase, locale_available, unittest +from pelican.generators import TemplatePagesGenerator +from pelican.settings import read_settings +from pelican.tests.support import (LoggedTestCase, get_article, + locale_available, unittest) +from pelican.writers import Writer class TestUtils(LoggedTestCase): @@ -72,7 +74,7 @@ class TestUtils(LoggedTestCase): '2012-11-22T22:11:10Z': date_hour_sec_z, '2012-11-22T22:11:10-0500': date_hour_sec_est, '2012-11-22T22:11:10.123Z': date_hour_sec_frac_z, - } + } # examples from http://www.w3.org/TR/NOTE-datetime iso_8601_date = utils.SafeDatetime(year=1997, month=7, day=16) @@ -95,7 +97,6 @@ class TestUtils(LoggedTestCase): # invalid ones invalid_dates = ['2010-110-12', 'yay'] - for value, expected in dates.items(): self.assertEqual(utils.get_date(value), expected, value) @@ -290,7 +291,9 @@ class TestUtils(LoggedTestCase): self.assertEqual(utils.strftime(d, '%d/%m/%Y'), '29/08/2012') # 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 self.assertEqual(utils.strftime(d, '%d%%%m%%%y'), '29%08%12') @@ -306,8 +309,9 @@ class TestUtils(LoggedTestCase): 'Published in 29-08-2012') # with non-ascii text - self.assertEqual(utils.strftime(d, '%d/%m/%Y Øl trinken beim Besäufnis'), - '29/08/2012 Øl trinken beim Besäufnis') + self.assertEqual( + utils.strftime(d, '%d/%m/%Y Øl trinken beim Besäufnis'), + '29/08/2012 Øl trinken beim Besäufnis') # alternative formatting options self.assertEqual(utils.strftime(d, '%-d/%-m/%y'), '29/8/12') @@ -316,7 +320,6 @@ class TestUtils(LoggedTestCase): d = utils.SafeDatetime(2012, 8, 9) self.assertEqual(utils.strftime(d, '%-d/%-m/%y'), '9/8/12') - # test the output of utils.strftime in a different locale # Turkish locale @unittest.skipUnless(locale_available('tr_TR.UTF-8') or @@ -339,17 +342,18 @@ class TestUtils(LoggedTestCase): 'Çarşamba, 29 Ağustos 2012') # 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') # 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ışı') # restore locale back locale.setlocale(locale.LC_ALL, old_locale) - # test the output of utils.strftime in a different locale # French locale @unittest.skipUnless(locale_available('fr_FR.UTF-8') or @@ -373,21 +377,28 @@ class TestUtils(LoggedTestCase): self.assertTrue(utils.strftime(d, '%A') in ('mercredi', 'Mercredi')) # 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') # 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') # restore locale back locale.setlocale(locale.LC_ALL, old_locale) - def test_maybe_pluralize(self): - self.assertEqual(utils.maybe_pluralize(0, 'Article', 'Articles'), '0 Articles') - self.assertEqual(utils.maybe_pluralize(1, 'Article', 'Articles'), '1 Article') - self.assertEqual(utils.maybe_pluralize(2, 'Article', 'Articles'), '2 Articles') + self.assertEqual( + utils.maybe_pluralize(0, 'Article', 'Articles'), + '0 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): @@ -435,8 +446,9 @@ class TestCopy(unittest.TestCase): def test_copy_file_create_dirs(self): self._create_file('a.txt') - utils.copy(os.path.join(self.root_dir, 'a.txt'), - os.path.join(self.root_dir, 'b0', 'b1', 'b2', 'b3', 'b.txt')) + utils.copy( + os.path.join(self.root_dir, 'a.txt'), + os.path.join(self.root_dir, 'b0', 'b1', 'b2', 'b3', 'b.txt')) self._exist_dir('b0') self._exist_dir('b0', 'b1') self._exist_dir('b0', 'b1', 'b2') @@ -491,35 +503,39 @@ class TestDateFormatter(unittest.TestCase): template_file.write('date = {{ date|strftime("%A, %d %B %Y") }}') self.date = utils.SafeDatetime(2012, 8, 29) - def tearDown(self): shutil.rmtree(self.temp_content) shutil.rmtree(self.temp_output) # reset locale to default locale.setlocale(locale.LC_ALL, '') - @unittest.skipUnless(locale_available('fr_FR.UTF-8') or locale_available('French'), 'French locale needed') 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': locale.setlocale(locale.LC_ALL, str('French')) else: locale.setlocale(locale.LC_ALL, str('fr_FR.UTF-8')) - date = utils.SafeDatetime(2014,8,14) - # we compare the lower() dates since macos10 returns "Jeudi" for %A whereas linux reports "jeudi" - self.assertEqual( u'jeudi, 14 août 2014', utils.strftime(date, date_format="%A, %d %B %Y").lower() ) + date = utils.SafeDatetime(2014, 8, 14) + # we compare the lower() dates since macos10 returns + # "Jeudi" for %A 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() - 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: 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) 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 locale_available('French'), @@ -530,9 +546,12 @@ class TestDateFormatter(unittest.TestCase): else: locale_string = 'fr_FR.UTF-8' settings = read_settings( - override = {'LOCALE': locale_string, - 'TEMPLATE_PAGES': {'template/source.html': - 'generated/file.html'}}) + override={ + 'LOCALE': locale_string, + 'TEMPLATE_PAGES': { + 'template/source.html': 'generated/file.html' + } + }) generator = TemplatePagesGenerator( {'date': self.date}, settings, @@ -543,7 +562,7 @@ class TestDateFormatter(unittest.TestCase): generator.generate_output(writer) output_path = os.path.join( - self.temp_output, 'generated', 'file.html') + self.temp_output, 'generated', 'file.html') # output file has been generated self.assertTrue(os.path.exists(output_path)) @@ -553,7 +572,6 @@ class TestDateFormatter(unittest.TestCase): self.assertEqual(output_file, utils.strftime(self.date, 'date = %A, %d %B %Y')) - @unittest.skipUnless(locale_available('tr_TR.UTF-8') or locale_available('Turkish'), 'Turkish locale needed') @@ -563,9 +581,12 @@ class TestDateFormatter(unittest.TestCase): else: locale_string = 'tr_TR.UTF-8' settings = read_settings( - override = {'LOCALE': locale_string, - 'TEMPLATE_PAGES': {'template/source.html': - 'generated/file.html'}}) + override={ + 'LOCALE': locale_string, + 'TEMPLATE_PAGES': { + 'template/source.html': 'generated/file.html' + } + }) generator = TemplatePagesGenerator( {'date': self.date}, settings, @@ -576,7 +597,7 @@ class TestDateFormatter(unittest.TestCase): generator.generate_output(writer) output_path = os.path.join( - self.temp_output, 'generated', 'file.html') + self.temp_output, 'generated', 'file.html') # output file has been generated self.assertTrue(os.path.exists(output_path)) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 92e8c919..f8abbd7a 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -1,29 +1,30 @@ #!/usr/bin/env python - # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +from __future__ import print_function, unicode_literals + import argparse -try: - from html import unescape # py3.4+ -except ImportError: - from six.moves.html_parser import HTMLParser - unescape = HTMLParser().unescape +import logging import os import re import subprocess import sys import time -import logging from codecs import open + from six.moves.urllib.error import URLError from six.moves.urllib.parse import urlparse from six.moves.urllib.request import urlretrieve -# pelican.log has to be the first pelican module to be loaded # because logging.setLoggerClass has to be called before logging.getLogger from pelican.log import init -from pelican.utils import slugify, SafeDatetime +from pelican.utils import SafeDatetime, slugify + +try: + from html import unescape # py3.4+ +except ImportError: + from six.moves.html_parser import HTMLParser + unescape = HTMLParser().unescape logger = logging.getLogger(__name__) @@ -70,12 +71,19 @@ def decode_wp_content(content, br=True): content = "" for p in pgraphs: content = content + "

    " + p.strip() + "

    \n" - # under certain strange conditions it could create a P of entirely whitespace + # under certain strange conditions it could create + # a P of entirely whitespace content = re.sub(r'

    \s*

    ', '', content) - content = re.sub(r'

    ([^<]+)', "

    \\1

    ", content) + content = re.sub( + r'

    ([^<]+)', + "

    \\1

    ", + content) # don't wrap tags - content = re.sub(r'

    \s*(]*>)\s*

    ', "\\1", content) - #problem with nested lists + content = re.sub( + r'

    \s*(]*>)\s*

    ', + "\\1", + content) + # problem with nested lists content = re.sub(r'

    (', "\\1", content) content = re.sub(r'

    ]*)>', "

    ", content) content = content.replace('

    ', '

    ') @@ -84,12 +92,20 @@ def decode_wp_content(content, br=True): if br: def _preserve_newline(match): return match.group(0).replace("\n", "") - content = re.sub(r'/<(script|style).*?<\/\\1>/s', _preserve_newline, content) + content = re.sub( + r'/<(script|style).*?<\/\\1>/s', + _preserve_newline, + content) # optionally make line breaks content = re.sub(r'(?)\s*\n', "
    \n", content) content = content.replace("", "\n") - content = re.sub(r'(]*>)\s*
    ', "\\1", content) - content = re.sub(r'
    (\s*]*>)', '\\1', content) + content = re.sub( + r'(]*>)\s*
    ', "\\1", + content) + content = re.sub( + r'
    (\s*]*>)', + '\\1', + content) content = re.sub(r'\n

    ', "

    ", content) if pre_tags: @@ -100,13 +116,14 @@ def decode_wp_content(content, br=True): return content + def get_items(xml): """Opens a WordPress xml file and returns a list of items""" try: from bs4 import BeautifulSoup except ImportError: - error = ('Missing dependency ' - '"BeautifulSoup4" and "lxml" required to import WordPress XML files.') + error = ('Missing dependency "BeautifulSoup4" and "lxml" required to ' + 'import WordPress XML files.') sys.exit(error) with open(xml, encoding='utf-8') as infile: xmlfile = infile.read() @@ -114,12 +131,14 @@ def get_items(xml): items = soup.rss.channel.findAll('item') return items + def get_filename(filename, post_id): if filename is not None: return filename else: return post_id + def wp2fields(xml, wp_custpost=False): """Opens a wordpress XML file, and yield Pelican fields""" @@ -141,16 +160,18 @@ def wp2fields(xml, wp_custpost=False): content = item.find('encoded').string raw_date = item.find('post_date').string - date_object = time.strptime(raw_date, "%Y-%m-%d %H:%M:%S") - date = time.strftime("%Y-%m-%d %H:%M", date_object) + date_object = time.strptime(raw_date, '%Y-%m-%d %H:%M:%S') + date = time.strftime('%Y-%m-%d %H:%M', date_object) author = item.find('creator').string - categories = [cat.string for cat in item.findAll('category', {'domain' : 'category'})] - # caturl = [cat['nicename'] for cat in item.find(domain='category')] + categories = [cat.string for cat + in item.findAll('category', {'domain': 'category'})] - tags = [tag.string for tag in item.findAll('category', {'domain' : 'post_tag'})] + tags = [tag.string for tag + in item.findAll('category', {'domain': 'post_tag'})] # To publish a post the status should be 'published' - status = 'published' if item.find('status').string == "publish" else item.find('status').string + status = 'published' if item.find('status').string == "publish" \ + else item.find('status').string kind = 'article' post_type = item.find('post_type').string @@ -159,16 +180,17 @@ def wp2fields(xml, wp_custpost=False): elif wp_custpost: if post_type == 'post': pass - # Old behaviour was to name everything not a page as an article. - # Theoretically all attachments have status == inherit so - # no attachments should be here. But this statement is to + # Old behaviour was to name everything not a page as an + # article.Theoretically all attachments have status == inherit + # so no attachments should be here. But this statement is to # maintain existing behaviour in case that doesn't hold true. elif post_type == 'attachment': pass else: kind = post_type - yield (title, content, filename, date, author, categories, tags, status, - kind, "wp-html") + yield (title, content, filename, date, author, categories, + tags, status, kind, 'wp-html') + def dc2fields(file): """Opens a Dotclear export file, and yield pelican fields""" @@ -176,10 +198,10 @@ def dc2fields(file): from bs4 import BeautifulSoup except ImportError: error = ('Missing dependency ' - '"BeautifulSoup4" and "lxml" required to import Dotclear files.') + '"BeautifulSoup4" and "lxml" required ' + 'to import Dotclear files.') sys.exit(error) - in_cat = False in_post = False category_list = {} @@ -203,7 +225,7 @@ def dc2fields(file): # remove 1st and last "" fields[0] = fields[0][1:] # fields[-1] = fields[-1][:-1] - category_list[fields[0]]=fields[2] + category_list[fields[0]] = fields[2] elif in_post: if not line: in_post = False @@ -249,45 +271,50 @@ def dc2fields(file): # remove seconds post_creadt = ':'.join(post_creadt.split(':')[0:2]) - author = "" + author = '' categories = [] tags = [] if cat_id: - categories = [category_list[id].strip() for id in cat_id.split(',')] + categories = [category_list[id].strip() for id + in cat_id.split(',')] # Get tags related to a post - tag = post_meta.replace('{', '').replace('}', '').replace('a:1:s:3:\\"tag\\";a:', '').replace('a:0:', '') + tag = (post_meta.replace('{', '') + .replace('}', '') + .replace('a:1:s:3:\\"tag\\";a:', '') + .replace('a:0:', '')) if len(tag) > 1: if int(tag[:1]) == 1: newtag = tag.split('"')[1] tags.append( BeautifulSoup( - newtag - , "xml" + newtag, + 'xml' ) # bs4 always outputs UTF-8 .decode('utf-8') ) else: - i=1 - j=1 + i = 1 + j = 1 while(i <= int(tag[:1])): - newtag = tag.split('"')[j].replace('\\','') + newtag = tag.split('"')[j].replace('\\', '') tags.append( BeautifulSoup( - newtag - , "xml" + newtag, + 'xml' ) # bs4 always outputs UTF-8 .decode('utf-8') ) - i=i+1 - if j < int(tag[:1])*2: - j=j+2 + i = i + 1 + if j < int(tag[:1]) * 2: + j = j + 2 """ - dotclear2 does not use markdown by default unless you use the markdown plugin + dotclear2 does not use markdown by default unless + you use the markdown plugin Ref: http://plugins.dotaddict.org/dc2/details/formatting-markdown """ if post_format == "markdown": @@ -322,12 +349,13 @@ def posterous2fields(api_token, email, password): # py2 import import urllib2 as urllib_request - - def get_posterous_posts(api_token, email, password, page = 1): - base64string = base64.encodestring(("%s:%s" % (email, password)).encode('utf-8')).replace(b'\n', b'') - url = "http://posterous.com/api/v2/users/me/sites/primary/posts?api_token=%s&page=%d" % (api_token, page) + def get_posterous_posts(api_token, email, password, page=1): + base64string = base64.encodestring( + ("%s:%s" % (email, password)).encode('utf-8')).replace('\n', '') + url = ("http://posterous.com/api/v2/users/me/sites/primary/" + "posts?api_token=%s&page=%d") % (api_token, page) request = urllib_request.Request(url) - request.add_header("Authorization", "Basic %s" % base64string.decode()) + request.add_header('Authorization', 'Basic %s' % base64string.decode()) handle = urllib_request.urlopen(request) posts = json.loads(handle.read().decode('utf-8')) return posts @@ -344,16 +372,18 @@ def posterous2fields(api_token, email, password): slug = slugify(post.get('title')) tags = [tag.get('name') for tag in post.get('tags')] raw_date = post.get('display_date') - date_object = SafeDatetime.strptime(raw_date[:-6], "%Y/%m/%d %H:%M:%S") + date_object = SafeDatetime.strptime( + raw_date[:-6], '%Y/%m/%d %H:%M:%S') offset = int(raw_date[-5:]) - delta = timedelta(hours = offset / 100) + delta = timedelta(hours=(offset / 100)) date_object -= delta - date = date_object.strftime("%Y-%m-%d %H:%M") - kind = 'article' # TODO: Recognise pages + date = date_object.strftime('%Y-%m-%d %H:%M') + kind = 'article' # TODO: Recognise pages status = 'published' # TODO: Find a way for draft posts - yield (post.get('title'), post.get('body_cleaned'), slug, date, - post.get('user').get('display_name'), [], tags, status, kind, "html") + yield (post.get('title'), post.get('body_cleaned'), + slug, date, post.get('user').get('display_name'), + [], tags, status, kind, 'html') def tumblr2fields(api_key, blogname): @@ -374,7 +404,9 @@ def tumblr2fields(api_key, blogname): import urllib2 as urllib_request def get_tumblr_posts(api_key, blogname, offset=0): - url = "http://api.tumblr.com/v2/blog/%s.tumblr.com/posts?api_key=%s&offset=%d&filter=raw" % (blogname, api_key, offset) + url = ("http://api.tumblr.com/v2/blog/%s.tumblr.com/" + "posts?api_key=%s&offset=%d&filter=raw") % ( + blogname, api_key, offset) request = urllib_request.Request(url) handle = urllib_request.urlopen(request) posts = json.loads(handle.read().decode('utf-8')) @@ -384,7 +416,10 @@ def tumblr2fields(api_key, blogname): posts = get_tumblr_posts(api_key, blogname, offset) while len(posts) > 0: for post in posts: - title = post.get('title') or post.get('source_title') or post.get('type').capitalize() + title = \ + post.get('title') or \ + post.get('source_title') or \ + post.get('type').capitalize() slug = post.get('slug') or slugify(title) tags = post.get('tags') timestamp = post.get('timestamp') @@ -398,7 +433,11 @@ def tumblr2fields(api_key, blogname): fmtstr = '![%s](%s)' else: fmtstr = '%s' - content = '\n'.join(fmtstr % (photo.get('caption'), photo.get('original_size').get('url')) for photo in post.get('photos')) + content = '' + for photo in post.get('photos'): + content += '\n'.join( + fmtstr % (photo.get('caption'), + photo.get('original_size').get('url'))) content += '\n\n' + post.get('caption') elif type == 'quote': if format == 'markdown': @@ -417,16 +456,29 @@ def tumblr2fields(api_key, blogname): fmtstr = '[via](%s)\n\n' else: fmtstr = '

    via

    \n' - content = fmtstr % post.get('source_url') + post.get('caption') + post.get('player') + content = fmtstr % post.get('source_url') + \ + post.get('caption') + \ + post.get('player') elif type == 'video': if format == 'markdown': fmtstr = '[via](%s)\n\n' else: fmtstr = '

    via

    \n' - content = fmtstr % post.get('source_url') + post.get('caption') + '\n'.join(player.get('embed_code') for player in post.get('player')) + source = fmtstr % post.get('source_url') + caption = post.get('caption') + players = '\n'.join(player.get('embed_code') + for player in post.get('player')) + content = source + caption + players elif type == 'answer': title = post.get('question') - content = '

    %s: %s

    \n%s' % (post.get('asking_name'), post.get('asking_url'), post.get('question'), post.get('answer')) + content = ('

    ' + '%s' + ': %s' + '

    \n' + ' %s' % (post.get('asking_name'), + post.get('asking_url'), + post.get('question'), + post.get('answer'))) content = content.rstrip() + '\n' kind = 'article' @@ -438,25 +490,30 @@ def tumblr2fields(api_key, blogname): offset += len(posts) posts = get_tumblr_posts(api_key, blogname, offset) + def feed2fields(file): """Read a feed and yield pelican fields""" import feedparser d = feedparser.parse(file) for entry in d.entries: - date = (time.strftime("%Y-%m-%d %H:%M", entry.updated_parsed) - if hasattr(entry, "updated_parsed") else None) - author = entry.author if hasattr(entry, "author") else None - tags = [e['term'] for e in entry.tags] if hasattr(entry, "tags") else None + date = (time.strftime('%Y-%m-%d %H:%M', entry.updated_parsed) + if hasattr(entry, 'updated_parsed') else None) + author = entry.author if hasattr(entry, 'author') else None + tags = ([e['term'] for e in entry.tags] + if hasattr(entry, 'tags') else None) slug = slugify(entry.title) kind = 'article' - yield (entry.title, entry.description, slug, date, author, [], tags, None, - kind, "html") + yield (entry.title, entry.description, slug, date, + author, [], tags, None, kind, 'html') + + +def build_header(title, date, author, categories, tags, slug, + status=None, attachments=None): + """Build a header from a list of fields""" -def build_header(title, date, author, categories, tags, slug, status=None, attachments=None): from docutils.utils import column_width - """Build a header from a list of fields""" header = '%s\n%s\n' % (title, '#' * column_width(title)) if date: header += ':date: %s\n' % date @@ -475,8 +532,9 @@ def build_header(title, date, author, categories, tags, slug, status=None, attac header += '\n' return header -def build_markdown_header(title, date, author, categories, tags, slug, status=None, - attachments=None): + +def build_markdown_header(title, date, author, categories, tags, + slug, status=None, attachments=None): """Build a header from a list of fields""" header = 'Title: %s\n' % title if date: @@ -496,6 +554,7 @@ def build_markdown_header(title, date, author, categories, tags, slug, status=No header += '\n' return header + def get_ext(out_markup, in_markup='html'): if in_markup == 'markdown' or out_markup == 'markdown': ext = '.md' @@ -503,26 +562,27 @@ def get_ext(out_markup, in_markup='html'): ext = '.rst' return ext + def get_out_filename(output_path, filename, ext, kind, - dirpage, dircat, categories, wp_custpost): + dirpage, dircat, categories, wp_custpost): filename = os.path.basename(filename) # Enforce filename restrictions for various filesystems at once; see # http://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words # we do not need to filter words because an extension will be appended - filename = re.sub(r'[<>:"/\\|?*^% ]', '-', filename) # invalid chars - filename = filename.lstrip('.') # should not start with a dot + filename = re.sub(r'[<>:"/\\|?*^% ]', '-', filename) # invalid chars + filename = filename.lstrip('.') # should not start with a dot if not filename: filename = '_' - filename = filename[:249] # allow for 5 extra characters + filename = filename[:249] # allow for 5 extra characters - out_filename = os.path.join(output_path, filename+ext) + out_filename = os.path.join(output_path, filename + ext) # option to put page posts in pages/ subdirectory if dirpage and kind == 'page': pages_dir = os.path.join(output_path, 'pages') if not os.path.isdir(pages_dir): os.mkdir(pages_dir) - out_filename = os.path.join(pages_dir, filename+ext) + out_filename = os.path.join(pages_dir, filename + ext) elif not dirpage and kind == 'page': pass # option to put wp custom post types in directories with post type @@ -539,18 +599,19 @@ def get_out_filename(output_path, filename, ext, kind, else: catname = '' out_filename = os.path.join(output_path, typename, - catname, filename+ext) + catname, filename + ext) if not os.path.isdir(os.path.join(output_path, typename, catname)): os.makedirs(os.path.join(output_path, typename, catname)) # option to put files in directories with categories names elif dircat and (len(categories) > 0): catname = slugify(categories[0]) - out_filename = os.path.join(output_path, catname, filename+ext) + out_filename = os.path.join(output_path, catname, filename + ext) if not os.path.isdir(os.path.join(output_path, catname)): os.mkdir(os.path.join(output_path, catname)) return out_filename + def get_attachments(xml): """returns a dictionary of posts that have attachments with a list of the attachment_urls @@ -566,7 +627,7 @@ def get_attachments(xml): if kind == 'attachment': attachments.append((item.find('post_parent').string, - item.find('attachment_url').string)) + item.find('attachment_url').string)) else: filename = get_filename(filename, post_id) names[post_id] = filename @@ -575,7 +636,7 @@ def get_attachments(xml): try: parent_name = names[parent] except KeyError: - #attachment's parent is not a valid post + # attachment's parent is not a valid post parent_name = None try: @@ -585,6 +646,7 @@ def get_attachments(xml): attachedposts[parent_name].append(url) return attachedposts + def download_attachments(output_path, urls): """Downloads WordPress attachments and returns a list of paths to attachments that can be associated with a post (relative path to output @@ -592,8 +654,8 @@ def download_attachments(output_path, urls): locations = [] for url in urls: path = urlparse(url).path - #teardown path and rebuild to negate any errors with - #os.path.join and leading /'s + # teardown path and rebuild to negate any errors with + # os.path.join and leading /'s path = path.split('/') filename = path.pop(-1) localpath = '' @@ -608,12 +670,13 @@ def download_attachments(output_path, urls): urlretrieve(url, os.path.join(full_path, filename)) locations.append(os.path.join(localpath, filename)) except (URLError, IOError) as e: - #Python 2.7 throws an IOError rather Than URLError + # Python 2.7 throws an IOError rather Than URLError logger.warning("No file could be downloaded from %s\n%s", url, e) return locations -def fields2pelican(fields, out_markup, output_path, +def fields2pelican( + fields, out_markup, output_path, dircat=False, strip_raw=False, disable_slugs=False, dirpage=False, filename_template=None, filter_author=None, wp_custpost=False, wp_attach=False, attachments=None): @@ -634,24 +697,26 @@ def fields2pelican(fields, out_markup, output_path, ext = get_ext(out_markup, in_markup) if ext == '.md': - header = build_markdown_header(title, date, author, categories, - tags, slug, status, attached_files) + header = build_markdown_header( + title, date, author, categories, tags, slug, + status, attached_files) else: - out_markup = "rst" + out_markup = 'rst' header = build_header(title, date, author, categories, - tags, slug, status, attached_files) + tags, slug, status, attached_files) - out_filename = get_out_filename(output_path, filename, ext, - kind, dirpage, dircat, categories, wp_custpost) + out_filename = get_out_filename( + output_path, filename, ext, kind, dirpage, dircat, + categories, wp_custpost) print(out_filename) - if in_markup in ("html", "wp-html"): - html_filename = os.path.join(output_path, filename+'.html') + if in_markup in ('html', 'wp-html'): + html_filename = os.path.join(output_path, filename + '.html') with open(html_filename, 'w', encoding='utf-8') as fp: # Replace newlines with paragraphs wrapped with

    so # HTML is valid before conversion - if in_markup == "wp-html": + if in_markup == 'wp-html': new_content = decode_wp_content(content) else: paragraphs = content.splitlines() @@ -660,79 +725,95 @@ def fields2pelican(fields, out_markup, output_path, fp.write(new_content) - parse_raw = '--parse-raw' if not strip_raw else '' cmd = ('pandoc --normalize {0} --from=html' - ' --to={1} -o "{2}" "{3}"').format( - parse_raw, out_markup, out_filename, html_filename) + ' --to={1} -o "{2}" "{3}"') + cmd = cmd.format(parse_raw, out_markup, + out_filename, html_filename) try: rc = subprocess.call(cmd, shell=True) if rc < 0: - error = "Child was terminated by signal %d" % -rc + error = 'Child was terminated by signal %d' % -rc exit(error) elif rc > 0: - error = "Please, check your Pandoc installation." + error = 'Please, check your Pandoc installation.' exit(error) except OSError as e: - error = "Pandoc execution failed: %s" % e + error = 'Pandoc execution failed: %s' % e exit(error) os.remove(html_filename) with open(out_filename, 'r', encoding='utf-8') as fs: content = fs.read() - if out_markup == "markdown": - # In markdown, to insert a
    , end a line with two or more spaces & then a end-of-line - content = content.replace("\\\n ", " \n") - content = content.replace("\\\n", " \n") + if out_markup == 'markdown': + # In markdown, to insert a
    , end a line with two + # or more spaces & then a end-of-line + content = content.replace('\\\n ', ' \n') + content = content.replace('\\\n', ' \n') with open(out_filename, 'w', encoding='utf-8') as fs: fs.write(header + content) if wp_attach and attachments and None in attachments: print("downloading attachments that don't have a parent post") urls = attachments[None] - orphan_galleries = download_attachments(output_path, urls) + download_attachments(output_path, urls) + def main(): parser = argparse.ArgumentParser( - description="Transform feed, WordPress, Tumblr, Dotclear, or Posterous " - "files into reST (rst) or Markdown (md) files. Be sure to " - "have pandoc installed.", + description="Transform feed, WordPress, Tumblr, Dotclear, or " + "Posterous files into reST (rst) or Markdown (md) files. " + "Be sure to have pandoc installed.", formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument(dest='input', help='The input file to read') - parser.add_argument('--wpfile', action='store_true', dest='wpfile', + parser.add_argument( + dest='input', help='The input file to read') + parser.add_argument( + '--wpfile', action='store_true', dest='wpfile', help='Wordpress XML export') - parser.add_argument('--dotclear', action='store_true', dest='dotclear', + parser.add_argument( + '--dotclear', action='store_true', dest='dotclear', help='Dotclear export') - parser.add_argument('--posterous', action='store_true', dest='posterous', + parser.add_argument( + '--posterous', action='store_true', dest='posterous', help='Posterous export') - parser.add_argument('--tumblr', action='store_true', dest='tumblr', + parser.add_argument( + '--tumblr', action='store_true', dest='tumblr', help='Tumblr export') - parser.add_argument('--feed', action='store_true', dest='feed', + parser.add_argument( + '--feed', action='store_true', dest='feed', help='Feed to parse') - parser.add_argument('-o', '--output', dest='output', default='output', + parser.add_argument( + '-o', '--output', dest='output', default='output', help='Output path') - parser.add_argument('-m', '--markup', dest='markup', default='rst', + parser.add_argument( + '-m', '--markup', dest='markup', default='rst', help='Output markup format (supports rst & markdown)') - parser.add_argument('--dir-cat', action='store_true', dest='dircat', + parser.add_argument( + '--dir-cat', action='store_true', dest='dircat', help='Put files in directories with categories name') - parser.add_argument('--dir-page', action='store_true', dest='dirpage', + parser.add_argument( + '--dir-page', action='store_true', dest='dirpage', help=('Put files recognised as pages in "pages/" sub-directory' ' (wordpress import only)')) - parser.add_argument('--filter-author', dest='author', + parser.add_argument( + '--filter-author', dest='author', help='Import only post from the specified author') - parser.add_argument('--strip-raw', action='store_true', dest='strip_raw', + parser.add_argument( + '--strip-raw', action='store_true', dest='strip_raw', help="Strip raw HTML code that can't be converted to " "markup such as flash embeds or iframes (wordpress import only)") - parser.add_argument('--wp-custpost', action='store_true', + parser.add_argument( + '--wp-custpost', action='store_true', dest='wp_custpost', help='Put wordpress custom post types in directories. If used with ' '--dir-cat option directories will be created as ' '/post_type/category/ (wordpress import only)') - parser.add_argument('--wp-attach', action='store_true', dest='wp_attach', + parser.add_argument( + '--wp-attach', action='store_true', dest='wp_attach', help='(wordpress import only) Download files uploaded to wordpress as ' 'attachments. Files will be added to posts as a list in the post ' 'header. All files will be downloaded, even if ' @@ -740,16 +821,20 @@ def main(): 'with their original path inside the output directory. ' 'e.g. output/wp-uploads/date/postname/file.jpg ' '-- Requires an internet connection --') - parser.add_argument('--disable-slugs', action='store_true', + parser.add_argument( + '--disable-slugs', action='store_true', dest='disable_slugs', help='Disable storing slugs from imported posts within output. ' 'With this disabled, your Pelican URLs may not be consistent ' 'with your original posts.') - parser.add_argument('-e', '--email', dest='email', + parser.add_argument( + '-e', '--email', dest='email', help="Email address (posterous import only)") - parser.add_argument('-p', '--password', dest='password', + parser.add_argument( + '-p', '--password', dest='password', help="Password (posterous import only)") - parser.add_argument('-b', '--blogname', dest='blogname', + parser.add_argument( + '-b', '--blogname', dest='blogname', help="Blog name (Tumblr import only)") args = parser.parse_args() @@ -766,18 +851,20 @@ def main(): elif args.feed: input_type = 'feed' else: - error = "You must provide either --wpfile, --dotclear, --posterous, --tumblr or --feed options" + error = ('You must provide either --wpfile, --dotclear, ' + '--posterous, --tumblr or --feed options') exit(error) if not os.path.exists(args.output): try: os.mkdir(args.output) except OSError: - error = "Unable to create the output folder: " + args.output + error = 'Unable to create the output folder: ' + args.output exit(error) if args.wp_attach and input_type != 'wordpress': - error = "You must be importing a wordpress xml to use the --wp-attach option" + error = ('You must be importing a wordpress xml ' + 'to use the --wp-attach option') exit(error) if input_type == 'wordpress': @@ -796,14 +883,14 @@ def main(): else: attachments = None - init() # init logging - + # init logging + init() fields2pelican(fields, args.markup, args.output, dircat=args.dircat or False, dirpage=args.dirpage or False, strip_raw=args.strip_raw or False, disable_slugs=args.disable_slugs or False, filter_author=args.author, - wp_custpost = args.wp_custpost or False, - wp_attach = args.wp_attach or False, - attachments = attachments or None) + wp_custpost=args.wp_custpost or False, + wp_attach=args.wp_attach or False, + attachments=attachments or None) diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 58da4649..e6ccf2a8 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -1,18 +1,20 @@ #!/usr/bin/env python - # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function -import six +from __future__ import print_function, unicode_literals +import argparse +import codecs import os import string -import argparse import sys -import codecs + import pytz +import six + from pelican import __version__ + _TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates") @@ -44,9 +46,10 @@ CONF = { 'timezone': 'Europe/Paris' } -#url for list of valid timezones +# url for list of valid timezones _TZ_URL = 'http://en.wikipedia.org/wiki/List_of_tz_database_time_zones' + def _input_compat(prompt): if six.PY3: r = input(prompt) @@ -59,6 +62,7 @@ if six.PY3: else: str_compat = unicode + # Create a 'marked' default path, to determine if someone has supplied # a path on the command-line. class _DEFAULT_PATH_TYPE(str_compat): @@ -66,6 +70,7 @@ class _DEFAULT_PATH_TYPE(str_compat): _DEFAULT_PATH = _DEFAULT_PATH_TYPE(os.curdir) + def decoding_strings(f): def wrapper(*args, **kwargs): out = f(*args, **kwargs) @@ -164,7 +169,8 @@ def ask(question, answer=str_compat, default=None, l=None): print('You must enter an integer') return r else: - raise NotImplemented('Argument `answer` must be str_compat, bool, or integer') + raise NotImplemented( + 'Argument `answer` must be str_compat, bool, or integer') def ask_timezone(question, default, tzurl): @@ -177,7 +183,8 @@ def ask_timezone(question, default, tzurl): r = pytz.all_timezones[lower_tz.index(r)] break else: - print('Please enter a valid time zone:\n (check [{0}])'.format(tzurl)) + print('Please enter a valid time zone:\n' + ' (check [{0}])'.format(tzurl)) return r @@ -186,13 +193,13 @@ def main(): description="A kickstarter for Pelican", formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('-p', '--path', default=_DEFAULT_PATH, - help="The path to generate the blog into") + help="The path to generate the blog into") parser.add_argument('-t', '--title', metavar="title", - help='Set the title of the website') + help='Set the title of the website') parser.add_argument('-a', '--author', metavar="author", - help='Set the author name of the website') + help='Set the author name of the website') parser.add_argument('-l', '--lang', metavar="lang", - help='Set the default web site language') + help='Set the default web site language') args = parser.parse_args() @@ -214,50 +221,94 @@ needed by Pelican. 'Will save to:\n%s\n' % CONF['basedir']) else: CONF['basedir'] = os.path.abspath(os.path.expanduser( - ask('Where do you want to create your new web site?', answer=str_compat, default=args.path))) + ask('Where do you want to create your new web site?', + answer=str_compat, default=args.path))) - CONF['sitename'] = ask('What will be the title of this web site?', answer=str_compat, default=args.title) - CONF['author'] = ask('Who will be the author of this web site?', answer=str_compat, default=args.author) - CONF['lang'] = ask('What will be the default language of this web site?', str_compat, args.lang or CONF['lang'], 2) + CONF['sitename'] = ask('What will be the title of this web site?', + answer=str_compat, default=args.title) + CONF['author'] = ask('Who will be the author of this web site?', + answer=str_compat, default=args.author) + CONF['lang'] = ask('What will be the default language of this web site?', + str_compat, args.lang or CONF['lang'], 2) - if ask('Do you want to specify a URL prefix? e.g., http://example.com ', answer=bool, default=True): - CONF['siteurl'] = ask('What is your URL prefix? (see above example; no trailing slash)', str_compat, CONF['siteurl']) + if ask('Do you want to specify a URL prefix? e.g., http://example.com ', + answer=bool, default=True): + CONF['siteurl'] = ask('What is your URL prefix? (see ' + 'above example; no trailing slash)', + str_compat, CONF['siteurl']) - CONF['with_pagination'] = ask('Do you want to enable article pagination?', bool, bool(CONF['default_pagination'])) + CONF['with_pagination'] = ask('Do you want to enable article pagination?', + bool, bool(CONF['default_pagination'])) if CONF['with_pagination']: - CONF['default_pagination'] = ask('How many articles per page do you want?', int, CONF['default_pagination']) + CONF['default_pagination'] = ask('How many articles per page ' + 'do you want?', + int, CONF['default_pagination']) else: CONF['default_pagination'] = False - CONF['timezone'] = ask_timezone('What is your time zone?', CONF['timezone'], _TZ_URL) + CONF['timezone'] = ask_timezone('What is your time zone?', + CONF['timezone'], _TZ_URL) - automation = ask('Do you want to generate a Fabfile/Makefile to automate generation and publishing?', bool, True) - develop = ask('Do you want an auto-reload & simpleHTTP script to assist with theme and site development?', bool, True) + automation = ask('Do you want to generate a Fabfile/Makefile ' + 'to automate generation and publishing?', bool, True) + develop = ask('Do you want an auto-reload & simpleHTTP script ' + 'to assist with theme and site development?', bool, True) if automation: - 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_compat, CONF['ftp_host']) - CONF['ftp_user'] = ask('What is your username on that server?', str_compat, CONF['ftp_user']) - CONF['ftp_target_dir'] = ask('Where do you want to put your web site on that server?', str_compat, 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_compat, CONF['ssh_host']) - CONF['ssh_port'] = ask('What is the port of your SSH server?', int, CONF['ssh_port']) - CONF['ssh_user'] = ask('What is your username on that server?', str_compat, CONF['ssh_user']) - CONF['ssh_target_dir'] = ask('Where do you want to put your web site on that server?', str_compat, 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_compat, CONF['dropbox_dir']) - if ask('Do you want to upload your website using S3?', answer=bool, default=False): - CONF['s3_bucket'] = ask('What is the name of your S3 bucket?', str_compat, CONF['s3_bucket']) - if ask('Do you want to upload your website using Rackspace Cloud Files?', answer=bool, default=False): - CONF['cloudfiles_username'] = ask('What is your Rackspace Cloud username?', str_compat, CONF['cloudfiles_username']) - CONF['cloudfiles_api_key'] = ask('What is your Rackspace Cloud API key?', str_compat, CONF['cloudfiles_api_key']) - CONF['cloudfiles_container'] = ask('What is the name of your Cloud Files container?', str_compat, CONF['cloudfiles_container']) - if ask('Do you want to upload your website using GitHub Pages?', answer=bool, default=False): - if ask('Is this your personal page (username.github.io)?', answer=bool, default=False): - CONF['github_pages_branch'] = _GITHUB_PAGES_BRANCHES['personal'] + 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_compat, CONF['ftp_host']) + CONF['ftp_user'] = ask('What is your username on that server?', + str_compat, CONF['ftp_user']) + CONF['ftp_target_dir'] = ask('Where do you want to put your ' + 'web site on that server?', + str_compat, 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_compat, CONF['ssh_host']) + CONF['ssh_port'] = ask('What is the port of your SSH server?', + int, CONF['ssh_port']) + CONF['ssh_user'] = ask('What is your username on that server?', + str_compat, CONF['ssh_user']) + CONF['ssh_target_dir'] = ask('Where do you want to put your ' + 'web site on that server?', + str_compat, 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_compat, CONF['dropbox_dir']) + + if ask('Do you want to upload your website using S3?', + answer=bool, default=False): + CONF['s3_bucket'] = ask('What is the name of your S3 bucket?', + str_compat, CONF['s3_bucket']) + + if ask('Do you want to upload your website using ' + 'Rackspace Cloud Files?', answer=bool, default=False): + CONF['cloudfiles_username'] = ask('What is your Rackspace ' + 'Cloud username?', str_compat, + CONF['cloudfiles_username']) + CONF['cloudfiles_api_key'] = ask('What is your Rackspace ' + 'Cloud API key?', str_compat, + CONF['cloudfiles_api_key']) + CONF['cloudfiles_container'] = ask('What is the name of your ' + 'Cloud Files container?', + str_compat, + CONF['cloudfiles_container']) + + if ask('Do you want to upload your website using GitHub Pages?', + answer=bool, default=False): + if ask('Is this your personal page (username.github.io)?', + answer=bool, default=False): + CONF['github_pages_branch'] = \ + _GITHUB_PAGES_BRANCHES['personal'] else: - CONF['github_pages_branch'] = _GITHUB_PAGES_BRANCHES['project'] + CONF['github_pages_branch'] = \ + _GITHUB_PAGES_BRANCHES['project'] try: os.makedirs(os.path.join(CONF['basedir'], 'content')) @@ -270,7 +321,8 @@ needed by Pelican. print('Error: {0}'.format(e)) try: - with codecs.open(os.path.join(CONF['basedir'], 'pelicanconf.py'), 'w', 'utf-8') as fd: + with codecs.open(os.path.join(CONF['basedir'], 'pelicanconf.py'), + 'w', 'utf-8') as fd: conf_python = dict() for key, value in CONF.items(): conf_python[key] = repr(value) @@ -283,7 +335,8 @@ needed by Pelican. print('Error: {0}'.format(e)) try: - with codecs.open(os.path.join(CONF['basedir'], 'publishconf.py'), 'w', 'utf-8') as fd: + with codecs.open(os.path.join(CONF['basedir'], 'publishconf.py'), + 'w', 'utf-8') as fd: for line in get_template('publishconf.py'): template = string.Template(line) fd.write(template.safe_substitute(CONF)) @@ -293,7 +346,8 @@ needed by Pelican. if automation: try: - with codecs.open(os.path.join(CONF['basedir'], 'fabfile.py'), 'w', 'utf-8') as fd: + with codecs.open(os.path.join(CONF['basedir'], 'fabfile.py'), + 'w', 'utf-8') as fd: for line in get_template('fabfile.py'): template = string.Template(line) fd.write(template.safe_substitute(CONF)) @@ -301,7 +355,8 @@ needed by Pelican. except OSError as e: print('Error: {0}'.format(e)) try: - with codecs.open(os.path.join(CONF['basedir'], 'Makefile'), 'w', 'utf-8') as fd: + with codecs.open(os.path.join(CONF['basedir'], 'Makefile'), + 'w', 'utf-8') as fd: mkfile_template_name = 'Makefile' py_v = 'PY?=python' if six.PY3: @@ -323,7 +378,9 @@ needed by Pelican. value = '"' + value.replace('"', '\\"') + '"' conf_shell[key] = value try: - with codecs.open(os.path.join(CONF['basedir'], 'develop_server.sh'), 'w', 'utf-8') as fd: + with codecs.open(os.path.join(CONF['basedir'], + 'develop_server.sh'), + 'w', 'utf-8') as fd: lines = list(get_template('develop_server.sh')) py_v = 'PY=${PY:-python}\n' if six.PY3: @@ -333,7 +390,10 @@ needed by Pelican. template = string.Template(line) fd.write(template.safe_substitute(conf_shell)) fd.close() - os.chmod((os.path.join(CONF['basedir'], 'develop_server.sh')), 493) # mode 0o755 + + # mode 0o755 + os.chmod((os.path.join(CONF['basedir'], + 'develop_server.sh')), 493) except OSError as e: print('Error: {0}'.format(e)) diff --git a/pelican/tools/pelican_themes.py b/pelican/tools/pelican_themes.py index 8d71535d..e4bcb7c9 100755 --- a/pelican/tools/pelican_themes.py +++ b/pelican/tools/pelican_themes.py @@ -1,33 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function - -import six +from __future__ import print_function, unicode_literals import argparse import os import shutil import sys -try: - import pelican -except: - err('Cannot import pelican.\nYou must install Pelican in order to run this script.', -1) - - -global _THEMES_PATH -_THEMES_PATH = os.path.join( - os.path.dirname( - os.path.abspath( - pelican.__file__ - ) - ), - 'themes' -) - -__version__ = '0.2' -_BUILTIN_THEMES = ['simple', 'notmyidea'] - def err(msg, die=None): """Print an error message and exits if an exit code is given""" @@ -35,43 +14,71 @@ def err(msg, die=None): if die: sys.exit((die if type(die) is int else 1)) +try: + import pelican +except: + err('Cannot import pelican.\nYou must ' + 'install Pelican in order to run this script.', + -1) + + +global _THEMES_PATH +_THEMES_PATH = os.path.join( + os.path.dirname( + os.path.abspath(pelican.__file__) + ), + 'themes' +) + +__version__ = '0.2' +_BUILTIN_THEMES = ['simple', 'notmyidea'] + def main(): """Main function""" - parser = argparse.ArgumentParser(description="""Install themes for Pelican""") + parser = argparse.ArgumentParser( + description="""Install themes for Pelican""") - excl= parser.add_mutually_exclusive_group() - excl.add_argument('-l', '--list', dest='action', action="store_const", const='list', + excl = parser.add_mutually_exclusive_group() + excl.add_argument( + '-l', '--list', dest='action', action="store_const", const='list', help="Show the themes already installed and exit") - excl.add_argument('-p', '--path', dest='action', action="store_const", const='path', + excl.add_argument( + '-p', '--path', dest='action', action="store_const", const='path', help="Show the themes path and exit") - excl.add_argument('-V', '--version', action='version', version='pelican-themes v{0}'.format(__version__), + excl.add_argument( + '-V', '--version', action='version', + version='pelican-themes v{0}'.format(__version__), help='Print the version of this script') - - parser.add_argument('-i', '--install', dest='to_install', nargs='+', metavar="theme path", + parser.add_argument( + '-i', '--install', dest='to_install', nargs='+', metavar="theme path", help='The themes to install') - parser.add_argument('-r', '--remove', dest='to_remove', nargs='+', metavar="theme name", + parser.add_argument( + '-r', '--remove', dest='to_remove', nargs='+', metavar="theme name", help='The themes to remove') - parser.add_argument('-U', '--upgrade', dest='to_upgrade', nargs='+', - metavar="theme path", help='The themes to upgrade') - parser.add_argument('-s', '--symlink', dest='to_symlink', nargs='+', metavar="theme path", - help="Same as `--install', but create a symbolic link instead of copying the theme. Useful for theme development") - parser.add_argument('-c', '--clean', dest='clean', action="store_true", + parser.add_argument( + '-U', '--upgrade', dest='to_upgrade', nargs='+', + metavar="theme path", help='The themes to upgrade') + parser.add_argument( + '-s', '--symlink', dest='to_symlink', nargs='+', metavar="theme path", + help="Same as `--install', but create a symbolic link instead of " + "copying the theme. Useful for theme development") + parser.add_argument( + '-c', '--clean', dest='clean', action="store_true", help="Remove the broken symbolic links of the theme path") - - parser.add_argument('-v', '--verbose', dest='verbose', action="store_true", + parser.add_argument( + '-v', '--verbose', dest='verbose', + action="store_true", help="Verbose output") - args = parser.parse_args() - + to_install = args.to_install or args.to_upgrade to_sym = args.to_symlink or args.clean - if args.action: if args.action is 'list': list_themes(args.verbose) @@ -95,7 +102,7 @@ def main(): if args.to_upgrade: if args.verbose: print('Upgrading themes...') - + for i in args.to_upgrade: install(i, v=args.verbose, u=True) @@ -144,11 +151,13 @@ def list_themes(v=False): def remove(theme_name, v=False): """Removes a theme""" - theme_name = theme_name.replace('/','') + theme_name = theme_name.replace('/', '') target = os.path.join(_THEMES_PATH, theme_name) if theme_name in _BUILTIN_THEMES: - err(theme_name + ' is a builtin theme.\nYou cannot remove a builtin theme with this script, remove it by hand if you want.') + err(theme_name + ' is a builtin theme.\n' + 'You cannot remove a builtin theme with this script, ' + 'remove it by hand if you want.') elif os.path.islink(target): if v: print('Removing link `' + target + "'") @@ -180,7 +189,8 @@ def install(path, v=False, u=False): install(path, v) else: if v: - print("Copying `{p}' to `{t}' ...".format(p=path, t=theme_path)) + print("Copying '{p}' to '{t}' ...".format(p=path, + t=theme_path)) try: shutil.copytree(path, theme_path) @@ -189,14 +199,18 @@ def install(path, v=False, u=False): for root, dirs, files in os.walk(theme_path): for d in dirs: dname = os.path.join(root, d) - os.chmod(dname, 493) # 0o755 + os.chmod(dname, 493) # 0o755 for f in files: fname = os.path.join(root, f) - os.chmod(fname, 420) # 0o644 + os.chmod(fname, 420) # 0o644 except OSError as e: - err("Cannot change permissions of files or directory in `{r}':\n{e}".format(r=theme_path, e=str(e)), die=False) + err("Cannot change permissions of files " + "or directory in `{r}':\n{e}".format(r=theme_path, + e=str(e)), + die=False) except Exception as e: - err("Cannot copy `{p}' to `{t}':\n{e}".format(p=path, t=theme_path, e=str(e))) + err("Cannot copy `{p}' to `{t}':\n{e}".format( + p=path, t=theme_path, e=str(e))) def symlink(path, v=False): @@ -212,11 +226,13 @@ def symlink(path, v=False): err(path + ' : already exists') else: if v: - print("Linking `{p}' to `{t}' ...".format(p=path, t=theme_path)) + print("Linking `{p}' to `{t}' ...".format( + p=path, t=theme_path)) try: os.symlink(path, theme_path) except Exception as e: - err("Cannot link `{p}' to `{t}':\n{e}".format(p=path, t=theme_path, e=str(e))) + err("Cannot link `{p}' to `{t}':\n{e}".format( + p=path, t=theme_path, e=str(e))) def is_broken_link(path): @@ -227,7 +243,7 @@ def is_broken_link(path): def clean(v=False): """Removes the broken symbolic links""" - c=0 + c = 0 for path in os.listdir(_THEMES_PATH): path = os.path.join(_THEMES_PATH, path) if os.path.islink(path): @@ -236,9 +252,9 @@ def clean(v=False): print('Removing {0}'.format(path)) try: os.remove(path) - except OSError as e: + except OSError: print('Error: cannot remove {0}'.format(path)) else: - c+=1 + c += 1 print("\nRemoved {0} broken links".format(c)) diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py index 65dee23b..bf1199a8 100644 --- a/pelican/urlwrappers.py +++ b/pelican/urlwrappers.py @@ -4,9 +4,10 @@ from __future__ import unicode_literals import functools import logging import os + import six -from pelican.utils import (slugify, python_2_unicode_compatible) +from pelican.utils import python_2_unicode_compatible, slugify logger = logging.getLogger(__name__) diff --git a/pelican/utils.py b/pelican/utils.py index 43dca212..786a9425 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -1,29 +1,30 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function -import six +from __future__ import print_function, unicode_literals import codecs +import datetime import errno import fnmatch import locale import logging import os -import pytz import re import shutil import sys import traceback -import pickle -import datetime - from collections import Hashable from contextlib import contextmanager -import dateutil.parser from functools import partial from itertools import groupby -from jinja2 import Markup from operator import attrgetter -from posixpath import join as posix_join + +import dateutil.parser + +from jinja2 import Markup + +import pytz + +import six from six.moves.html_parser import HTMLParser logger = logging.getLogger(__name__) @@ -43,9 +44,9 @@ def strftime(date, date_format): formatting them with the date, (if necessary) decoding the output and replacing formatted output back. ''' - + def strip_zeros(x): + return x.lstrip('0') or '0' c89_directives = 'aAbBcdfHIjmMpSUwWxXyYzZ%' - strip_zeros = lambda x: x.lstrip('0') or '0' # grab candidate format options format_options = '%[-]?.' @@ -200,8 +201,8 @@ def deprecated_attribute(old, new, since=None, remove=None, doc=None): ' and will be removed by version {}'.format(version)) message.append('. Use {} instead.'.format(new)) logger.warning(''.join(message)) - logger.debug(''.join( - six.text_type(x) for x in traceback.format_stack())) + logger.debug(''.join(six.text_type(x) for x + in traceback.format_stack())) def fget(self): _warn() @@ -224,7 +225,7 @@ def get_date(string): """ string = re.sub(' +', ' ', string) default = SafeDatetime.now().replace(hour=0, minute=0, - second=0, microsecond=0) + second=0, microsecond=0) try: return dateutil.parser.parse(string, default=default) except (TypeError, ValueError): @@ -319,12 +320,12 @@ def copy(source, destination, ignores=None): for src_dir, subdirs, others in os.walk(source_): 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) for i in ignores)) - others[:] = (o for o in others if not any(fnmatch.fnmatch(o, i) - for i in ignores)) + others[:] = (o for o in others if not any(fnmatch.fnmatch(o, i) + for i in ignores)) if not os.path.isdir(dst_dir): logger.info('Creating directory %s', dst_dir) @@ -338,9 +339,11 @@ def copy(source, destination, ignores=None): logger.info('Copying %s to %s', src_path, dst_path) shutil.copy2(src_path, dst_path) else: - 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) + def clean_output_dir(path, retention): """Remove all files from output directory except those in retention list""" @@ -366,8 +369,8 @@ def clean_output_dir(path, retention): shutil.rmtree(file) logger.debug("Deleted directory %s", file) except Exception as e: - logger.error("Unable to delete directory %s; %s", - file, e) + logger.error("Unable to delete directory %s; %s", + file, e) elif os.path.isfile(file) or os.path.islink(file): try: os.remove(file) @@ -507,12 +510,12 @@ def process_translations(content_list, order_by=None): for slug, items in grouped_by_slugs: 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( - lambda i: i.metadata.get('translation', 'false').lower() - == 'false', - items)) - # …unless all items with that slug are translations + lambda i: + i.metadata.get('translation', 'false').lower() == 'false', + items)) + # ...unless all items with that slug are translations if not default_lang_items: default_lang_items = items @@ -522,13 +525,14 @@ def process_translations(content_list, order_by=None): len_ = len(lang_items) if len_ > 1: logger.warning('There are %s variants of "%s" with lang %s', - len_, slug, lang) + len_, slug, lang) for x in lang_items: logger.warning('\t%s', x.source_path) # find items with default language - default_lang_items = list(filter(attrgetter('in_default_lang'), - default_lang_items)) + default_lang_items = list(filter( + attrgetter('in_default_lang'), + default_lang_items)) # if there is no article with default language, take an other one if not default_lang_items: @@ -536,10 +540,9 @@ def process_translations(content_list, order_by=None): if not slug: logger.warning( - 'empty slug for %s. ' - 'You can fix this by adding a title or a slug to your ' - 'content', - default_lang_items[0].source_path) + 'Empty slug for %s. You can fix this by ' + 'adding a title or a slug to your content', + default_lang_items[0].source_path) index.extend(default_lang_items) translations.extend([x for x in items if x not in default_lang_items]) for a in items: @@ -567,10 +570,12 @@ def process_translations(content_list, order_by=None): index.sort(key=attrgetter(order_by), reverse=order_reversed) 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) else: - logger.warning('Invalid *_ORDER_BY setting (%s).' + logger.warning( + 'Invalid *_ORDER_BY setting (%s).' 'Valid options are strings and functions.', order_by) return index, translations @@ -589,12 +594,12 @@ def folder_watcher(path, extensions, ignores=[]): dirs[:] = [x for x in dirs if not x.startswith(os.curdir)] for f in files: - if (f.endswith(tuple(extensions)) and - not any(fnmatch.fnmatch(f, ignore) for ignore in ignores)): - try: - yield os.stat(os.path.join(root, f)).st_mtime - except OSError as e: - logger.warning('Caught Exception: %s', e) + if f.endswith(tuple(extensions)) and \ + not any(fnmatch.fnmatch(f, ignore) for ignore in ignores): + try: + yield os.stat(os.path.join(root, f)).st_mtime + except OSError as e: + logger.warning('Caught Exception: %s', e) LAST_MTIME = 0 while True: diff --git a/pelican/writers.py b/pelican/writers.py index e90a0004..4df7b859 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -1,22 +1,24 @@ # -*- coding: utf-8 -*- -from __future__ import with_statement, unicode_literals, print_function -import six +from __future__ import print_function, unicode_literals, with_statement -import os import logging +import os + +from feedgenerator import Atom1Feed, Rss201rev2Feed + +from jinja2 import Markup + +import six +from six.moves.urllib.parse import urlparse + +from pelican import signals +from pelican.paginator import Paginator +from pelican.utils import (get_relative_path, is_selected_for_writing, + path_to_url, set_date_tzinfo) if not six.PY3: from codecs import open -from feedgenerator import Atom1Feed, Rss201rev2Feed -from jinja2 import Markup -from six.moves.urllib.parse import urlparse - -from pelican.paginator import Paginator -from pelican.utils import (get_relative_path, path_to_url, set_date_tzinfo, - is_selected_for_writing) -from pelican import signals - logger = logging.getLogger(__name__) @@ -119,10 +121,10 @@ class Writer(object): feed.write(fp, 'utf-8') 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 - def write_file(self, name, template, context, relative_urls=False, paginated=None, override_output=False, **kwargs): """Render the template and write the file. @@ -139,9 +141,10 @@ class Writer(object): :param **kwargs: additional variables to pass to the templates """ - if name is False or name == "" or\ - not is_selected_for_writing(self.settings,\ - os.path.join(self.output_path, name)): + if name is False or \ + name == "" or \ + not is_selected_for_writing(self.settings, + os.path.join(self.output_path, name)): return elif not name: # other stuff, just return for now @@ -169,7 +172,8 @@ class Writer(object): def _get_localcontext(context, name, kwargs, relative_urls): localcontext = context.copy() - localcontext['localsiteurl'] = localcontext.get('localsiteurl', None) + localcontext['localsiteurl'] = localcontext.get( + 'localsiteurl', None) if relative_urls: relative_url = path_to_url(get_relative_path(name)) localcontext['SITEURL'] = relative_url @@ -201,11 +205,13 @@ class Writer(object): '%s_previous_page' % key: previous_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, page.save_as, override_output) else: # 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, override_output) diff --git a/tox.ini b/tox.ini index 34335b82..56ad0c14 100644 --- a/tox.ini +++ b/tox.ini @@ -38,4 +38,5 @@ deps = flake8 <= 2.4.1 git+https://github.com/public/flake8-import-order@2ac7052a4e02b4a8a0125a106d87465a3b9fd688 commands = + flake8 --version flake8 pelican From 9d57dcf020c11abfb0efc16d6d4387a269964b97 Mon Sep 17 00:00:00 2001 From: Julien Vehent Date: Wed, 19 Aug 2015 12:28:14 -0400 Subject: [PATCH 0215/1173] Fix calculation of tag count in dotclear import Upon import of a dotclear backup, `pelican-import` returned this stacktrace: ``` File "/usr/bin/pelican-import", line 11, in sys.exit(main()) File "/usr/lib/python3.4/site-packages/pelican/tools/pelican_import.py", line 809, in main attachments = attachments or None) File "/usr/lib/python3.4/site-packages/pelican/tools/pelican_import.py", line 621, in fields2pelican kind, in_markup) in fields: File "/usr/lib/python3.4/site-packages/pelican/tools/pelican_import.py", line 262, in dc2fields if int(tag[:1]) == 1: ValueError: invalid literal for int() with base 10: 'a' ``` --- pelican/tools/pelican_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 92e8c919..a6547f63 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -259,7 +259,7 @@ def dc2fields(file): # Get tags related to a post tag = post_meta.replace('{', '').replace('}', '').replace('a:1:s:3:\\"tag\\";a:', '').replace('a:0:', '') if len(tag) > 1: - if int(tag[:1]) == 1: + if int(len(tag[:1])) == 1: newtag = tag.split('"')[1] tags.append( BeautifulSoup( From 170a429c52ed57c45826fa31c31d5bf692698cac Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 25 Aug 2015 10:05:15 -0700 Subject: [PATCH 0216/1173] Add flake8, sphinx, and tox to dev requirements --- dev_requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dev_requirements.txt b/dev_requirements.txt index 028cbebd..d11b5c1f 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,5 +1,9 @@ # Tests +flake8 +-e git+https://github.com/public/flake8-import-order#egg=flake8-import-order mock +sphinx +tox # Optional Packages Markdown From ad72287b4cff8c46f35f693b487bfe39d00f0500 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 25 Aug 2015 11:25:57 -0700 Subject: [PATCH 0217/1173] Revert dev requirement additions Tox uses dev_requirements.txt to install things. That wasn't the original purpose of the dev_requirements.txt file, but for now these additions will have to remain a documentation issue until this can be sorted out properly. --- dev_requirements.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dev_requirements.txt b/dev_requirements.txt index d11b5c1f..028cbebd 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,9 +1,5 @@ # Tests -flake8 --e git+https://github.com/public/flake8-import-order#egg=flake8-import-order mock -sphinx -tox # Optional Packages Markdown From 7f795ed558f7eb5adabf1c2777db9b430ce121ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Fern=C3=A1ndez?= Date: Wed, 26 Aug 2015 11:23:28 +0200 Subject: [PATCH 0218/1173] Remove duplicate tags and authors in metadata --- pelican/readers.py | 5 ++++- .../article_with_duplicate_tags_authors.md | 15 +++++++++++++++ pelican/tests/test_generators.py | 2 ++ pelican/tests/test_readers.py | 10 ++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 pelican/tests/content/article_with_duplicate_tags_authors.md diff --git a/pelican/readers.py b/pelican/readers.py index bc4515e7..2e51c4ff 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals import logging import os import re +from collections import OrderedDict import docutils import docutils.core @@ -72,7 +73,9 @@ def ensure_metadata_list(text): else: text = text.split(',') - return [v for v in (w.strip() for w in text) if v] + return list(OrderedDict.fromkeys( + [v for v in (w.strip() for w in text) if v] + )) def _process_if_nonempty(processor, name, settings): diff --git a/pelican/tests/content/article_with_duplicate_tags_authors.md b/pelican/tests/content/article_with_duplicate_tags_authors.md new file mode 100644 index 00000000..7ab046f9 --- /dev/null +++ b/pelican/tests/content/article_with_duplicate_tags_authors.md @@ -0,0 +1,15 @@ +Title: Test metadata duplicates +Category: test +Tags: foo, bar, foobar, foo, bar +Authors: Author, First; Author, Second; Author, First +Date: 2010-12-02 10:14 +Modified: 2010-12-02 10:20 +Summary: I have a lot to test + +Test Markdown File Header +========================= + +Used for pelican test +--------------------- + +The quick brown fox jumped over the lazy dog's back. diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 2cfca04f..c9aa1cff 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -160,6 +160,7 @@ class TestArticlesGenerator(unittest.TestCase): ['Test markdown File', 'published', 'test', 'article'], ['Test md File', 'published', 'test', 'article'], ['Test mdown File', 'published', 'test', 'article'], + ['Test metadata duplicates', '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'], @@ -435,6 +436,7 @@ class TestArticlesGenerator(unittest.TestCase): 'Test markdown File', 'Test md File', 'Test mdown File', + 'Test metadata duplicates', 'Test mkd File', 'This is a super article !', 'This is a super article !', diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index 71394ee4..5fabc470 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -516,6 +516,16 @@ class MdReaderTest(ReaderTest): } self.assertDictHasSubset(page.metadata, expected) + def test_duplicate_tags_or_authors_are_removed(self): + reader = readers.MarkdownReader(settings=get_settings()) + content, metadata = reader.read( + _path('article_with_duplicate_tags_authors.md')) + expected = { + 'tags': ['foo', 'bar', 'foobar'], + 'authors': ['Author, First', 'Author, Second'], + } + self.assertDictHasSubset(metadata, expected) + class HTMLReaderTest(ReaderTest): def test_article_with_comments(self): From 9d0804de7af858880e5ef74f0c1c5d8f5ad6419b Mon Sep 17 00:00:00 2001 From: Andrea Corbellini Date: Wed, 19 Aug 2015 16:43:59 +0200 Subject: [PATCH 0219/1173] When truncating, consider hypens, apostrophes and HTML entities. --- pelican/tests/output/basic/category/misc.html | 2 +- pelican/tests/output/basic/index.html | 2 +- .../custom/author/alexis-metaireau3.html | 2 +- .../tests/output/custom/category/misc.html | 2 +- pelican/tests/output/custom/index3.html | 2 +- .../author/alexis-metaireau3.html | 4 +- .../output/custom_locale/category/misc.html | 4 +- .../tests/output/custom_locale/index3.html | 4 +- pelican/tests/test_utils.py | 28 ++++++-- pelican/utils.py | 69 ++++++++++++++++--- 10 files changed, 95 insertions(+), 24 deletions(-) diff --git a/pelican/tests/output/basic/category/misc.html b/pelican/tests/output/basic/category/misc.html index 0368793e..f491a464 100644 --- a/pelican/tests/output/basic/category/misc.html +++ b/pelican/tests/output/basic/category/misc.html @@ -90,7 +90,7 @@

    Testing another case

    This will now have a line number in 'custom' since it's the default in -pelican.conf, it ...

    +pelican.conf, it will ...

    read more
    diff --git a/pelican/tests/output/basic/index.html b/pelican/tests/output/basic/index.html index 3066172d..4c74500d 100644 --- a/pelican/tests/output/basic/index.html +++ b/pelican/tests/output/basic/index.html @@ -227,7 +227,7 @@ YEAH !

    Testing another case

    This will now have a line number in 'custom' since it's the default in -pelican.conf, it ...

    +pelican.conf, it will ...

    read more
    diff --git a/pelican/tests/output/custom/author/alexis-metaireau3.html b/pelican/tests/output/custom/author/alexis-metaireau3.html index 54c768ac..3ca4dd0d 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau3.html +++ b/pelican/tests/output/custom/author/alexis-metaireau3.html @@ -59,7 +59,7 @@

    Testing another case

    This will now have a line number in 'custom' since it's the default in -pelican.conf, it ...

    +pelican.conf, it will ...

    read more

    There are comments.

    diff --git a/pelican/tests/output/custom/category/misc.html b/pelican/tests/output/custom/category/misc.html index fa71085d..b705a552 100644 --- a/pelican/tests/output/custom/category/misc.html +++ b/pelican/tests/output/custom/category/misc.html @@ -103,7 +103,7 @@

    Testing another case

    This will now have a line number in 'custom' since it's the default in -pelican.conf, it ...

    +pelican.conf, it will ...

    read more

    There are comments.

    diff --git a/pelican/tests/output/custom/index3.html b/pelican/tests/output/custom/index3.html index 1dab4e7d..b968b7e8 100644 --- a/pelican/tests/output/custom/index3.html +++ b/pelican/tests/output/custom/index3.html @@ -59,7 +59,7 @@

    Testing another case

    This will now have a line number in 'custom' since it's the default in -pelican.conf, it ...

    +pelican.conf, it will ...

    read more

    There are comments.

    diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html index 66575c71..2fea24c3 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html @@ -59,7 +59,7 @@

    Testing another case

    This will now have a line number in 'custom' since it's the default in -pelican.conf, it ...

    +pelican.conf, it will ...

    read more

    There are comments.

    @@ -135,4 +135,4 @@ pelican.conf, it ...

    }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/category/misc.html b/pelican/tests/output/custom_locale/category/misc.html index bb78a8cc..f44f725d 100644 --- a/pelican/tests/output/custom_locale/category/misc.html +++ b/pelican/tests/output/custom_locale/category/misc.html @@ -103,7 +103,7 @@

    Testing another case

    This will now have a line number in 'custom' since it's the default in -pelican.conf, it ...

    +pelican.conf, it will ...

    read more

    There are comments.

    @@ -175,4 +175,4 @@ pelican.conf, it ...

    }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/index3.html b/pelican/tests/output/custom_locale/index3.html index 49f70ba2..926bc25e 100644 --- a/pelican/tests/output/custom_locale/index3.html +++ b/pelican/tests/output/custom_locale/index3.html @@ -59,7 +59,7 @@

    Testing another case

    This will now have a line number in 'custom' since it's the default in -pelican.conf, it ...

    +pelican.conf, it will ...

    read more

    There are comments.

    @@ -135,4 +135,4 @@ pelican.conf, it ...

    }()); - \ No newline at end of file + diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index d6fdf70e..a076a2c7 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -146,31 +146,51 @@ class TestUtils(LoggedTestCase): self.assertEqual(utils.get_relative_path(value), expected) def test_truncate_html_words(self): + # Plain text. self.assertEqual( utils.truncate_html_words('short string', 20), 'short string') - self.assertEqual( utils.truncate_html_words('word ' * 100, 20), 'word ' * 20 + '...') + # Words enclosed or intervaled by HTML tags. self.assertEqual( utils.truncate_html_words('

    ' + 'word ' * 100 + '

    ', 20), '

    ' + 'word ' * 20 + '...

    ') - self.assertEqual( utils.truncate_html_words( '' + 'word ' * 100 + '', 20), '' + 'word ' * 20 + '...') - self.assertEqual( utils.truncate_html_words('
    ' + 'word ' * 100, 20), '
    ' + 'word ' * 20 + '...') - self.assertEqual( utils.truncate_html_words('' + 'word ' * 100, 20), '' + 'word ' * 20 + '...') + # Words with hypens and apostrophes. + self.assertEqual( + utils.truncate_html_words("a-b " * 100, 20), + "a-b " * 20 + '...') + self.assertEqual( + utils.truncate_html_words("it's " * 100, 20), + "it's " * 20 + '...') + + # Words with HTML entity references. + self.assertEqual( + utils.truncate_html_words("é " * 100, 20), + "é " * 20 + '...') + self.assertEqual( + utils.truncate_html_words("café " * 100, 20), + "café " * 20 + '...') + self.assertEqual( + utils.truncate_html_words("èlite " * 100, 20), + "èlite " * 20 + '...') + self.assertEqual( + utils.truncate_html_words("cafetiére " * 100, 20), + "cafetiére " * 20 + '...') + def test_process_translations(self): # create a bunch of articles # 1: no translation metadata diff --git a/pelican/utils.py b/pelican/utils.py index 786a9425..7ad0914c 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -25,6 +25,7 @@ from jinja2 import Markup import pytz import six +from six.moves import html_entities from six.moves.html_parser import HTMLParser logger = logging.getLogger(__name__) @@ -408,7 +409,8 @@ def posixize_path(rel_path): class _HTMLWordTruncator(HTMLParser): - _word_regex = re.compile(r'\w[\w-]*', re.U) + _word_regex = re.compile(r"\w[\w'-]*", re.U) + _word_prefix_regex = re.compile(r'\w', re.U) _singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input') @@ -420,17 +422,37 @@ class _HTMLWordTruncator(HTMLParser): self.max_words = max_words self.words_found = 0 self.open_tags = [] + self.last_word_end = None self.truncate_at = None + def getoffset(self): + line_start = 0 + lineno, line_offset = self.getpos() + for i in range(lineno - 1): + line_start = self.rawdata.index('\n', line_start) + 1 + return line_start + line_offset + + def add_word(self, word_end): + self.words_found += 1 + self.last_word_end = None + if self.words_found == self.max_words: + self.truncate_at = word_end + + def add_last_word(self): + if self.last_word_end is not None: + self.add_word(self.last_word_end) + def handle_starttag(self, tag, attrs): if self.truncate_at is not None: return + self.add_last_word() if tag not in self._singlets: self.open_tags.insert(0, tag) def handle_endtag(self, tag): if self.truncate_at is not None: return + self.add_last_word() try: i = self.open_tags.index(tag) except ValueError: @@ -442,20 +464,49 @@ class _HTMLWordTruncator(HTMLParser): def handle_data(self, data): word_end = 0 + offset = self.getoffset() while self.words_found < self.max_words: match = self._word_regex.search(data, word_end) if not match: break - word_end = match.end(0) - self.words_found += 1 - if self.words_found == self.max_words: - line_start = 0 - lineno, line_offset = self.getpos() - for i in range(lineno - 1): - line_start = self.rawdata.index('\n', line_start) + 1 - self.truncate_at = line_start + line_offset + word_end + if match.start(0) > 0: + self.add_last_word() + + word_end = match.end(0) + self.last_word_end = offset + word_end + + if word_end < len(data): + self.add_last_word() + + def handle_ref(self, char): + offset = self.getoffset() + ref_end = self.rawdata.index(';', offset) + 1 + + if self.last_word_end is None: + if self._word_prefix_regex.match(char): + self.last_word_end = ref_end + else: + if self._word_regex.match(char): + self.last_word_end = ref_end + else: + self.add_last_word() + + def handle_entityref(self, name): + try: + codepoint = html_entities.name2codepoint[name] + except KeyError: + self.handle_ref('') + else: + self.handle_ref(chr(codepoint)) + + def handle_charref(self, name): + if name.startswith('x'): + codepoint = int(name[1:], 16) + else: + codepoint = int(name) + self.handle_ref(chr(codepoint)) def truncate_html_words(s, num, end_text='...'): From b0d41f081b83cb8b1534d907b95ea5f198f9ea67 Mon Sep 17 00:00:00 2001 From: derwinlu Date: Sat, 5 Sep 2015 10:53:44 +0200 Subject: [PATCH 0220/1173] remove article.keywords from simple theme We don't process 'keywords' metadata specially, so it never gets processed into a list. This prevents issues like #1733. --- pelican/themes/simple/templates/article.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pelican/themes/simple/templates/article.html b/pelican/themes/simple/templates/article.html index 8ddda4d0..48464ce4 100644 --- a/pelican/themes/simple/templates/article.html +++ b/pelican/themes/simple/templates/article.html @@ -1,10 +1,6 @@ {% extends "base.html" %} {% block head %} {{ super() }} - {% for keyword in article.keywords %} - - {% endfor %} - {% if article.description %} {% endif %} From 6afa7704b149c60384e5598afd9ceb11e064d373 Mon Sep 17 00:00:00 2001 From: derwinlu Date: Fri, 11 Sep 2015 22:06:51 +0200 Subject: [PATCH 0221/1173] Add versioning information to debug output --- pelican/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pelican/__init__.py b/pelican/__init__.py index 7fb8dfe4..48cb5980 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -379,6 +379,10 @@ def get_instance(args): def main(): args = parse_arguments() init(args.verbosity) + + logger.debug('Pelican version: %s', __version__) + logger.debug('Python version: %s', sys.version.split()[0]) + pelican, settings = get_instance(args) readers = Readers(settings) From a6c258eb7fda71e7421ae6b41180f2103c166bf3 Mon Sep 17 00:00:00 2001 From: Onur Aslan Date: Tue, 15 Sep 2015 02:24:21 +0300 Subject: [PATCH 0222/1173] Add index and author replacement indicators --- docs/content.rst | 8 ++++---- pelican/contents.py | 6 +++++- pelican/settings.py | 1 + pelican/tests/test_contents.py | 34 +++++++++++++++++++++++++++++++ pelican/tests/test_urlwrappers.py | 8 +++++++- 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/docs/content.rst b/docs/content.rst index 1e6ba099..0fa89921 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -329,11 +329,11 @@ of ``{attach}``, and letting the file's location be determined by the project's ``STATIC_SAVE_AS`` and ``STATIC_URL`` settings. (Per-file ``save_as`` and ``url`` overrides can still be set in ``EXTRA_PATH_METADATA``.) -Linking to tags and categories ------------------------------- +Linking to authors, categories, index and tags +---------------------------------------------- -You can link to tags and categories using the ``{tag}tagname`` and -``{category}foobar`` syntax. +You can link to authors, categories, index and tags using the ``{author}name``, +``{category}foobar``, ``{index}`` and ``{tag}tagname`` syntax. Deprecated internal link syntax ------------------------------- diff --git a/pelican/contents.py b/pelican/contents.py index 16d1f074..4d313ab8 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -21,7 +21,7 @@ from pelican.utils import (SafeDatetime, deprecated_attribute, memoized, slugify, strftime, truncate_html_words) # Import these so that they're avalaible when you import from pelican.contents. -from pelican.urlwrappers import (URLWrapper, Author, Category, Tag) # NOQA +from pelican.urlwrappers import (Author, Category, Tag, URLWrapper) # NOQA logger = logging.getLogger(__name__) @@ -253,6 +253,10 @@ class Content(object): origin = '/'.join((siteurl, Category(path, self.settings).url)) elif what == 'tag': origin = '/'.join((siteurl, Tag(path, self.settings).url)) + elif what == 'index': + origin = '/'.join((siteurl, self.settings['INDEX_SAVE_AS'])) + elif what == 'author': + origin = '/'.join((siteurl, Author(path, self.settings).url)) else: logger.warning( "Replacement Indicator '%s' not recognized, " diff --git a/pelican/settings.py b/pelican/settings.py index 4d75333a..d1b1648b 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -64,6 +64,7 @@ DEFAULT_CONFIG = { 'REVERSE_CATEGORY_ORDER': False, 'DELETE_OUTPUT_DIRECTORY': False, 'OUTPUT_RETENTION': [], + 'INDEX_SAVE_AS': 'index.html', 'ARTICLE_URL': '{slug}.html', 'ARTICLE_SAVE_AS': '{slug}.html', 'ARTICLE_ORDER_BY': 'reversed-date', diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index a3664383..59cff844 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -606,6 +606,40 @@ class TestStatic(LoggedTestCase): self.assertNotEqual(content, html) + def test_author_link_syntax(self): + "{author} link syntax triggers url replacement." + + html = 'link' + page = Page( + content=html, + metadata={'title': 'fakepage'}, + settings=self.settings, + source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), + context=self.context) + content = page.get_content('') + + self.assertNotEqual(content, html) + + def test_index_link_syntax(self): + "{index} link syntax triggers url replacement." + + html = 'link' + page = Page( + content=html, + metadata={'title': 'fakepage'}, + settings=self.settings, + source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), + context=self.context) + content = page.get_content('') + + self.assertNotEqual(content, html) + + expected_html = ('link') + self.assertEqual(content, expected_html) + def test_unknown_link_syntax(self): "{unknown} link syntax should trigger warning." diff --git a/pelican/tests/test_urlwrappers.py b/pelican/tests/test_urlwrappers.py index ae6eaaec..f3dc8198 100644 --- a/pelican/tests/test_urlwrappers.py +++ b/pelican/tests/test_urlwrappers.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from pelican.tests.support import unittest -from pelican.urlwrappers import Category, Tag, URLWrapper +from pelican.urlwrappers import Author, Category, Tag, URLWrapper class TestURLWrapper(unittest.TestCase): @@ -34,9 +34,11 @@ class TestURLWrapper(unittest.TestCase): def test_equality(self): tag = Tag('test', settings={}) cat = Category('test', settings={}) + author = Author('test', settings={}) # same name, but different class self.assertNotEqual(tag, cat) + self.assertNotEqual(tag, author) # should be equal vs text representing the same name self.assertEqual(tag, u'test') @@ -48,5 +50,9 @@ class TestURLWrapper(unittest.TestCase): tag_equal = Tag('Test', settings={}) self.assertEqual(tag, tag_equal) + # Author describing the same should be equal + author_equal = Author('Test', settings={}) + self.assertEqual(author, author_equal) + cat_ascii = Category('指導書', settings={}) self.assertEqual(cat_ascii, u'zhi-dao-shu') From d583efb8616cf19401865b04c4270b1a41a96d7f Mon Sep 17 00:00:00 2001 From: Andrea Corbellini Date: Fri, 4 Sep 2015 16:49:41 +0200 Subject: [PATCH 0223/1173] When truncating, stop parsing the document immediately after finding the last word. --- pelican/utils.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/pelican/utils.py b/pelican/utils.py index 7ad0914c..97768f53 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -414,6 +414,13 @@ class _HTMLWordTruncator(HTMLParser): _singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input') + class TruncationCompleted(Exception): + + def __init__(self, truncate_at): + super(_HTMLWordTruncator.TruncationCompleted, self).__init__( + truncate_at) + self.truncate_at = truncate_at + def __init__(self, max_words): # In Python 2, HTMLParser is not a new-style class, # hence super() cannot be used. @@ -425,6 +432,16 @@ class _HTMLWordTruncator(HTMLParser): self.last_word_end = None self.truncate_at = None + def feed(self, *args, **kwargs): + try: + # With Python 2, super() cannot be used. + # See the comment for __init__(). + HTMLParser.feed(self, *args, **kwargs) + except self.TruncationCompleted as exc: + self.truncate_at = exc.truncate_at + else: + self.truncate_at = None + def getoffset(self): line_start = 0 lineno, line_offset = self.getpos() @@ -436,22 +453,18 @@ class _HTMLWordTruncator(HTMLParser): self.words_found += 1 self.last_word_end = None if self.words_found == self.max_words: - self.truncate_at = word_end + raise self.TruncationCompleted(word_end) def add_last_word(self): if self.last_word_end is not None: self.add_word(self.last_word_end) def handle_starttag(self, tag, attrs): - if self.truncate_at is not None: - return self.add_last_word() if tag not in self._singlets: self.open_tags.insert(0, tag) def handle_endtag(self, tag): - if self.truncate_at is not None: - return self.add_last_word() try: i = self.open_tags.index(tag) From c255a35800911f74a3d07689cc6f1296cb6660a9 Mon Sep 17 00:00:00 2001 From: Andrea Corbellini Date: Tue, 22 Sep 2015 20:52:30 +0200 Subject: [PATCH 0224/1173] Use unichr() instead of chr() with Python 2. --- pelican/tests/test_utils.py | 12 ++++++++++++ pelican/utils.py | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index a076a2c7..d967b247 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -190,6 +190,18 @@ class TestUtils(LoggedTestCase): self.assertEqual( utils.truncate_html_words("cafetiére " * 100, 20), "cafetiére " * 20 + '...') + self.assertEqual( + utils.truncate_html_words("∫dx " * 100, 20), + "∫dx " * 20 + '...') + + # Words with HTML character references inside and outside + # the ASCII range. + self.assertEqual( + utils.truncate_html_words("é " * 100, 20), + "é " * 20 + '...') + self.assertEqual( + utils.truncate_html_words("∫dx " * 100, 20), + "∫dx " * 20 + '...') def test_process_translations(self): # create a bunch of articles diff --git a/pelican/utils.py b/pelican/utils.py index 7ad0914c..697a182b 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -499,14 +499,14 @@ class _HTMLWordTruncator(HTMLParser): except KeyError: self.handle_ref('') else: - self.handle_ref(chr(codepoint)) + self.handle_ref(six.unichr(codepoint)) def handle_charref(self, name): if name.startswith('x'): codepoint = int(name[1:], 16) else: codepoint = int(name) - self.handle_ref(chr(codepoint)) + self.handle_ref(six.unichr(codepoint)) def truncate_html_words(s, num, end_text='...'): From a670a3e0409719779d6dbe5939e6c39836e6162b Mon Sep 17 00:00:00 2001 From: Ben Rosser Date: Mon, 21 Sep 2015 00:15:29 -0400 Subject: [PATCH 0225/1173] Made name of blogroll/social widgets configurable. The BLOGROLL_WIDGET_NAME and SOCIAL_WIDGET_NAME settings are now respected by notmyidea if they are specified in your config file. They override the default names of "blogroll" and "links" in the notmyidea theme. Used default() in template to simplify template code. Renaming BLOGROLL setting to LINKS, changed default also. Updated tests to check 'links' instead of 'blogroll'. Whoops; links, not link. --- THANKS | 1 + docs/settings.rst | 4 ++++ pelican/tests/output/custom/a-markdown-powered-article.html | 2 +- pelican/tests/output/custom/archives.html | 2 +- pelican/tests/output/custom/article-1.html | 2 +- pelican/tests/output/custom/article-2.html | 2 +- pelican/tests/output/custom/article-3.html | 2 +- pelican/tests/output/custom/author/alexis-metaireau.html | 2 +- pelican/tests/output/custom/author/alexis-metaireau2.html | 2 +- pelican/tests/output/custom/author/alexis-metaireau3.html | 2 +- pelican/tests/output/custom/authors.html | 2 +- pelican/tests/output/custom/categories.html | 2 +- pelican/tests/output/custom/category/bar.html | 2 +- pelican/tests/output/custom/category/cat1.html | 2 +- pelican/tests/output/custom/category/misc.html | 2 +- pelican/tests/output/custom/category/yeah.html | 2 +- pelican/tests/output/custom/drafts/a-draft-article.html | 2 +- pelican/tests/output/custom/filename_metadata-example.html | 2 +- pelican/tests/output/custom/index.html | 2 +- pelican/tests/output/custom/index2.html | 2 +- pelican/tests/output/custom/index3.html | 2 +- pelican/tests/output/custom/jinja2_template.html | 2 +- pelican/tests/output/custom/oh-yeah-fr.html | 2 +- pelican/tests/output/custom/oh-yeah.html | 2 +- pelican/tests/output/custom/override/index.html | 2 +- .../tests/output/custom/pages/this-is-a-test-hidden-page.html | 2 +- pelican/tests/output/custom/pages/this-is-a-test-page.html | 2 +- pelican/tests/output/custom/second-article-fr.html | 2 +- pelican/tests/output/custom/second-article.html | 2 +- pelican/tests/output/custom/tag/bar.html | 2 +- pelican/tests/output/custom/tag/baz.html | 2 +- pelican/tests/output/custom/tag/foo.html | 2 +- pelican/tests/output/custom/tag/foobar.html | 2 +- pelican/tests/output/custom/tag/oh.html | 2 +- pelican/tests/output/custom/tag/yeah.html | 2 +- pelican/tests/output/custom/tags.html | 2 +- pelican/tests/output/custom/this-is-a-super-article.html | 2 +- pelican/tests/output/custom/unbelievable.html | 2 +- pelican/tests/output/custom_locale/archives.html | 2 +- .../tests/output/custom_locale/author/alexis-metaireau.html | 2 +- .../tests/output/custom_locale/author/alexis-metaireau2.html | 2 +- .../tests/output/custom_locale/author/alexis-metaireau3.html | 2 +- pelican/tests/output/custom_locale/authors.html | 2 +- pelican/tests/output/custom_locale/categories.html | 2 +- pelican/tests/output/custom_locale/category/bar.html | 2 +- pelican/tests/output/custom_locale/category/cat1.html | 2 +- pelican/tests/output/custom_locale/category/misc.html | 2 +- pelican/tests/output/custom_locale/category/yeah.html | 2 +- .../tests/output/custom_locale/drafts/a-draft-article.html | 2 +- pelican/tests/output/custom_locale/index.html | 2 +- pelican/tests/output/custom_locale/index2.html | 2 +- pelican/tests/output/custom_locale/index3.html | 2 +- pelican/tests/output/custom_locale/jinja2_template.html | 2 +- pelican/tests/output/custom_locale/oh-yeah-fr.html | 2 +- pelican/tests/output/custom_locale/override/index.html | 2 +- .../custom_locale/pages/this-is-a-test-hidden-page.html | 2 +- .../tests/output/custom_locale/pages/this-is-a-test-page.html | 2 +- .../posts/2010/décembre/02/this-is-a-super-article/index.html | 2 +- .../posts/2010/octobre/15/unbelievable/index.html | 2 +- .../custom_locale/posts/2010/octobre/20/oh-yeah/index.html | 2 +- .../posts/2011/avril/20/a-markdown-powered-article/index.html | 2 +- .../custom_locale/posts/2011/février/17/article-1/index.html | 2 +- .../custom_locale/posts/2011/février/17/article-2/index.html | 2 +- .../custom_locale/posts/2011/février/17/article-3/index.html | 2 +- .../posts/2012/février/29/second-article/index.html | 2 +- .../2012/novembre/30/filename_metadata-example/index.html | 2 +- pelican/tests/output/custom_locale/second-article-fr.html | 2 +- pelican/tests/output/custom_locale/tag/bar.html | 2 +- pelican/tests/output/custom_locale/tag/baz.html | 2 +- pelican/tests/output/custom_locale/tag/foo.html | 2 +- pelican/tests/output/custom_locale/tag/foobar.html | 2 +- pelican/tests/output/custom_locale/tag/oh.html | 2 +- pelican/tests/output/custom_locale/tag/yeah.html | 2 +- pelican/tests/output/custom_locale/tags.html | 2 +- pelican/themes/notmyidea/templates/base.html | 4 ++-- 75 files changed, 79 insertions(+), 74 deletions(-) diff --git a/THANKS b/THANKS index 15503473..a3af2426 100644 --- a/THANKS +++ b/THANKS @@ -24,6 +24,7 @@ Andrew Spiers Arnaud BOS asselinpaul Axel Haustant +Ben Rosser (TC01) Benoît HERVIER Borgar Brandon W Maister diff --git a/docs/settings.rst b/docs/settings.rst index 202fc45f..61fd8521 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -724,6 +724,10 @@ Setting name What does it do? ``TWITTER_USERNAME`` Allows for adding a button to articles to encourage others to tweet about them. Add your Twitter username if you want this button to appear. +``LINKS_WIDGET_NAME`` Allows override of the name of the links widget. + If not specified, defaults to "links". +``SOCIAL_WIDGET_NAME`` Allows override of the name of the "social" widget. + If not specified, defaults to "social". ======================= ======================================================= In addition, you can use the "wide" version of the ``notmyidea`` theme by diff --git a/pelican/tests/output/custom/a-markdown-powered-article.html b/pelican/tests/output/custom/a-markdown-powered-article.html index 59ffa4d1..6ccc6c42 100644 --- a/pelican/tests/output/custom/a-markdown-powered-article.html +++ b/pelican/tests/output/custom/a-markdown-powered-article.html @@ -71,7 +71,7 @@
    -

    blogroll

    +

    links

    • Biologeek
    • Filyb
    • diff --git a/pelican/tests/output/custom/archives.html b/pelican/tests/output/custom/archives.html index e238b73d..c256b7de 100644 --- a/pelican/tests/output/custom/archives.html +++ b/pelican/tests/output/custom/archives.html @@ -56,7 +56,7 @@
    -

    blogroll

    +

    links

    • Biologeek
    • Filyb
    • diff --git a/pelican/tests/output/custom/article-1.html b/pelican/tests/output/custom/article-1.html index 1fbd6719..33cf3a5f 100644 --- a/pelican/tests/output/custom/article-1.html +++ b/pelican/tests/output/custom/article-1.html @@ -70,7 +70,7 @@
    -

    blogroll

    +

    links

    • Biologeek
    • Filyb
    • diff --git a/pelican/tests/output/custom/article-2.html b/pelican/tests/output/custom/article-2.html index d62ddd14..8c8e47eb 100644 --- a/pelican/tests/output/custom/article-2.html +++ b/pelican/tests/output/custom/article-2.html @@ -70,7 +70,7 @@
    -

    blogroll

    +

    links

    • Biologeek
    • Filyb
    • diff --git a/pelican/tests/output/custom/article-3.html b/pelican/tests/output/custom/article-3.html index c862a0c2..773b89e0 100644 --- a/pelican/tests/output/custom/article-3.html +++ b/pelican/tests/output/custom/article-3.html @@ -70,7 +70,7 @@
    -

    blogroll

    +

    links

    • Biologeek
    • Filyb
    • diff --git a/pelican/tests/output/custom/author/alexis-metaireau.html b/pelican/tests/output/custom/author/alexis-metaireau.html index cbeb0555..d6521a5e 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau.html +++ b/pelican/tests/output/custom/author/alexis-metaireau.html @@ -129,7 +129,7 @@
    -

    blogroll

    +

    links

    • Biologeek
    • Filyb
    • diff --git a/pelican/tests/output/custom/author/alexis-metaireau2.html b/pelican/tests/output/custom/author/alexis-metaireau2.html index 6357bfb3..2101719b 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau2.html +++ b/pelican/tests/output/custom/author/alexis-metaireau2.html @@ -143,7 +143,7 @@ YEAH !

    -

    blogroll

    +

    links

    • Biologeek
    • Filyb
    • diff --git a/pelican/tests/output/custom/author/alexis-metaireau3.html b/pelican/tests/output/custom/author/alexis-metaireau3.html index 3ca4dd0d..d531fd1e 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau3.html +++ b/pelican/tests/output/custom/author/alexis-metaireau3.html @@ -94,7 +94,7 @@ pelican.conf, it will ...

    -

    blogroll

    +

    links

    • Biologeek
    • Filyb
    • diff --git a/pelican/tests/output/custom/authors.html b/pelican/tests/output/custom/authors.html index f157de26..6539b3e9 100644 --- a/pelican/tests/output/custom/authors.html +++ b/pelican/tests/output/custom/authors.html @@ -38,7 +38,7 @@
      -

      blogroll

      +

      links

      • Biologeek
      • Filyb
      • diff --git a/pelican/tests/output/custom/categories.html b/pelican/tests/output/custom/categories.html index ddb1377b..17f44be7 100644 --- a/pelican/tests/output/custom/categories.html +++ b/pelican/tests/output/custom/categories.html @@ -36,7 +36,7 @@
      -

      blogroll

      +

      links

      • Biologeek
      • Filyb
      • diff --git a/pelican/tests/output/custom/category/bar.html b/pelican/tests/output/custom/category/bar.html index 9666d2e2..79803b1a 100644 --- a/pelican/tests/output/custom/category/bar.html +++ b/pelican/tests/output/custom/category/bar.html @@ -54,7 +54,7 @@ YEAH !

        -

        blogroll

        +

        links

        • Biologeek
        • Filyb
        • diff --git a/pelican/tests/output/custom/category/cat1.html b/pelican/tests/output/custom/category/cat1.html index d9132d10..0230ac2f 100644 --- a/pelican/tests/output/custom/category/cat1.html +++ b/pelican/tests/output/custom/category/cat1.html @@ -123,7 +123,7 @@
        -

        blogroll

        +

        links

        • Biologeek
        • Filyb
        • diff --git a/pelican/tests/output/custom/category/misc.html b/pelican/tests/output/custom/category/misc.html index b705a552..abe32c8d 100644 --- a/pelican/tests/output/custom/category/misc.html +++ b/pelican/tests/output/custom/category/misc.html @@ -134,7 +134,7 @@ pelican.conf, it will ...

        -

        blogroll

        +

        links

        • Biologeek
        • Filyb
        • diff --git a/pelican/tests/output/custom/category/yeah.html b/pelican/tests/output/custom/category/yeah.html index 6d94c9ad..d22bb9f8 100644 --- a/pelican/tests/output/custom/category/yeah.html +++ b/pelican/tests/output/custom/category/yeah.html @@ -62,7 +62,7 @@
          -

          blogroll

          +

          links

          • Biologeek
          • Filyb
          • diff --git a/pelican/tests/output/custom/drafts/a-draft-article.html b/pelican/tests/output/custom/drafts/a-draft-article.html index 963722f6..ba424259 100644 --- a/pelican/tests/output/custom/drafts/a-draft-article.html +++ b/pelican/tests/output/custom/drafts/a-draft-article.html @@ -56,7 +56,7 @@ listed anywhere else.

          -

          blogroll

          +

          links

          • Biologeek
          • Filyb
          • diff --git a/pelican/tests/output/custom/filename_metadata-example.html b/pelican/tests/output/custom/filename_metadata-example.html index cf4fc782..c4977f63 100644 --- a/pelican/tests/output/custom/filename_metadata-example.html +++ b/pelican/tests/output/custom/filename_metadata-example.html @@ -70,7 +70,7 @@
          -

          blogroll

          +

          links

          • Biologeek
          • Filyb
          • diff --git a/pelican/tests/output/custom/index.html b/pelican/tests/output/custom/index.html index 284bd0f4..87af6a06 100644 --- a/pelican/tests/output/custom/index.html +++ b/pelican/tests/output/custom/index.html @@ -129,7 +129,7 @@
          -

          blogroll

          +

          links

          • Biologeek
          • Filyb
          • diff --git a/pelican/tests/output/custom/index2.html b/pelican/tests/output/custom/index2.html index 7e5b7230..0c457a65 100644 --- a/pelican/tests/output/custom/index2.html +++ b/pelican/tests/output/custom/index2.html @@ -143,7 +143,7 @@ YEAH !

          -

          blogroll

          +

          links

          • Biologeek
          • Filyb
          • diff --git a/pelican/tests/output/custom/index3.html b/pelican/tests/output/custom/index3.html index b968b7e8..523288c5 100644 --- a/pelican/tests/output/custom/index3.html +++ b/pelican/tests/output/custom/index3.html @@ -94,7 +94,7 @@ pelican.conf, it will ...

          -

          blogroll

          +

          links

          • Biologeek
          • Filyb
          • diff --git a/pelican/tests/output/custom/jinja2_template.html b/pelican/tests/output/custom/jinja2_template.html index 21f678f9..a098ed49 100644 --- a/pelican/tests/output/custom/jinja2_template.html +++ b/pelican/tests/output/custom/jinja2_template.html @@ -33,7 +33,7 @@ Some text
            -

            blogroll

            +

            links

            • Biologeek
            • Filyb
            • diff --git a/pelican/tests/output/custom/oh-yeah-fr.html b/pelican/tests/output/custom/oh-yeah-fr.html index d457b1bf..9f56f27b 100644 --- a/pelican/tests/output/custom/oh-yeah-fr.html +++ b/pelican/tests/output/custom/oh-yeah-fr.html @@ -72,7 +72,7 @@ Translations:
            -

            blogroll

            +

            links

            • Biologeek
            • Filyb
            • diff --git a/pelican/tests/output/custom/oh-yeah.html b/pelican/tests/output/custom/oh-yeah.html index 235a4903..caddf53c 100644 --- a/pelican/tests/output/custom/oh-yeah.html +++ b/pelican/tests/output/custom/oh-yeah.html @@ -77,7 +77,7 @@ YEAH !

            -

            blogroll

            +

            links

            • Biologeek
            • Filyb
            • diff --git a/pelican/tests/output/custom/override/index.html b/pelican/tests/output/custom/override/index.html index 3753ba84..8ab5f7eb 100644 --- a/pelican/tests/output/custom/override/index.html +++ b/pelican/tests/output/custom/override/index.html @@ -37,7 +37,7 @@ at a custom location.

            -

            blogroll

            +

            links

            • Biologeek
            • Filyb
            • diff --git a/pelican/tests/output/custom/pages/this-is-a-test-hidden-page.html b/pelican/tests/output/custom/pages/this-is-a-test-hidden-page.html index f8c06b1f..41beb5a3 100644 --- a/pelican/tests/output/custom/pages/this-is-a-test-hidden-page.html +++ b/pelican/tests/output/custom/pages/this-is-a-test-hidden-page.html @@ -37,7 +37,7 @@ Anyone can see this page but it's not linked to anywhere!

            -

            blogroll

            +

            links

            • Biologeek
            • Filyb
            • diff --git a/pelican/tests/output/custom/pages/this-is-a-test-page.html b/pelican/tests/output/custom/pages/this-is-a-test-page.html index a4b171c9..b548b52c 100644 --- a/pelican/tests/output/custom/pages/this-is-a-test-page.html +++ b/pelican/tests/output/custom/pages/this-is-a-test-page.html @@ -37,7 +37,7 @@
            -

            blogroll

            +

            links

            • Biologeek
            • Filyb
            • diff --git a/pelican/tests/output/custom/second-article-fr.html b/pelican/tests/output/custom/second-article-fr.html index 42289377..9ada2475 100644 --- a/pelican/tests/output/custom/second-article-fr.html +++ b/pelican/tests/output/custom/second-article-fr.html @@ -72,7 +72,7 @@
            -

            blogroll

            +

            links

            • Biologeek
            • Filyb
            • diff --git a/pelican/tests/output/custom/second-article.html b/pelican/tests/output/custom/second-article.html index 689b1203..f2d9ffa4 100644 --- a/pelican/tests/output/custom/second-article.html +++ b/pelican/tests/output/custom/second-article.html @@ -72,7 +72,7 @@
            -

            blogroll

            +

            links

            • Biologeek
            • Filyb
            • diff --git a/pelican/tests/output/custom/tag/bar.html b/pelican/tests/output/custom/tag/bar.html index f462ffbd..b15860f1 100644 --- a/pelican/tests/output/custom/tag/bar.html +++ b/pelican/tests/output/custom/tag/bar.html @@ -113,7 +113,7 @@ YEAH !

            -

            blogroll

            +

            links

            • Biologeek
            • Filyb
            • diff --git a/pelican/tests/output/custom/tag/baz.html b/pelican/tests/output/custom/tag/baz.html index 2c9b1d08..a581e319 100644 --- a/pelican/tests/output/custom/tag/baz.html +++ b/pelican/tests/output/custom/tag/baz.html @@ -70,7 +70,7 @@
            -

            blogroll

            +

            links

            • Biologeek
            • Filyb
            • diff --git a/pelican/tests/output/custom/tag/foo.html b/pelican/tests/output/custom/tag/foo.html index 9c58956b..36282dd7 100644 --- a/pelican/tests/output/custom/tag/foo.html +++ b/pelican/tests/output/custom/tag/foo.html @@ -83,7 +83,7 @@ as well as inline markup.

            -

            blogroll

            +

            links

            • Biologeek
            • Filyb
            • diff --git a/pelican/tests/output/custom/tag/foobar.html b/pelican/tests/output/custom/tag/foobar.html index 7d17d1fb..ab84124c 100644 --- a/pelican/tests/output/custom/tag/foobar.html +++ b/pelican/tests/output/custom/tag/foobar.html @@ -62,7 +62,7 @@
              -

              blogroll

              +

              links

              • Biologeek
              • Filyb
              • diff --git a/pelican/tests/output/custom/tag/oh.html b/pelican/tests/output/custom/tag/oh.html index 855d1f8b..c7f9701d 100644 --- a/pelican/tests/output/custom/tag/oh.html +++ b/pelican/tests/output/custom/tag/oh.html @@ -36,7 +36,7 @@
              -

              blogroll

              +

              links

              • Biologeek
              • Filyb
              • diff --git a/pelican/tests/output/custom/tag/yeah.html b/pelican/tests/output/custom/tag/yeah.html index e3c765fa..c14f1f47 100644 --- a/pelican/tests/output/custom/tag/yeah.html +++ b/pelican/tests/output/custom/tag/yeah.html @@ -54,7 +54,7 @@ YEAH !

                -

                blogroll

                +

                links

                • Biologeek
                • Filyb
                • diff --git a/pelican/tests/output/custom/tags.html b/pelican/tests/output/custom/tags.html index 23f70c0f..9d3e2bdf 100644 --- a/pelican/tests/output/custom/tags.html +++ b/pelican/tests/output/custom/tags.html @@ -43,7 +43,7 @@
                  -

                  blogroll

                  +

                  links

                  • Biologeek
                  • Filyb
                  • diff --git a/pelican/tests/output/custom/this-is-a-super-article.html b/pelican/tests/output/custom/this-is-a-super-article.html index e370769c..a36bf412 100644 --- a/pelican/tests/output/custom/this-is-a-super-article.html +++ b/pelican/tests/output/custom/this-is-a-super-article.html @@ -85,7 +85,7 @@
                  -

                  blogroll

                  +

                  links

                  • Biologeek
                  • Filyb
                  • diff --git a/pelican/tests/output/custom/unbelievable.html b/pelican/tests/output/custom/unbelievable.html index 2e51cf07..77a16a8d 100644 --- a/pelican/tests/output/custom/unbelievable.html +++ b/pelican/tests/output/custom/unbelievable.html @@ -102,7 +102,7 @@ pelican.conf, it will have nothing in default.

                  -

                  blogroll

                  +

                  links

                  • Biologeek
                  • Filyb
                  • diff --git a/pelican/tests/output/custom_locale/archives.html b/pelican/tests/output/custom_locale/archives.html index 68b46b14..ed375051 100644 --- a/pelican/tests/output/custom_locale/archives.html +++ b/pelican/tests/output/custom_locale/archives.html @@ -56,7 +56,7 @@
                  -

                  blogroll

                  +

                  links

                  • Biologeek
                  • Filyb
                  • diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau.html b/pelican/tests/output/custom_locale/author/alexis-metaireau.html index f8b36655..299578cf 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau.html @@ -129,7 +129,7 @@
                  -

                  blogroll

                  +

                  links

                  • Biologeek
                  • Filyb
                  • diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau2.html b/pelican/tests/output/custom_locale/author/alexis-metaireau2.html index 5d9e6c3c..6f3a0b71 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau2.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau2.html @@ -143,7 +143,7 @@ YEAH !

                  -

                  blogroll

                  +

                  links

                  • Biologeek
                  • Filyb
                  • diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html index 2fea24c3..6eb5645e 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html @@ -94,7 +94,7 @@ pelican.conf, it will ...

                  -

                  blogroll

                  +

                  links

                  • Biologeek
                  • Filyb
                  • diff --git a/pelican/tests/output/custom_locale/authors.html b/pelican/tests/output/custom_locale/authors.html index 91ea479d..5cd227d9 100644 --- a/pelican/tests/output/custom_locale/authors.html +++ b/pelican/tests/output/custom_locale/authors.html @@ -38,7 +38,7 @@
                    -

                    blogroll

                    +

                    links

                    • Biologeek
                    • Filyb
                    • diff --git a/pelican/tests/output/custom_locale/categories.html b/pelican/tests/output/custom_locale/categories.html index ddb1377b..17f44be7 100644 --- a/pelican/tests/output/custom_locale/categories.html +++ b/pelican/tests/output/custom_locale/categories.html @@ -36,7 +36,7 @@
                    -

                    blogroll

                    +

                    links

                    • Biologeek
                    • Filyb
                    • diff --git a/pelican/tests/output/custom_locale/category/bar.html b/pelican/tests/output/custom_locale/category/bar.html index c416b358..2aeef95c 100644 --- a/pelican/tests/output/custom_locale/category/bar.html +++ b/pelican/tests/output/custom_locale/category/bar.html @@ -54,7 +54,7 @@ YEAH !

                      -

                      blogroll

                      +

                      links

                      • Biologeek
                      • Filyb
                      • diff --git a/pelican/tests/output/custom_locale/category/cat1.html b/pelican/tests/output/custom_locale/category/cat1.html index 871b2e3f..48c3ba18 100644 --- a/pelican/tests/output/custom_locale/category/cat1.html +++ b/pelican/tests/output/custom_locale/category/cat1.html @@ -123,7 +123,7 @@
                      -

                      blogroll

                      +

                      links

                      • Biologeek
                      • Filyb
                      • diff --git a/pelican/tests/output/custom_locale/category/misc.html b/pelican/tests/output/custom_locale/category/misc.html index f44f725d..d977a4cc 100644 --- a/pelican/tests/output/custom_locale/category/misc.html +++ b/pelican/tests/output/custom_locale/category/misc.html @@ -134,7 +134,7 @@ pelican.conf, it will ...

                      -

                      blogroll

                      +

                      links

                      • Biologeek
                      • Filyb
                      • diff --git a/pelican/tests/output/custom_locale/category/yeah.html b/pelican/tests/output/custom_locale/category/yeah.html index c5e6c7f0..a00e2028 100644 --- a/pelican/tests/output/custom_locale/category/yeah.html +++ b/pelican/tests/output/custom_locale/category/yeah.html @@ -62,7 +62,7 @@
                        -

                        blogroll

                        +

                        links

                        • Biologeek
                        • Filyb
                        • diff --git a/pelican/tests/output/custom_locale/drafts/a-draft-article.html b/pelican/tests/output/custom_locale/drafts/a-draft-article.html index dfd4f00e..c1817e25 100644 --- a/pelican/tests/output/custom_locale/drafts/a-draft-article.html +++ b/pelican/tests/output/custom_locale/drafts/a-draft-article.html @@ -56,7 +56,7 @@ listed anywhere else.

                        -

                        blogroll

                        +

                        links

                        • Biologeek
                        • Filyb
                        • diff --git a/pelican/tests/output/custom_locale/index.html b/pelican/tests/output/custom_locale/index.html index 3ad201de..f1174c31 100644 --- a/pelican/tests/output/custom_locale/index.html +++ b/pelican/tests/output/custom_locale/index.html @@ -129,7 +129,7 @@
                        -

                        blogroll

                        +

                        links

                        • Biologeek
                        • Filyb
                        • diff --git a/pelican/tests/output/custom_locale/index2.html b/pelican/tests/output/custom_locale/index2.html index bbd1efd0..286ff9c3 100644 --- a/pelican/tests/output/custom_locale/index2.html +++ b/pelican/tests/output/custom_locale/index2.html @@ -143,7 +143,7 @@ YEAH !

                        -

                        blogroll

                        +

                        links

                        • Biologeek
                        • Filyb
                        • diff --git a/pelican/tests/output/custom_locale/index3.html b/pelican/tests/output/custom_locale/index3.html index 926bc25e..c30b7bb3 100644 --- a/pelican/tests/output/custom_locale/index3.html +++ b/pelican/tests/output/custom_locale/index3.html @@ -94,7 +94,7 @@ pelican.conf, it will ...

                        -

                        blogroll

                        +

                        links

                        • Biologeek
                        • Filyb
                        • diff --git a/pelican/tests/output/custom_locale/jinja2_template.html b/pelican/tests/output/custom_locale/jinja2_template.html index 21f678f9..a098ed49 100644 --- a/pelican/tests/output/custom_locale/jinja2_template.html +++ b/pelican/tests/output/custom_locale/jinja2_template.html @@ -33,7 +33,7 @@ Some text
                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/oh-yeah-fr.html b/pelican/tests/output/custom_locale/oh-yeah-fr.html index f5cae7cf..33e05c3b 100644 --- a/pelican/tests/output/custom_locale/oh-yeah-fr.html +++ b/pelican/tests/output/custom_locale/oh-yeah-fr.html @@ -72,7 +72,7 @@ Translations:
                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/override/index.html b/pelican/tests/output/custom_locale/override/index.html index 3753ba84..8ab5f7eb 100644 --- a/pelican/tests/output/custom_locale/override/index.html +++ b/pelican/tests/output/custom_locale/override/index.html @@ -37,7 +37,7 @@ at a custom location.

                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/pages/this-is-a-test-hidden-page.html b/pelican/tests/output/custom_locale/pages/this-is-a-test-hidden-page.html index f8c06b1f..41beb5a3 100644 --- a/pelican/tests/output/custom_locale/pages/this-is-a-test-hidden-page.html +++ b/pelican/tests/output/custom_locale/pages/this-is-a-test-hidden-page.html @@ -37,7 +37,7 @@ Anyone can see this page but it's not linked to anywhere!

                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/pages/this-is-a-test-page.html b/pelican/tests/output/custom_locale/pages/this-is-a-test-page.html index a4b171c9..b548b52c 100644 --- a/pelican/tests/output/custom_locale/pages/this-is-a-test-page.html +++ b/pelican/tests/output/custom_locale/pages/this-is-a-test-page.html @@ -37,7 +37,7 @@
                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html b/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html index def80358..b7018b64 100644 --- a/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html +++ b/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html @@ -85,7 +85,7 @@
                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html b/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html index a6ea8699..3c364ffc 100644 --- a/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html +++ b/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html @@ -102,7 +102,7 @@ pelican.conf, it will have nothing in default.

                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html b/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html index 59fd6edd..3ec06207 100644 --- a/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html +++ b/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html @@ -77,7 +77,7 @@ YEAH !

                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html b/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html index e6d585ff..81421b37 100644 --- a/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html +++ b/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html @@ -71,7 +71,7 @@
                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html b/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html index d768e15f..64bab330 100644 --- a/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html +++ b/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html @@ -70,7 +70,7 @@
                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html b/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html index b223473a..60de5faa 100644 --- a/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html +++ b/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html @@ -70,7 +70,7 @@
                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html b/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html index 3bd59fa5..13aa5797 100644 --- a/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html +++ b/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html @@ -70,7 +70,7 @@
                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html b/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html index aff4d61f..9608314f 100644 --- a/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html +++ b/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html @@ -72,7 +72,7 @@
                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html b/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html index 659004bd..9b3df468 100644 --- a/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html +++ b/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html @@ -70,7 +70,7 @@
                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/second-article-fr.html b/pelican/tests/output/custom_locale/second-article-fr.html index 79fb5406..dc17237f 100644 --- a/pelican/tests/output/custom_locale/second-article-fr.html +++ b/pelican/tests/output/custom_locale/second-article-fr.html @@ -72,7 +72,7 @@
                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/tag/bar.html b/pelican/tests/output/custom_locale/tag/bar.html index c1f33e64..12f368aa 100644 --- a/pelican/tests/output/custom_locale/tag/bar.html +++ b/pelican/tests/output/custom_locale/tag/bar.html @@ -113,7 +113,7 @@ YEAH !

                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/tag/baz.html b/pelican/tests/output/custom_locale/tag/baz.html index 597199e7..8a2c1c31 100644 --- a/pelican/tests/output/custom_locale/tag/baz.html +++ b/pelican/tests/output/custom_locale/tag/baz.html @@ -70,7 +70,7 @@
                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/tag/foo.html b/pelican/tests/output/custom_locale/tag/foo.html index 288f1768..c08baed4 100644 --- a/pelican/tests/output/custom_locale/tag/foo.html +++ b/pelican/tests/output/custom_locale/tag/foo.html @@ -83,7 +83,7 @@ as well as inline markup.

                          -

                          blogroll

                          +

                          links

                          • Biologeek
                          • Filyb
                          • diff --git a/pelican/tests/output/custom_locale/tag/foobar.html b/pelican/tests/output/custom_locale/tag/foobar.html index 59dcede1..42bb1325 100644 --- a/pelican/tests/output/custom_locale/tag/foobar.html +++ b/pelican/tests/output/custom_locale/tag/foobar.html @@ -62,7 +62,7 @@
                            -

                            blogroll

                            +

                            links

                            • Biologeek
                            • Filyb
                            • diff --git a/pelican/tests/output/custom_locale/tag/oh.html b/pelican/tests/output/custom_locale/tag/oh.html index 855d1f8b..c7f9701d 100644 --- a/pelican/tests/output/custom_locale/tag/oh.html +++ b/pelican/tests/output/custom_locale/tag/oh.html @@ -36,7 +36,7 @@
                            -

                            blogroll

                            +

                            links

                            • Biologeek
                            • Filyb
                            • diff --git a/pelican/tests/output/custom_locale/tag/yeah.html b/pelican/tests/output/custom_locale/tag/yeah.html index 4dc36ce5..581c67c0 100644 --- a/pelican/tests/output/custom_locale/tag/yeah.html +++ b/pelican/tests/output/custom_locale/tag/yeah.html @@ -54,7 +54,7 @@ YEAH !

                              -

                              blogroll

                              +

                              links

                              • Biologeek
                              • Filyb
                              • diff --git a/pelican/tests/output/custom_locale/tags.html b/pelican/tests/output/custom_locale/tags.html index aa182ab6..59a36ace 100644 --- a/pelican/tests/output/custom_locale/tags.html +++ b/pelican/tests/output/custom_locale/tags.html @@ -43,7 +43,7 @@
                                -

                                blogroll

                                +

                                links

                                • Biologeek
                                • Filyb
                                • diff --git a/pelican/themes/notmyidea/templates/base.html b/pelican/themes/notmyidea/templates/base.html index 188715d4..7818c235 100644 --- a/pelican/themes/notmyidea/templates/base.html +++ b/pelican/themes/notmyidea/templates/base.html @@ -41,7 +41,7 @@
                                  {% if LINKS %}
                                  -

                                  blogroll

                                  +

                                  {{ LINKS_WIDGET_NAME | default('links') }}

                                    {% for name, link in LINKS %}
                                  • {{ name }}
                                  • @@ -51,7 +51,7 @@ {% endif %} {% if SOCIAL or FEED_ALL_ATOM or FEED_ALL_RSS %}
    - + \ No newline at end of file diff --git a/pelican/tests/output/basic/article-1.html b/pelican/tests/output/basic/article-1.html index b09eef79..a5c92675 100644 --- a/pelican/tests/output/basic/article-1.html +++ b/pelican/tests/output/basic/article-1.html @@ -38,7 +38,7 @@ Published: Thu 17 February 2011 -

    In cat1.

    +

    In cat1.

    Article 1

    @@ -65,4 +65,4 @@
    - + \ No newline at end of file diff --git a/pelican/tests/output/basic/article-2.html b/pelican/tests/output/basic/article-2.html index e340a4f6..1a182c5f 100644 --- a/pelican/tests/output/basic/article-2.html +++ b/pelican/tests/output/basic/article-2.html @@ -38,7 +38,7 @@ Published: Thu 17 February 2011 -

    In cat1.

    +

    In cat1.

    Article 2

    @@ -65,4 +65,4 @@
    - + \ No newline at end of file diff --git a/pelican/tests/output/basic/article-3.html b/pelican/tests/output/basic/article-3.html index 08bf4fc1..7fab0edd 100644 --- a/pelican/tests/output/basic/article-3.html +++ b/pelican/tests/output/basic/article-3.html @@ -38,7 +38,7 @@ Published: Thu 17 February 2011 -

    In cat1.

    +

    In cat1.

    Article 3

    @@ -65,4 +65,4 @@
    - + \ No newline at end of file diff --git a/pelican/tests/output/basic/author/alexis-metaireau.html b/pelican/tests/output/basic/author/alexis-metaireau.html index eeca537a..3fe6c95e 100644 --- a/pelican/tests/output/basic/author/alexis-metaireau.html +++ b/pelican/tests/output/basic/author/alexis-metaireau.html @@ -38,10 +38,10 @@
    - By Alexis Métaireau + By Alexis Métaireau
    -

    In yeah.

    -

    tags: foobarfoobar

    +

    In yeah.

    +

    tags: foo bar foobar

    Some content here !

    This is a simple title

    @@ -74,10 +74,10 @@
    - By Alexis Métaireau + By Alexis Métaireau
    -

    In bar.

    -

    tags: ohbaryeah

    +

    In bar.

    +

    tags: oh bar yeah

    Why not ?

    After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! @@ -88,8 +88,8 @@ YEAH !

    read more
    - - + +
    - + \ No newline at end of file diff --git a/pelican/tests/output/basic/authors.html b/pelican/tests/output/basic/authors.html index 288543b5..c580269f 100644 --- a/pelican/tests/output/basic/authors.html +++ b/pelican/tests/output/basic/authors.html @@ -51,4 +51,4 @@
    - + \ No newline at end of file diff --git a/pelican/tests/output/basic/categories.html b/pelican/tests/output/basic/categories.html index 9a6682c0..5aa2791f 100644 --- a/pelican/tests/output/basic/categories.html +++ b/pelican/tests/output/basic/categories.html @@ -49,4 +49,4 @@
    - + \ No newline at end of file diff --git a/pelican/tests/output/basic/category/bar.html b/pelican/tests/output/basic/category/bar.html index d3eb38da..6ae80ed1 100644 --- a/pelican/tests/output/basic/category/bar.html +++ b/pelican/tests/output/basic/category/bar.html @@ -34,10 +34,10 @@
    - By Alexis Métaireau + By Alexis Métaireau
    -

    In bar.

    -

    tags: ohbaryeah

    +

    In bar.

    +

    tags: oh bar yeah

    Why not ?

    After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! @@ -65,4 +65,4 @@ YEAH !

    - + \ No newline at end of file diff --git a/pelican/tests/output/basic/category/cat1.html b/pelican/tests/output/basic/category/cat1.html index f21bc9ab..369146d0 100644 --- a/pelican/tests/output/basic/category/cat1.html +++ b/pelican/tests/output/basic/category/cat1.html @@ -33,7 +33,7 @@ Published: Wed 20 April 2011 -

    In cat1.

    +

    In cat1.

    You're mutually oblivious.

    a root-relative link to unbelievable @@ -56,7 +56,7 @@ Published: Thu 17 February 2011 -

    In cat1.

    +

    In cat1.

    Article 1

    @@ -76,7 +76,7 @@ Published: Thu 17 February 2011 -

    In cat1.

    +

    In cat1.

    Article 2

    @@ -96,15 +96,15 @@ Published: Thu 17 February 2011 -

    In cat1.

    +

    In cat1.

    Article 3

    read more
    - - + +