diff --git a/.travis.yml b/.travis.yml index 762ada98..6641d4cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,18 @@ language: python python: - "3.6" env: - - TOX_ENV=docs - - TOX_ENV=flake8 - - TOX_ENV=py27 - - TOX_ENV=py35 - - TOX_ENV=py36 + global: + - PYPI_USERNAME=autopub + - secure: "h5V/+YL+CrqvfAesNkSb824Ngk5x+f0eFzj/LBbmnzjvArKAmc6R6WGyx8SDD7WF/PlaTf0M1fH3a7pjIS8Ee+TS1Rb0Lt1HPqUs1yntg1+Js2ZQp3p20wfsDc+bZ4/2g8xLsSMv1EJ4np7/GJ5fXqpSxjr/Xs5LYA7ZLwNNwDw=" + - secure: "GiDFfmjH7uzYNnkjQMV/mIkbRdmgkGmtbFPeaj9taBNA5tPp3IBt3GOOS6UL/zm9xiwu9Xo6sxZWkGzY19Hsdv28YPH34N3abo0QSnz4IGiHs152Hi7Qi6Tb0QkT5D3OxuSIm8LmFL7+su89Q7vBFowrT6HL1Mn8CDDWSj3eqbo=" + - TWINE_USERNAME=$PYPI_USERNAME + - TWINE_PASSWORD=$PYPI_PASSWORD + matrix: + - TOX_ENV=docs + - TOX_ENV=flake8 + - TOX_ENV=py27 + - TOX_ENV=py35 + - TOX_ENV=py36 matrix: include: - python: 3.7 @@ -23,8 +30,25 @@ before_install: install: - pip install tox==2.5.0 script: tox -e $TOX_ENV +before_deploy: + - 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then travis_terminate 0; fi' + - pip install githubrelease + - pip install --pre autopub + - autopub check || travis_terminate 0 + - pip install poetry + - pip install twine + - git checkout ${TRAVIS_BRANCH} + - git remote set-url origin https://$GITHUB_TOKEN@github.com/$TRAVIS_REPO_SLUG +deploy: + provider: script + script: autopub deploy + skip_cleanup: true + on: + branch: master + python: "3.7" +# The channel name "irc.freenode.org#pelican" is encrypted against getpelican/pelican to prevent IRC spam of forks notifications: irc: channels: - - "irc.freenode.org#pelican" + - secure: "JP57f61QovrhmLoAF6oPOzIK2aXGfSO06FHg7yiuKBOWMiaxQejZUGJX919muCLhWJXDugsviIqCMoAWwNV3o1WQbqIr+G5TR+N9MrtCs4Zi6vpGj09bR8giKUKx+PPKEoe1Ew56E4y2LxzGO4Lj9hZx8M2YVdwPNWrWZgp6WXE=" on_success: change diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 49660ea1..7ae47593 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -97,6 +97,14 @@ Using Git and GitHub 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. +* Add a ``RELEASE.md`` file in the root of the project that contains the + release type (major, minor, patch) and a summary of the changes that will be + used as the release changelog entry. For example:: + + Release type: minor + + Reload browser window upon changes to content, settings, or theme + * Check for unnecessary whitespace via ``git diff --check`` before committing. * 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. diff --git a/docs/changelog.rst b/docs/changelog.rst index be87ed17..befe358a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,30 @@ Release history ############### +4.1.3 - 2019-10-09 +================== + +* Fix quick-start docs regarding `pelican --listen` +* Set default listen address to 127.0.0.1 +* Add extra/optional Markdown dependency to setup.py +* Use correct SSH port syntax for rsync in tasks.py +* Place all deprecated settings handling together +* Add related project URLs for display on PyPI +* Skip some tests on Windows that can't pass due to filesystem differences + +4.1.2 - 2019-09-23 +================== + +Fix pelican.settings.load_source to avoid caching issues - PR #2621 + +4.1.1 - 2019-08-23 +================== + +* Add AutoPub to auto-publish releases on PR merge +* Add CSS classes for reStructuredText figures +* Pass `argv` to Pelican `main` entrypoint +* Set default content status to a blank string rather than `None` + 4.1.0 - 2019-07-14 ================== diff --git a/docs/install.rst b/docs/install.rst index 571de95e..2da7b9be 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -9,6 +9,10 @@ You can install Pelican via several different methods. The simplest is via pip install pelican +Or, if you plan on using Markdown:: + + pip install pelican[Markdown] + (Keep in mind that operating systems will often require you to prefix the above command with ``sudo`` in order to install Pelican system-wide.) @@ -40,7 +44,11 @@ Optional packages ----------------- If you plan on using `Markdown `_ as a -markup format, you'll need to install the Markdown library:: +markup format, you can install Pelican with Markdown support:: + + pip install pelican[Markdown] + +Or you might need to install it a posteriori:: pip install Markdown diff --git a/docs/publish.rst b/docs/publish.rst index 96d67d58..489558d8 100644 --- a/docs/publish.rst +++ b/docs/publish.rst @@ -54,20 +54,12 @@ 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. +assets, running Pelican's simple built-in web server will often provide a more +reliable previewing experience:: -For Python 2, run:: + pelican --listen - cd output - python -m SimpleHTTPServer - -For Python 3, run:: - - cd output - python -m http.server - -Once the basic server has been started, you can preview your site at +Once the web server has been started, you can preview your site at: http://localhost:8000/ Deployment diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 4e6714b4..484a318f 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -8,10 +8,10 @@ Installation ------------ Install Pelican (and optionally Markdown if you intend to use it) on Python -2.7.x or Python 3.3+ by running the following command in your preferred +2.7.x or Python 3.5+ by running the following command in your preferred terminal, prefixing with ``sudo`` if permissions warrant:: - pip install pelican markdown + pip install pelican[Markdown] Create a project ---------------- @@ -50,18 +50,18 @@ Given that this example article is in Markdown format, save it as Generate your site ------------------ -From your site directory, run the ``pelican`` command to generate your site:: +From your project root directory, run the ``pelican`` command to generate your site:: pelican content -Your site has now been generated inside the ``output`` directory. (You may see +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, navigate to your generated output directory and +Open a new terminal session, navigate to your project root directory, and run the following command to launch Pelican's web server:: pelican --listen diff --git a/docs/settings.rst b/docs/settings.rst index 57b59cea..0e21b931 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -991,7 +991,7 @@ By default, pages subsequent to ``.../foo.html`` are created as ``.../foo2.html``, etc. The ``PAGINATION_PATTERNS`` setting can be used to change this. It takes a sequence of triples, where each triple consists of:: - (minimum_page, page_url, page_save_as,) + (minimum_page, page_url, page_save_as,) For ``page_url`` and ``page_save_as``, you may use a number of variables. ``{url}`` and ``{save_as}`` correspond respectively to the ``*_URL`` and @@ -1005,7 +1005,7 @@ subsequent pages at ``.../page/2/`` etc, you could set ``PAGINATION_PATTERNS`` as follows:: PAGINATION_PATTERNS = ( - (1, '{url}', '{save_as}`, + (1, '{url}', '{save_as}', (2, '{base_name}/page/{number}/', '{base_name}/page/{number}/index.html'), ) diff --git a/docs/themes.rst b/docs/themes.rst index 354e0c36..b00d0236 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -75,7 +75,7 @@ output_file The name of the file currently being generated. For articles The list of articles, ordered descending by date. All the elements are `Article` objects, so you can access their attributes (e.g. title, summary, author - etc.). Sometimes this is shadowed (for instance in + etc.). Sometimes this is shadowed (for instance, in the tags page). You will then find info about it in the `all_articles` variable. dates The same list of articles, but ordered by date, diff --git a/pelican/__init__.py b/pelican/__init__.py index 0a06d905..499ded04 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -11,7 +11,6 @@ import logging import multiprocessing import os import pprint -import re import sys import time import traceback @@ -52,7 +51,6 @@ class Pelican(object): # define the default settings self.settings = settings - self._handle_deprecation() self.path = settings['PATH'] self.theme = settings['THEME'] @@ -94,65 +92,6 @@ class Pelican(object): logger.debug('Restoring system path') sys.path = _sys_path - def _handle_deprecation(self): - - if self.settings.get('CLEAN_URLS', False): - logger.warning('Found deprecated `CLEAN_URLS` in settings.' - ' Modifying the following settings for the' - ' same behaviour.') - - self.settings['ARTICLE_URL'] = '{slug}/' - self.settings['ARTICLE_LANG_URL'] = '{slug}-{lang}/' - self.settings['PAGE_URL'] = 'pages/{slug}/' - self.settings['PAGE_LANG_URL'] = 'pages/{slug}-{lang}/' - - for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL', - 'PAGE_LANG_URL'): - logger.warning("%s = '%s'", setting, self.settings[setting]) - - if self.settings.get('AUTORELOAD_IGNORE_CACHE'): - logger.warning('Found deprecated `AUTORELOAD_IGNORE_CACHE` in ' - 'settings. Use --ignore-cache instead.') - self.settings.pop('AUTORELOAD_IGNORE_CACHE') - - if self.settings.get('ARTICLE_PERMALINK_STRUCTURE', False): - logger.warning('Found deprecated `ARTICLE_PERMALINK_STRUCTURE` in' - ' settings. Modifying the following settings for' - ' the same behaviour.') - - structure = self.settings['ARTICLE_PERMALINK_STRUCTURE'] - - # Convert %(variable) into {variable}. - structure = re.sub(r'%\((\w+)\)s', r'{\g<1>}', structure) - - # Convert %x into {date:%x} for strftime - structure = re.sub(r'(%[A-z])', r'{date:\g<1>}', structure) - - # Strip a / prefix - structure = re.sub('^/', '', structure) - - for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL', - 'PAGE_LANG_URL', 'DRAFT_URL', 'DRAFT_LANG_URL', - 'ARTICLE_SAVE_AS', 'ARTICLE_LANG_SAVE_AS', - 'DRAFT_SAVE_AS', 'DRAFT_LANG_SAVE_AS', - 'PAGE_SAVE_AS', 'PAGE_LANG_SAVE_AS'): - self.settings[setting] = os.path.join(structure, - self.settings[setting]) - logger.warning("%s = '%s'", setting, self.settings[setting]) - - for new, old in [('FEED', 'FEED_ATOM'), ('TAG_FEED', 'TAG_FEED_ATOM'), - ('CATEGORY_FEED', 'CATEGORY_FEED_ATOM'), - ('TRANSLATION_FEED', 'TRANSLATION_FEED_ATOM')]: - if self.settings.get(new, False): - logger.warning( - 'Found deprecated `%(new)s` in settings. Modify %(new)s ' - 'to %(old)s in your settings and theme for the same ' - 'behavior. Temporarily setting %(old)s for backwards ' - 'compatibility.', - {'new': new, 'old': old} - ) - self.settings[old] = self.settings[new] - def run(self): """Run the generators and return""" start_time = time.time() @@ -307,7 +246,7 @@ class PrintSettings(argparse.Action): parser.exit() -def parse_arguments(): +def parse_arguments(argv=None): parser = argparse.ArgumentParser( description='A tool to generate a static blog, ' ' with restructured text input files.', @@ -400,7 +339,7 @@ def parse_arguments(): help='IP to bind to when serving files via HTTP ' '(default: 127.0.0.1)') - args = parser.parse_args() + args = parser.parse_args(argv) if args.port is not None and not args.listen: logger.warning('--port without --listen has no effect') @@ -560,8 +499,8 @@ def listen(server, port, output, excqueue=None): return -def main(): - args = parse_arguments() +def main(argv=None): + args = parse_arguments(argv) logs_dedup_min_level = getattr(logging, args.logs_dedup_min_level) init_logging(args.verbosity, args.fatal, logs_dedup_min_level=logs_dedup_min_level) diff --git a/pelican/contents.py b/pelican/contents.py index 9292f14f..a862db2d 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -141,7 +141,8 @@ class Content(object): # manage status if not hasattr(self, 'status'): - self.status = getattr(self, 'default_status', None) + # Previous default of None broke comment plugins and perhaps others + self.status = getattr(self, 'default_status', '') # store the summary metadata if it is set if 'summary' in metadata: diff --git a/pelican/settings.py b/pelican/settings.py index b0f9c454..f9f58be1 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -14,12 +14,16 @@ import six from pelican.log import LimitFilter + try: - # SourceFileLoader is the recommended way in Python 3.3+ - from importlib.machinery import SourceFileLoader + # spec_from_file_location is the recommended way in Python 3.5+ + import importlib.util def load_source(name, path): - return SourceFileLoader(name, path).load_module() + spec = importlib.util.spec_from_file_location(name, path) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod except ImportError: # but it does not exist in Python 2.7, so fall back to imp import imp @@ -167,7 +171,7 @@ DEFAULT_CONFIG = { 'WRITE_SELECTED': [], 'FORMATTED_FIELDS': ['summary'], 'PORT': 8000, - 'BIND': '', + 'BIND': '127.0.0.1', } PYGMENTS_RST_OPTIONS = None @@ -439,6 +443,67 @@ def handle_deprecated_settings(settings): 'Falling back to default.', key) settings[key] = DEFAULT_CONFIG[key] + # CLEAN_URLS + if settings.get('CLEAN_URLS', False): + logger.warning('Found deprecated `CLEAN_URLS` in settings.' + ' Modifying the following settings for the' + ' same behaviour.') + + settings['ARTICLE_URL'] = '{slug}/' + settings['ARTICLE_LANG_URL'] = '{slug}-{lang}/' + settings['PAGE_URL'] = 'pages/{slug}/' + settings['PAGE_LANG_URL'] = 'pages/{slug}-{lang}/' + + for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL', + 'PAGE_LANG_URL'): + logger.warning("%s = '%s'", setting, settings[setting]) + + # AUTORELOAD_IGNORE_CACHE -> --ignore-cache + if settings.get('AUTORELOAD_IGNORE_CACHE'): + logger.warning('Found deprecated `AUTORELOAD_IGNORE_CACHE` in ' + 'settings. Use --ignore-cache instead.') + settings.pop('AUTORELOAD_IGNORE_CACHE') + + # ARTICLE_PERMALINK_STRUCTURE + if settings.get('ARTICLE_PERMALINK_STRUCTURE', False): + logger.warning('Found deprecated `ARTICLE_PERMALINK_STRUCTURE` in' + ' settings. Modifying the following settings for' + ' the same behaviour.') + + structure = settings['ARTICLE_PERMALINK_STRUCTURE'] + + # Convert %(variable) into {variable}. + structure = re.sub(r'%\((\w+)\)s', r'{\g<1>}', structure) + + # Convert %x into {date:%x} for strftime + structure = re.sub(r'(%[A-z])', r'{date:\g<1>}', structure) + + # Strip a / prefix + structure = re.sub('^/', '', structure) + + for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL', + 'PAGE_LANG_URL', 'DRAFT_URL', 'DRAFT_LANG_URL', + 'ARTICLE_SAVE_AS', 'ARTICLE_LANG_SAVE_AS', + 'DRAFT_SAVE_AS', 'DRAFT_LANG_SAVE_AS', + 'PAGE_SAVE_AS', 'PAGE_LANG_SAVE_AS'): + settings[setting] = os.path.join(structure, + settings[setting]) + logger.warning("%s = '%s'", setting, settings[setting]) + + # {,TAG,CATEGORY,TRANSLATION}_FEED -> {,TAG,CATEGORY,TRANSLATION}_FEED_ATOM + for new, old in [('FEED', 'FEED_ATOM'), ('TAG_FEED', 'TAG_FEED_ATOM'), + ('CATEGORY_FEED', 'CATEGORY_FEED_ATOM'), + ('TRANSLATION_FEED', 'TRANSLATION_FEED_ATOM')]: + if settings.get(new, False): + logger.warning( + 'Found deprecated `%(new)s` in settings. Modify %(new)s ' + 'to %(old)s in your settings and theme for the same ' + 'behavior. Temporarily setting %(old)s for backwards ' + 'compatibility.', + {'new': new, 'old': old} + ) + settings[old] = settings[new] + return settings diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 104bc889..efc438c8 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -14,9 +14,10 @@ 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, get_context, get_settings,\ - unittest -from pelican.utils import SafeDatetime, path_to_url, truncate_html_words +from pelican.tests.support import (LoggedTestCase, get_context, get_settings, + unittest) +from pelican.utils import (SafeDatetime, path_to_url, posixize_path, + truncate_html_words) # generate one paragraph, enclosed with

@@ -943,7 +944,7 @@ class TestStatic(LoggedTestCase): source_path=os.path.join('dir', 'foo.jpg'), context=self.settings.copy()) - expected_save_as = os.path.join('dir', 'foo.jpg') + expected_save_as = posixize_path(os.path.join('dir', 'foo.jpg')) self.assertEqual(static.status, 'draft') self.assertEqual(static.save_as, expected_save_as) self.assertEqual(static.url, path_to_url(expected_save_as)) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 267571da..2455d1f4 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -1089,7 +1089,12 @@ class TestStaticGenerator(unittest.TestCase): os.mkdir(os.path.join(self.temp_output, "static")) self.generator.fallback_to_symlinks = True self.generator.generate_context() - self.generator.generate_output(None) + try: + self.generator.generate_output(None) + except OSError as e: + # On Windows, possibly others, due to not holding symbolic link + # privilege + self.skipTest(e) self.assertTrue(os.path.islink(self.endfile)) def test_existing_symlink_is_considered_up_to_date(self): @@ -1097,7 +1102,11 @@ class TestStaticGenerator(unittest.TestCase): with open(self.startfile, "w") as f: f.write("staticcontent") os.mkdir(os.path.join(self.temp_output, "static")) - os.symlink(self.startfile, self.endfile) + try: + os.symlink(self.startfile, self.endfile) + except OSError as e: + # On Windows, possibly others + self.skipTest(e) staticfile = MagicMock() staticfile.source_path = self.startfile staticfile.save_as = self.endfile @@ -1109,7 +1118,11 @@ class TestStaticGenerator(unittest.TestCase): with open(self.startfile, "w") as f: f.write("staticcontent") os.mkdir(os.path.join(self.temp_output, "static")) - os.symlink("invalid", self.endfile) + try: + os.symlink("invalid", self.endfile) + except OSError as e: + # On Windows, possibly others + self.skipTest(e) staticfile = MagicMock() staticfile.source_path = self.startfile staticfile.save_as = self.endfile diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 2831eeed..444190d3 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -717,6 +717,8 @@ class TestDateFormatter(unittest.TestCase): class TestSanitisedJoin(unittest.TestCase): + @unittest.skipIf(platform == 'win32', + "Different filesystem root on Windows") def test_detect_parent_breakout(self): with six.assertRaisesRegex( self, @@ -727,6 +729,8 @@ class TestSanitisedJoin(unittest.TestCase): "../test" ) + @unittest.skipIf(platform == 'win32', + "Different filesystem root on Windows") def test_detect_root_breakout(self): with six.assertRaisesRegex( self, @@ -737,6 +741,8 @@ class TestSanitisedJoin(unittest.TestCase): "/test" ) + @unittest.skipIf(platform == 'win32', + "Different filesystem root on Windows") def test_pass_deep_subpaths(self): self.assertEqual( utils.sanitised_join( diff --git a/pelican/tools/templates/tasks.py.jinja2 b/pelican/tools/templates/tasks.py.jinja2 index a800b79b..0362eba7 100644 --- a/pelican/tools/templates/tasks.py.jinja2 +++ b/pelican/tools/templates/tasks.py.jinja2 @@ -23,8 +23,10 @@ CONFIG = { 'deploy_path': SETTINGS['OUTPUT_PATH'], {% if ssh %} # Remote server configuration - 'production': '{{ssh_user}}@{{ssh_host}}:{{ssh_port}}', - 'dest_path': '{{ssh_target_dir}}', + 'ssh_user': '{{ssh_user}}', + 'ssh_host': '{{ssh_host}}', + 'ssh_port': '{{ssh_port}}', + 'ssh_path': '{{ssh_target_dir}}', {% endif %} {% if cloudfiles %} # Rackspace Cloud Files configuration settings @@ -130,7 +132,8 @@ def publish(c): c.run('pelican -s {settings_publish}'.format(**CONFIG)) c.run( 'rsync --delete --exclude ".DS_Store" -pthrvz -c ' - '{} {production}:{dest_path}'.format( + '-e "ssh -p {ssh_port}" ' + '{} {ssh_user}@{ssh_host}:{ssh_path}'.format( CONFIG['deploy_path'].rstrip('/') + '/', **CONFIG)) diff --git a/pyproject.toml b/pyproject.toml index f27dc047..11c91c42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pelican" -version = "4.1.0" +version = "4.1.3" description = "Static site generator supporting Markdown and reStructuredText" authors = ["Justin Mayer "] license = "AGPLv3" @@ -59,6 +59,15 @@ markdown = ["markdown"] [tool.poetry.scripts] pelican = "pelican.__main__:main" +[tool.autopub] +project-name = "Pelican" +git-username = "botpub" +git-email = "botpub@autopub.rocks" +changelog-file = "docs/changelog.rst" +changelog-header = "###############" +version-header = "=" +version-strings = ["setup.py"] +build-system = "setuptools" + [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["setuptools >= 40.6.0", "wheel"] diff --git a/setup.py b/setup.py index f9076f4d..23857608 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ from os.path import join, relpath from setuptools import setup -version = "4.1.0" +version = "4.1.3" requires = ['feedgenerator >= 1.9', 'jinja2 >= 2.7', 'pygments', 'docutils', 'pytz >= 0a', 'blinker', 'unidecode', 'six >= 1.4', @@ -33,11 +33,17 @@ setup( name='pelican', version=version, url='https://getpelican.com/', - author='Alexis Metaireau', - maintainer='Justin Mayer', + author='Justin Mayer', author_email='authors@getpelican.com', description="Static site generator supporting reStructuredText and " "Markdown source content.", + project_urls={ + 'Documentation': 'https://docs.getpelican.com/', + 'Funding': 'https://donate.getpelican.com/', + 'Source': 'https://github.com/getpelican/pelican', + 'Tracker': 'https://github.com/getpelican/pelican/issues', + }, + keywords='static web site generator SSG reStructuredText Markdown', license='AGPLv3', long_description=description, packages=['pelican', 'pelican.tools'], @@ -55,6 +61,9 @@ setup( for name in names], }, install_requires=requires, + extras_require={ + 'Markdown': ['markdown~=3.1.1'] + }, entry_points=entry_points, classifiers=[ 'Development Status :: 5 - Production/Stable',